|
| 1 | +import { bench, run, group, summary } from "mitata"; |
| 2 | +import { readFileSync } from "fs"; |
| 3 | +import { join } from "path"; |
| 4 | + |
| 5 | +// JSON parser |
| 6 | +import { parse } from "../src/json"; |
| 7 | +// High-level API |
| 8 | +import { XcodeProject } from "../src/api/XcodeProject"; |
| 9 | +// Legacy xcode package for comparison |
| 10 | +import legacyXcode from "xcode"; |
| 11 | + |
| 12 | +const FIXTURES_DIR = join(__dirname, "../src/json/__tests__/fixtures"); |
| 13 | + |
| 14 | +// Test fixtures ordered by size (small to large) |
| 15 | +const fixtures = [ |
| 16 | + { name: "small (float)", file: "01-float.pbxproj", bytes: 264 }, |
| 17 | + { name: "swift", file: "project-swift.pbxproj", bytes: 18593 }, |
| 18 | + { name: "react-native-74", file: "project-rn74.pbxproj", bytes: 29812 }, |
| 19 | + { name: "expo-app-clip", file: "009-expo-app-clip.pbxproj", bytes: 39922 }, |
| 20 | + { name: "shopify-tophat", file: "shopify-tophat.pbxproj", bytes: 49021 }, |
| 21 | + { name: "AFNetworking", file: "AFNetworking.pbxproj", bytes: 101506 }, |
| 22 | + { name: "Cocoa-Application", file: "Cocoa-Application.pbxproj", bytes: 169497 }, |
| 23 | + { name: "swift-protobuf", file: "swift-protobuf.pbxproj", bytes: 263169 }, |
| 24 | +]; |
| 25 | + |
| 26 | +// Pre-load all fixture contents to measure pure parse time |
| 27 | +const fixtureContents = new Map<string, string>(); |
| 28 | +const fixturePaths = new Map<string, string>(); |
| 29 | + |
| 30 | +for (const fixture of fixtures) { |
| 31 | + const filePath = join(FIXTURES_DIR, fixture.file); |
| 32 | + fixtureContents.set(fixture.name, readFileSync(filePath, "utf8")); |
| 33 | + fixturePaths.set(fixture.name, filePath); |
| 34 | +} |
| 35 | + |
| 36 | +function formatSize(bytes: number): string { |
| 37 | + if (bytes < 1024) return `${bytes}B`; |
| 38 | + return `${(bytes / 1024).toFixed(0)}KB`; |
| 39 | +} |
| 40 | + |
| 41 | +// Calculate total size for summary |
| 42 | +const totalBytes = fixtures.reduce((sum, f) => sum + f.bytes, 0); |
| 43 | + |
| 44 | +console.log(`\n========================================`); |
| 45 | +console.log(`@bacons/xcode Parser Benchmark`); |
| 46 | +console.log(`========================================`); |
| 47 | +console.log(`Total fixture data: ${(totalBytes / 1024).toFixed(1)}KB`); |
| 48 | +console.log(`Fixtures: ${fixtures.length} files\n`); |
| 49 | + |
| 50 | +// Group 1: parse() across all fixtures |
| 51 | +group("parse() - all fixtures", () => { |
| 52 | + for (const fixture of fixtures) { |
| 53 | + const content = fixtureContents.get(fixture.name)!; |
| 54 | + bench(`${fixture.name} (${formatSize(fixture.bytes)})`, () => { |
| 55 | + parse(content); |
| 56 | + }); |
| 57 | + } |
| 58 | +}); |
| 59 | + |
| 60 | +// Group 2: Full XcodeProject load (parse + object graph inflation) |
| 61 | +group("XcodeProject.open() - Full load", () => { |
| 62 | + for (const fixture of fixtures) { |
| 63 | + const filePath = fixturePaths.get(fixture.name)!; |
| 64 | + bench(`${fixture.name} (${formatSize(fixture.bytes)})`, () => { |
| 65 | + XcodeProject.open(filePath); |
| 66 | + }); |
| 67 | + } |
| 68 | +}); |
| 69 | + |
| 70 | +// Group 3: Parse + build round-trip |
| 71 | +group("Round-trip (parse + build)", () => { |
| 72 | + const { build } = require("../src/json") as typeof import("../src/json"); |
| 73 | + |
| 74 | + for (const fixture of fixtures.slice(0, 5)) { |
| 75 | + const content = fixtureContents.get(fixture.name)!; |
| 76 | + bench(`${fixture.name} (${formatSize(fixture.bytes)})`, () => { |
| 77 | + const json = parse(content); |
| 78 | + build(json); |
| 79 | + }); |
| 80 | + } |
| 81 | +}); |
| 82 | + |
| 83 | +// Group 4: Throughput test - largest file |
| 84 | +group("Throughput (swift-protobuf 263KB)", () => { |
| 85 | + const largestContent = fixtureContents.get("swift-protobuf")!; |
| 86 | + const largestPath = fixturePaths.get("swift-protobuf")!; |
| 87 | + |
| 88 | + bench("parse() only", () => { |
| 89 | + parse(largestContent); |
| 90 | + }); |
| 91 | + |
| 92 | + bench("XcodeProject.open()", () => { |
| 93 | + XcodeProject.open(largestPath); |
| 94 | + }); |
| 95 | +}); |
| 96 | + |
| 97 | +// Group 5: Comparison with legacy xcode package |
| 98 | +summary(() => { |
| 99 | + group("vs legacy xcode (react-native 29KB)", () => { |
| 100 | + const content = fixtureContents.get("react-native-74")!; |
| 101 | + const filePath = fixturePaths.get("react-native-74")!; |
| 102 | + |
| 103 | + bench("@bacons/xcode parse()", () => { |
| 104 | + parse(content); |
| 105 | + }); |
| 106 | + |
| 107 | + bench("legacy xcode parseSync()", () => { |
| 108 | + legacyXcode.project(filePath).parseSync(); |
| 109 | + }); |
| 110 | + }); |
| 111 | +}); |
| 112 | + |
| 113 | +// Note: Legacy xcode crashes on swift-protobuf with: |
| 114 | +// "Expected "/*", "=", or [A-Za-z0-9_.] but "/" found" |
| 115 | +// This demonstrates the spec-compliance advantage of @bacons/xcode |
| 116 | + |
| 117 | +await run({ |
| 118 | + avg: true, |
| 119 | + json: false, |
| 120 | + colors: true, |
| 121 | + min_max: true, |
| 122 | + percentiles: true, |
| 123 | +}); |
| 124 | + |
| 125 | +// Print throughput summary |
| 126 | +console.log(`\n========================================`); |
| 127 | +console.log(`Throughput Summary`); |
| 128 | +console.log(`========================================`); |
| 129 | + |
| 130 | +const iterations = 20; |
| 131 | +const largestContent = fixtureContents.get("swift-protobuf")!; |
| 132 | +const largestBytes = fixtures.find(f => f.name === "swift-protobuf")!.bytes; |
| 133 | + |
| 134 | +const start = performance.now(); |
| 135 | +for (let i = 0; i < iterations; i++) { |
| 136 | + parse(largestContent); |
| 137 | +} |
| 138 | +const elapsed = performance.now() - start; |
| 139 | +const throughput = (largestBytes * iterations / 1024 / 1024) / (elapsed / 1000); |
| 140 | + |
| 141 | +console.log(`parse() throughput: ${throughput.toFixed(2)} MB/s`); |
| 142 | +console.log(`\nNote: Legacy xcode package crashes on swift-protobuf fixture`); |
| 143 | +console.log(` demonstrating @bacons/xcode's spec-compliance advantage.\n`); |
0 commit comments