wyreframe 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +692 -0
- package/README.md +65 -5
- package/package.json +8 -7
- package/src/index.ts +425 -0
- package/src/renderer/Renderer.gen.tsx +49 -0
- package/src/renderer/Renderer.mjs +41 -1
- package/src/renderer/Renderer.res +78 -0
- package/src/test/Expect.mjs +9 -0
- package/src/parser/Core/__tests__/Bounds_test.mjs +0 -326
- package/src/parser/Core/__tests__/Bounds_test.res +0 -412
- package/src/parser/Core/__tests__/Grid_test.mjs +0 -322
- package/src/parser/Core/__tests__/Grid_test.res +0 -319
- package/src/parser/Core/__tests__/Types_test.mjs +0 -614
- package/src/parser/Core/__tests__/Types_test.res +0 -650
- package/src/parser/Detector/__tests__/BoxTracer_test.mjs +0 -70
- package/src/parser/Detector/__tests__/BoxTracer_test.res +0 -92
- package/src/parser/Detector/__tests__/HierarchyBuilder_test.mjs +0 -489
- package/src/parser/Detector/__tests__/HierarchyBuilder_test.res +0 -849
- package/src/parser/Detector/__tests__/ShapeDetector_test.mjs +0 -377
- package/src/parser/Detector/__tests__/ShapeDetector_test.res +0 -563
- package/src/parser/Interactions/__tests__/InteractionMerger_test.mjs +0 -576
- package/src/parser/Interactions/__tests__/InteractionMerger_test.res +0 -646
- package/src/parser/Scanner/__tests__/Grid_manual.mjs +0 -214
- package/src/parser/Scanner/__tests__/Grid_manual.res +0 -141
- package/src/parser/Semantic/Elements/__tests__/ButtonParser_test.mjs +0 -189
- package/src/parser/Semantic/Elements/__tests__/ButtonParser_test.res +0 -257
- package/src/parser/Semantic/Elements/__tests__/CheckboxParser_test.mjs +0 -202
- package/src/parser/Semantic/Elements/__tests__/CheckboxParser_test.res +0 -250
- package/src/parser/Semantic/Elements/__tests__/CodeTextParser_manual.mjs +0 -293
- package/src/parser/Semantic/Elements/__tests__/CodeTextParser_manual.res +0 -134
- package/src/parser/Semantic/Elements/__tests__/InputParser_test.mjs +0 -253
- package/src/parser/Semantic/Elements/__tests__/InputParser_test.res +0 -304
- package/src/parser/Semantic/Elements/__tests__/LinkParser_test.mjs +0 -289
- package/src/parser/Semantic/Elements/__tests__/LinkParser_test.res +0 -402
- package/src/parser/Semantic/Elements/__tests__/TextParser_test.mjs +0 -149
- package/src/parser/Semantic/Elements/__tests__/TextParser_test.res +0 -167
- package/src/parser/Semantic/__tests__/ASTBuilder_test.mjs +0 -187
- package/src/parser/Semantic/__tests__/ASTBuilder_test.res +0 -192
- package/src/parser/Semantic/__tests__/ParserRegistry_test.mjs +0 -154
- package/src/parser/Semantic/__tests__/ParserRegistry_test.res +0 -191
- package/src/parser/Semantic/__tests__/SemanticParser_integration_test.mjs +0 -768
- package/src/parser/Semantic/__tests__/SemanticParser_integration_test.res +0 -1069
- package/src/parser/Semantic/__tests__/SemanticParser_manual.mjs +0 -1329
- package/src/parser/Semantic/__tests__/SemanticParser_manual.res +0 -544
- package/src/parser/__tests__/GridScanner_integration.test.mjs +0 -632
- package/src/parser/__tests__/GridScanner_integration.test.res +0 -816
- package/src/parser/__tests__/Performance.test.mjs +0 -244
- package/src/parser/__tests__/Performance.test.res +0 -371
- package/src/parser/__tests__/PerformanceFixtures.mjs +0 -200
- package/src/parser/__tests__/PerformanceFixtures.res +0 -284
- package/src/parser/__tests__/WyreframeParser_integration.test.mjs +0 -770
- package/src/parser/__tests__/WyreframeParser_integration.test.res +0 -1008
- package/src/parser/__tests__/fixtures/alignment-test.txt +0 -9
- package/src/parser/__tests__/fixtures/all-elements.txt +0 -16
- package/src/parser/__tests__/fixtures/login-scene.txt +0 -17
- package/src/parser/__tests__/fixtures/multi-scene.txt +0 -25
- package/src/parser/__tests__/fixtures/nested-boxes.txt +0 -15
- package/src/parser/__tests__/fixtures/simple-box.txt +0 -5
- package/src/parser/__tests__/fixtures/with-dividers.txt +0 -14
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
-
|
|
3
|
-
import * as Parser from "../Parser.mjs";
|
|
4
|
-
import * as Vitest from "rescript-vitest/src/Vitest.mjs";
|
|
5
|
-
import * as Core__Array from "@rescript/core/src/Core__Array.mjs";
|
|
6
|
-
import * as PerformanceFixtures from "./PerformanceFixtures.mjs";
|
|
7
|
-
|
|
8
|
-
function measureTime(fn) {
|
|
9
|
-
let start = Date.now();
|
|
10
|
-
let result = fn();
|
|
11
|
-
let end = Date.now();
|
|
12
|
-
let duration = end - start;
|
|
13
|
-
return [
|
|
14
|
-
duration,
|
|
15
|
-
result
|
|
16
|
-
];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function getMemoryUsageMB() {
|
|
20
|
-
return (typeof process !== 'undefined' && process.memoryUsage ? process.memoryUsage().heapUsed / 1024 / 1024 : 0);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function forceGC() {
|
|
24
|
-
((typeof global !== 'undefined' && global.gc ? global.gc() : undefined));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
Vitest.describe("Performance Benchmarks", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => {
|
|
28
|
-
Vitest.test("parses 100-line wireframe in ≤50ms", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => {
|
|
29
|
-
let wireframe = PerformanceFixtures.generateWireframe(100);
|
|
30
|
-
let match = measureTime(() => Parser.parse(wireframe));
|
|
31
|
-
let result = match[1];
|
|
32
|
-
let duration = match[0];
|
|
33
|
-
if (result.TAG === "Ok") {
|
|
34
|
-
t.expect(result._0.scenes.length).toBeGreaterThan(0);
|
|
35
|
-
} else {
|
|
36
|
-
console.error("Parse errors:");
|
|
37
|
-
console.error(result._0);
|
|
38
|
-
t.expect(true).toBe(false);
|
|
39
|
-
}
|
|
40
|
-
console.log(`100-line wireframe parsed in ` + duration.toString() + `ms`);
|
|
41
|
-
t.expect(duration).toBeLessThanOrEqual(50.0);
|
|
42
|
-
});
|
|
43
|
-
Vitest.test("parses 500-line wireframe in ≤200ms", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => {
|
|
44
|
-
let wireframe = PerformanceFixtures.generateWireframe(500);
|
|
45
|
-
let match = measureTime(() => Parser.parse(wireframe));
|
|
46
|
-
let result = match[1];
|
|
47
|
-
let duration = match[0];
|
|
48
|
-
if (result.TAG === "Ok") {
|
|
49
|
-
t.expect(result._0.scenes.length).toBeGreaterThan(0);
|
|
50
|
-
} else {
|
|
51
|
-
console.error("Parse errors:");
|
|
52
|
-
console.error(result._0);
|
|
53
|
-
t.expect(true).toBe(false);
|
|
54
|
-
}
|
|
55
|
-
console.log(`500-line wireframe parsed in ` + duration.toString() + `ms`);
|
|
56
|
-
t.expect(duration).toBeLessThanOrEqual(200.0);
|
|
57
|
-
});
|
|
58
|
-
Vitest.test("parses 2000-line wireframe in ≤1000ms", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => {
|
|
59
|
-
let wireframe = PerformanceFixtures.generateWireframe(2000);
|
|
60
|
-
let match = measureTime(() => Parser.parse(wireframe));
|
|
61
|
-
let result = match[1];
|
|
62
|
-
let duration = match[0];
|
|
63
|
-
if (result.TAG === "Ok") {
|
|
64
|
-
t.expect(result._0.scenes.length).toBeGreaterThan(0);
|
|
65
|
-
} else {
|
|
66
|
-
console.error("Parse errors:");
|
|
67
|
-
console.error(result._0);
|
|
68
|
-
t.expect(true).toBe(false);
|
|
69
|
-
}
|
|
70
|
-
console.log(`2000-line wireframe parsed in ` + duration.toString() + `ms`);
|
|
71
|
-
t.expect(duration).toBeLessThanOrEqual(1000.0);
|
|
72
|
-
});
|
|
73
|
-
Vitest.test("memory usage <50MB for 2000-line wireframe", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => {
|
|
74
|
-
let wireframe = PerformanceFixtures.generateWireframe(2000);
|
|
75
|
-
forceGC();
|
|
76
|
-
let initialMemory = getMemoryUsageMB();
|
|
77
|
-
let result = Parser.parse(wireframe);
|
|
78
|
-
let finalMemory = getMemoryUsageMB();
|
|
79
|
-
let memoryDelta = finalMemory - initialMemory;
|
|
80
|
-
if (result.TAG === "Ok") {
|
|
81
|
-
t.expect(result._0.scenes.length).toBeGreaterThan(0);
|
|
82
|
-
} else {
|
|
83
|
-
console.error("Parse errors:");
|
|
84
|
-
console.error(result._0);
|
|
85
|
-
t.expect(true).toBe(false);
|
|
86
|
-
}
|
|
87
|
-
console.log(`Initial memory: ` + initialMemory.toString() + `MB`);
|
|
88
|
-
console.log(`Final memory: ` + finalMemory.toString() + `MB`);
|
|
89
|
-
console.log(`Memory delta: ` + memoryDelta.toString() + `MB`);
|
|
90
|
-
if (memoryDelta > 0.0) {
|
|
91
|
-
return t.expect(memoryDelta).toBeLessThan(50.0);
|
|
92
|
-
} else {
|
|
93
|
-
console.warn("Memory test skipped: gc not exposed or memory delta is zero");
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
Vitest.describe("Fixture Generation Validation", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => {
|
|
98
|
-
let testSizes = [
|
|
99
|
-
50,
|
|
100
|
-
100,
|
|
101
|
-
200,
|
|
102
|
-
500,
|
|
103
|
-
1000,
|
|
104
|
-
2000
|
|
105
|
-
];
|
|
106
|
-
testSizes.forEach(targetSize => Vitest.test(`generates valid ` + targetSize.toString() + `-line wireframe`, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => {
|
|
107
|
-
let wireframe = PerformanceFixtures.generateWireframe(targetSize);
|
|
108
|
-
let actualLines = PerformanceFixtures.getLineCount(wireframe);
|
|
109
|
-
let minLines = targetSize * (1.0 - 0.30) | 0;
|
|
110
|
-
let maxLines = targetSize * (1.0 + 0.30) | 0;
|
|
111
|
-
console.log(`Target: ` + targetSize.toString() + ` lines, Actual: ` + actualLines.toString() + ` lines`);
|
|
112
|
-
t.expect(actualLines).toBeGreaterThanOrEqual(minLines);
|
|
113
|
-
t.expect(actualLines).toBeLessThanOrEqual(maxLines);
|
|
114
|
-
t.expect(PerformanceFixtures.validateWireframe(wireframe)).toBe(true);
|
|
115
|
-
let result = Parser.parse(wireframe);
|
|
116
|
-
if (result.TAG === "Ok") {
|
|
117
|
-
return t.expect(result._0.scenes.length).toBeGreaterThan(0);
|
|
118
|
-
}
|
|
119
|
-
console.error(`Parse errors for ` + targetSize.toString() + `-line wireframe:`);
|
|
120
|
-
console.error(result._0);
|
|
121
|
-
t.expect(true).toBe(false);
|
|
122
|
-
}));
|
|
123
|
-
});
|
|
124
|
-
Vitest.test("parsing is consistent across multiple iterations", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => {
|
|
125
|
-
let wireframe = PerformanceFixtures.generateWireframe(500);
|
|
126
|
-
let durations = [];
|
|
127
|
-
Parser.parse(wireframe);
|
|
128
|
-
for (let _for = 0; _for <= 9; ++_for) {
|
|
129
|
-
let match = measureTime(() => Parser.parse(wireframe));
|
|
130
|
-
let result = match[1];
|
|
131
|
-
durations.push(match[0]);
|
|
132
|
-
if (result.TAG === "Ok") {
|
|
133
|
-
t.expect(result._0.scenes.length).toBeGreaterThan(0);
|
|
134
|
-
} else {
|
|
135
|
-
t.expect(true).toBe(false);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
let sum = Core__Array.reduce(durations, 0.0, (acc, d) => acc + d);
|
|
139
|
-
let average = sum / 10;
|
|
140
|
-
let variance = Core__Array.reduce(durations, 0.0, (acc, d) => {
|
|
141
|
-
let diff = d - average;
|
|
142
|
-
return acc + diff * diff;
|
|
143
|
-
}) / 10;
|
|
144
|
-
let stdDev = Math.sqrt(variance);
|
|
145
|
-
let min = Core__Array.reduce(durations, Number.POSITIVE_INFINITY, (acc, d) => Math.min(acc, d));
|
|
146
|
-
let max = Core__Array.reduce(durations, Number.NEGATIVE_INFINITY, (acc, d) => Math.max(acc, d));
|
|
147
|
-
console.log(`\nPerformance Statistics (` + (10).toString() + ` iterations):`);
|
|
148
|
-
console.log(` Average: ` + average.toString() + `ms`);
|
|
149
|
-
console.log(` Std Dev: ` + stdDev.toString() + `ms`);
|
|
150
|
-
console.log(` Min: ` + min.toString() + `ms`);
|
|
151
|
-
console.log(` Max: ` + max.toString() + `ms`);
|
|
152
|
-
console.log(` Coefficient of Variation: ` + (stdDev / average * 100.0).toString() + `%`);
|
|
153
|
-
t.expect(max).toBeLessThanOrEqual(200.0);
|
|
154
|
-
if (average >= 10.0) {
|
|
155
|
-
let coefficientOfVariation = stdDev / average;
|
|
156
|
-
return t.expect(coefficientOfVariation).toBeLessThan(0.5);
|
|
157
|
-
}
|
|
158
|
-
console.log(" CV check skipped: average too small for meaningful variance measurement");
|
|
159
|
-
});
|
|
160
|
-
Vitest.test("performance scales linearly with wireframe size", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => {
|
|
161
|
-
let sizes = [
|
|
162
|
-
100,
|
|
163
|
-
200,
|
|
164
|
-
400,
|
|
165
|
-
800
|
|
166
|
-
];
|
|
167
|
-
let measurements = [];
|
|
168
|
-
sizes.forEach(size => {
|
|
169
|
-
let wireframe = PerformanceFixtures.generateWireframe(size);
|
|
170
|
-
let match = measureTime(() => Parser.parse(wireframe));
|
|
171
|
-
if (match[1].TAG !== "Ok") {
|
|
172
|
-
return t.expect(true).toBe(false);
|
|
173
|
-
}
|
|
174
|
-
measurements.push([
|
|
175
|
-
size,
|
|
176
|
-
match[0]
|
|
177
|
-
]);
|
|
178
|
-
});
|
|
179
|
-
console.log("\nLinear Scaling Analysis:");
|
|
180
|
-
measurements.forEach(param => {
|
|
181
|
-
console.log(` ` + param[0].toString() + ` lines: ` + param[1].toString() + `ms`);
|
|
182
|
-
});
|
|
183
|
-
for (let i = 0, i_finish = measurements.length - 2 | 0; i <= i_finish; ++i) {
|
|
184
|
-
let match = measurements[i];
|
|
185
|
-
let duration1 = match[1];
|
|
186
|
-
let match$1 = measurements[i + 1 | 0];
|
|
187
|
-
let duration2 = match$1[1];
|
|
188
|
-
let sizeRatio = match$1[0] / match[0];
|
|
189
|
-
if (duration1 >= 10.0 && duration2 >= 10.0) {
|
|
190
|
-
let durationRatio = duration2 / duration1;
|
|
191
|
-
console.log(` Size ratio: ` + sizeRatio.toString() + `x, Duration ratio: ` + durationRatio.toString() + `x`);
|
|
192
|
-
t.expect(durationRatio).toBeLessThan(sizeRatio * 2.5);
|
|
193
|
-
} else {
|
|
194
|
-
console.log(` Size ratio: ` + sizeRatio.toString() + `x, Duration ratio: N/A (durations too small)`);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
Vitest.describe("Nested Boxes Performance", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => Vitest.test("handles deep nesting efficiently", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => {
|
|
201
|
-
let depths = [
|
|
202
|
-
1,
|
|
203
|
-
2,
|
|
204
|
-
3,
|
|
205
|
-
4
|
|
206
|
-
];
|
|
207
|
-
depths.forEach(depth => {
|
|
208
|
-
let wireframe = PerformanceFixtures.generateNestedBoxes(depth);
|
|
209
|
-
let match = measureTime(() => Parser.parse(wireframe));
|
|
210
|
-
let result = match[1];
|
|
211
|
-
let duration = match[0];
|
|
212
|
-
console.log(`Nesting depth ` + depth.toString() + `: ` + duration.toString() + `ms`);
|
|
213
|
-
if (result.TAG !== "Ok") {
|
|
214
|
-
console.error(`Parse errors at depth ` + depth.toString() + `:`);
|
|
215
|
-
console.error(result._0);
|
|
216
|
-
t.expect(true).toBe(false);
|
|
217
|
-
}
|
|
218
|
-
t.expect(duration).toBeLessThan(100.0);
|
|
219
|
-
});
|
|
220
|
-
}));
|
|
221
|
-
|
|
222
|
-
Vitest.describe("Simple Box Generation", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => Vitest.test("generates and parses simple boxes", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => {
|
|
223
|
-
let box1 = PerformanceFixtures.generateSimpleBox(undefined, undefined, undefined);
|
|
224
|
-
let box2 = PerformanceFixtures.generateSimpleBox(30, 5, "TestBox");
|
|
225
|
-
t.expect(PerformanceFixtures.validateWireframe(box1)).toBe(true);
|
|
226
|
-
t.expect(PerformanceFixtures.validateWireframe(box2)).toBe(true);
|
|
227
|
-
let result1 = Parser.parse(box1);
|
|
228
|
-
let result2 = Parser.parse(box2);
|
|
229
|
-
if (result1.TAG === "Ok" && result2.TAG === "Ok") {
|
|
230
|
-
return;
|
|
231
|
-
} else {
|
|
232
|
-
return t.expect(true).toBe(false);
|
|
233
|
-
}
|
|
234
|
-
}));
|
|
235
|
-
|
|
236
|
-
let pass;
|
|
237
|
-
|
|
238
|
-
export {
|
|
239
|
-
pass,
|
|
240
|
-
measureTime,
|
|
241
|
-
getMemoryUsageMB,
|
|
242
|
-
forceGC,
|
|
243
|
-
}
|
|
244
|
-
/* Not a pure module */
|
|
@@ -1,371 +0,0 @@
|
|
|
1
|
-
// Performance.test.res
|
|
2
|
-
// Performance benchmarks for WyreframeParser
|
|
3
|
-
// Requirements: REQ-22 (Parsing Speed), REQ-23 (Memory Efficiency)
|
|
4
|
-
|
|
5
|
-
open Vitest
|
|
6
|
-
|
|
7
|
-
let pass = ()
|
|
8
|
-
|
|
9
|
-
// Helper to measure execution time
|
|
10
|
-
let measureTime = (fn: unit => 'a): (float, 'a) => {
|
|
11
|
-
let start = Date.now()
|
|
12
|
-
let result = fn()
|
|
13
|
-
let end = Date.now()
|
|
14
|
-
let duration = end -. start
|
|
15
|
-
(duration, result)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// Helper to get memory usage in MB
|
|
19
|
-
let getMemoryUsageMB = (): float => {
|
|
20
|
-
// Node.js specific - process.memoryUsage().heapUsed
|
|
21
|
-
%raw(`typeof process !== 'undefined' && process.memoryUsage ? process.memoryUsage().heapUsed / 1024 / 1024 : 0`)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Helper to force garbage collection if available
|
|
25
|
-
let forceGC = (): unit => {
|
|
26
|
-
%raw(`typeof global !== 'undefined' && global.gc ? global.gc() : undefined`)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
describe("Performance Benchmarks", t => {
|
|
30
|
-
// PERF-01: Small wireframe parsing (100 lines) - ≤50ms
|
|
31
|
-
test("parses 100-line wireframe in ≤50ms", t => {
|
|
32
|
-
let wireframe = PerformanceFixtures.generateWireframe(100)
|
|
33
|
-
|
|
34
|
-
let (duration, result) = measureTime(() => {
|
|
35
|
-
Parser.parse(wireframe)
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
// Verify parse succeeded
|
|
39
|
-
switch result {
|
|
40
|
-
| Ok(ast) => {
|
|
41
|
-
t->expect(Array.length(ast.scenes))->Expect.Int.toBeGreaterThan(0)
|
|
42
|
-
}
|
|
43
|
-
| Error(errors) => {
|
|
44
|
-
// Log errors for debugging
|
|
45
|
-
Console.error("Parse errors:")
|
|
46
|
-
Console.error(errors)
|
|
47
|
-
t->expect(true)->Expect.toBe(false) // fail: Expected successful parse
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Verify performance requirement
|
|
52
|
-
Console.log(`100-line wireframe parsed in ${Float.toString(duration)}ms`)
|
|
53
|
-
t->expect(duration)->Expect.Float.toBeLessThanOrEqual(50.0)
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
// PERF-02: Medium wireframe parsing (500 lines) - ≤200ms
|
|
57
|
-
test("parses 500-line wireframe in ≤200ms", t => {
|
|
58
|
-
let wireframe = PerformanceFixtures.generateWireframe(500)
|
|
59
|
-
|
|
60
|
-
let (duration, result) = measureTime(() => {
|
|
61
|
-
Parser.parse(wireframe)
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
// Verify parse succeeded
|
|
65
|
-
switch result {
|
|
66
|
-
| Ok(ast) => {
|
|
67
|
-
t->expect(Array.length(ast.scenes))->Expect.Int.toBeGreaterThan(0)
|
|
68
|
-
}
|
|
69
|
-
| Error(errors) => {
|
|
70
|
-
Console.error("Parse errors:")
|
|
71
|
-
Console.error(errors)
|
|
72
|
-
t->expect(true)->Expect.toBe(false) // fail: Expected successful parse
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Verify performance requirement
|
|
77
|
-
Console.log(`500-line wireframe parsed in ${Float.toString(duration)}ms`)
|
|
78
|
-
t->expect(duration)->Expect.Float.toBeLessThanOrEqual(200.0)
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
// PERF-03: Large wireframe parsing (2000 lines) - ≤1000ms
|
|
82
|
-
test("parses 2000-line wireframe in ≤1000ms", t => {
|
|
83
|
-
let wireframe = PerformanceFixtures.generateWireframe(2000)
|
|
84
|
-
|
|
85
|
-
let (duration, result) = measureTime(() => {
|
|
86
|
-
Parser.parse(wireframe)
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
// Verify parse succeeded
|
|
90
|
-
switch result {
|
|
91
|
-
| Ok(ast) => {
|
|
92
|
-
t->expect(Array.length(ast.scenes))->Expect.Int.toBeGreaterThan(0)
|
|
93
|
-
}
|
|
94
|
-
| Error(errors) => {
|
|
95
|
-
Console.error("Parse errors:")
|
|
96
|
-
Console.error(errors)
|
|
97
|
-
t->expect(true)->Expect.toBe(false) // fail: Expected successful parse
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Verify performance requirement
|
|
102
|
-
Console.log(`2000-line wireframe parsed in ${Float.toString(duration)}ms`)
|
|
103
|
-
t->expect(duration)->Expect.Float.toBeLessThanOrEqual(1000.0)
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
// PERF-04: Memory usage test - <50MB for 2000 lines
|
|
107
|
-
test("memory usage <50MB for 2000-line wireframe", t => {
|
|
108
|
-
let wireframe = PerformanceFixtures.generateWireframe(2000)
|
|
109
|
-
|
|
110
|
-
// Force garbage collection to get clean baseline
|
|
111
|
-
forceGC()
|
|
112
|
-
|
|
113
|
-
// Record initial memory
|
|
114
|
-
let initialMemory = getMemoryUsageMB()
|
|
115
|
-
|
|
116
|
-
// Parse wireframe
|
|
117
|
-
let result = Parser.parse(wireframe)
|
|
118
|
-
|
|
119
|
-
// Record final memory
|
|
120
|
-
let finalMemory = getMemoryUsageMB()
|
|
121
|
-
|
|
122
|
-
// Calculate delta
|
|
123
|
-
let memoryDelta = finalMemory -. initialMemory
|
|
124
|
-
|
|
125
|
-
// Verify parse succeeded
|
|
126
|
-
switch result {
|
|
127
|
-
| Ok(ast) => {
|
|
128
|
-
t->expect(Array.length(ast.scenes))->Expect.Int.toBeGreaterThan(0)
|
|
129
|
-
}
|
|
130
|
-
| Error(errors) => {
|
|
131
|
-
Console.error("Parse errors:")
|
|
132
|
-
Console.error(errors)
|
|
133
|
-
t->expect(true)->Expect.toBe(false) // fail: Expected successful parse
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Log memory usage
|
|
138
|
-
Console.log(`Initial memory: ${Float.toString(initialMemory)}MB`)
|
|
139
|
-
Console.log(`Final memory: ${Float.toString(finalMemory)}MB`)
|
|
140
|
-
Console.log(`Memory delta: ${Float.toString(memoryDelta)}MB`)
|
|
141
|
-
|
|
142
|
-
// Verify memory requirement
|
|
143
|
-
// Note: This test may be skipped if gc is not exposed
|
|
144
|
-
if memoryDelta > 0.0 {
|
|
145
|
-
t->expect(memoryDelta)->Expect.Float.toBeLessThan(50.0)
|
|
146
|
-
} else {
|
|
147
|
-
Console.warn("Memory test skipped: gc not exposed or memory delta is zero")
|
|
148
|
-
pass
|
|
149
|
-
}
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
// PERF-05: Fixture generation validation
|
|
153
|
-
describe("Fixture Generation Validation", t => {
|
|
154
|
-
let testSizes = [50, 100, 200, 500, 1000, 2000]
|
|
155
|
-
|
|
156
|
-
testSizes->Array.forEach(targetSize => {
|
|
157
|
-
test(`generates valid ${Int.toString(targetSize)}-line wireframe`, t => {
|
|
158
|
-
let wireframe = PerformanceFixtures.generateWireframe(targetSize)
|
|
159
|
-
|
|
160
|
-
// Check line count is approximately correct (±30% tolerance for all sizes)
|
|
161
|
-
let actualLines = PerformanceFixtures.getLineCount(wireframe)
|
|
162
|
-
let tolerance = 0.30 // 30% tolerance - fixture generation is approximate
|
|
163
|
-
let minLines = Float.toInt(Float.fromInt(targetSize) *. (1.0 -. tolerance))
|
|
164
|
-
let maxLines = Float.toInt(Float.fromInt(targetSize) *. (1.0 +. tolerance))
|
|
165
|
-
|
|
166
|
-
Console.log(
|
|
167
|
-
`Target: ${Int.toString(targetSize)} lines, Actual: ${Int.toString(actualLines)} lines`,
|
|
168
|
-
)
|
|
169
|
-
|
|
170
|
-
t->expect(actualLines)->Expect.Int.toBeGreaterThanOrEqual(minLines)
|
|
171
|
-
t->expect(actualLines)->Expect.Int.toBeLessThanOrEqual(maxLines)
|
|
172
|
-
|
|
173
|
-
// Verify basic syntactic correctness
|
|
174
|
-
t->expect(PerformanceFixtures.validateWireframe(wireframe))->Expect.toBe(true)
|
|
175
|
-
|
|
176
|
-
// Verify it parses without errors
|
|
177
|
-
let result = Parser.parse(wireframe)
|
|
178
|
-
switch result {
|
|
179
|
-
| Ok(ast) => {
|
|
180
|
-
t->expect(Array.length(ast.scenes))->Expect.Int.toBeGreaterThan(0)
|
|
181
|
-
}
|
|
182
|
-
| Error(errors) => {
|
|
183
|
-
Console.error(`Parse errors for ${Int.toString(targetSize)}-line wireframe:`)
|
|
184
|
-
Console.error(errors)
|
|
185
|
-
t->expect(true)->Expect.toBe(false) // fail: Expected successful parse
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
})
|
|
189
|
-
})
|
|
190
|
-
})
|
|
191
|
-
|
|
192
|
-
// PERF-06: Multiple parse iterations consistency
|
|
193
|
-
test("parsing is consistent across multiple iterations", t => {
|
|
194
|
-
let wireframe = PerformanceFixtures.generateWireframe(500)
|
|
195
|
-
let iterations = 10
|
|
196
|
-
let durations = []
|
|
197
|
-
|
|
198
|
-
// Warm-up iteration (JIT compilation)
|
|
199
|
-
let _ = Parser.parse(wireframe)
|
|
200
|
-
|
|
201
|
-
// Measured iterations
|
|
202
|
-
for _ in 0 to iterations - 1 {
|
|
203
|
-
let (duration, result) = measureTime(() => {
|
|
204
|
-
Parser.parse(wireframe)
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
durations->Array.push(duration)->ignore
|
|
208
|
-
|
|
209
|
-
// Verify each parse succeeds
|
|
210
|
-
switch result {
|
|
211
|
-
| Ok(ast) => {
|
|
212
|
-
t->expect(Array.length(ast.scenes))->Expect.Int.toBeGreaterThan(0)
|
|
213
|
-
}
|
|
214
|
-
| Error(_) => {
|
|
215
|
-
t->expect(true)->Expect.toBe(false) // fail: Expected successful parse on all iterations
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Calculate statistics
|
|
221
|
-
let sum = durations->Array.reduce(0.0, (acc, d) => acc +. d)
|
|
222
|
-
let average = sum /. Float.fromInt(iterations)
|
|
223
|
-
|
|
224
|
-
// Calculate standard deviation
|
|
225
|
-
let variance =
|
|
226
|
-
durations->Array.reduce(0.0, (acc, d) => {
|
|
227
|
-
let diff = d -. average
|
|
228
|
-
acc +. diff *. diff
|
|
229
|
-
}) /. Float.fromInt(iterations)
|
|
230
|
-
let stdDev = Math.sqrt(variance)
|
|
231
|
-
|
|
232
|
-
let min = durations->Array.reduce(Float.Constants.positiveInfinity, (acc, d) => Math.min(acc, d))
|
|
233
|
-
let max = durations->Array.reduce(Float.Constants.negativeInfinity, (acc, d) => Math.max(acc, d))
|
|
234
|
-
|
|
235
|
-
// Log statistics
|
|
236
|
-
Console.log(`\nPerformance Statistics (${Int.toString(iterations)} iterations):`)
|
|
237
|
-
Console.log(` Average: ${Float.toString(average)}ms`)
|
|
238
|
-
Console.log(` Std Dev: ${Float.toString(stdDev)}ms`)
|
|
239
|
-
Console.log(` Min: ${Float.toString(min)}ms`)
|
|
240
|
-
Console.log(` Max: ${Float.toString(max)}ms`)
|
|
241
|
-
Console.log(` Coefficient of Variation: ${Float.toString(stdDev /. average *. 100.0)}%`)
|
|
242
|
-
|
|
243
|
-
// Verify all iterations meet performance target
|
|
244
|
-
t->expect(max)->Expect.Float.toBeLessThanOrEqual(200.0)
|
|
245
|
-
|
|
246
|
-
// Verify consistency (standard deviation < 50% of average)
|
|
247
|
-
// Note: Skip CV check for very fast operations (< 10ms average)
|
|
248
|
-
// because sub-millisecond timing has inherently high variance
|
|
249
|
-
if average >= 10.0 {
|
|
250
|
-
let coefficientOfVariation = stdDev /. average
|
|
251
|
-
t->expect(coefficientOfVariation)->Expect.Float.toBeLessThan(0.5)
|
|
252
|
-
} else {
|
|
253
|
-
// For fast operations, just verify max is reasonable
|
|
254
|
-
Console.log(" CV check skipped: average too small for meaningful variance measurement")
|
|
255
|
-
pass
|
|
256
|
-
}
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
// Additional test: Verify linear scaling
|
|
260
|
-
test("performance scales linearly with wireframe size", t => {
|
|
261
|
-
let sizes = [100, 200, 400, 800]
|
|
262
|
-
let measurements = []
|
|
263
|
-
|
|
264
|
-
sizes->Array.forEach(size => {
|
|
265
|
-
let wireframe = PerformanceFixtures.generateWireframe(size)
|
|
266
|
-
|
|
267
|
-
let (duration, result) = measureTime(() => {
|
|
268
|
-
Parser.parse(wireframe)
|
|
269
|
-
})
|
|
270
|
-
|
|
271
|
-
// Verify parse succeeded
|
|
272
|
-
switch result {
|
|
273
|
-
| Ok(_) => {
|
|
274
|
-
measurements->Array.push((size, duration))->ignore
|
|
275
|
-
}
|
|
276
|
-
| Error(_) => {
|
|
277
|
-
t->expect(true)->Expect.toBe(false) // fail: Expected successful parse
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
})
|
|
281
|
-
|
|
282
|
-
// Log measurements
|
|
283
|
-
Console.log("\nLinear Scaling Analysis:")
|
|
284
|
-
measurements->Array.forEach(((size, duration)) => {
|
|
285
|
-
Console.log(` ${Int.toString(size)} lines: ${Float.toString(duration)}ms`)
|
|
286
|
-
})
|
|
287
|
-
|
|
288
|
-
// Verify approximate linear relationship
|
|
289
|
-
// For each doubling of size, duration should roughly double (±50% tolerance)
|
|
290
|
-
for i in 0 to Array.length(measurements) - 2 {
|
|
291
|
-
let (size1, duration1) = measurements->Array.getUnsafe(i)
|
|
292
|
-
let (size2, duration2) = measurements->Array.getUnsafe(i + 1)
|
|
293
|
-
|
|
294
|
-
let sizeRatio = Float.fromInt(size2) /. Float.fromInt(size1)
|
|
295
|
-
|
|
296
|
-
// Skip ratio comparison if durations are too small (< 10ms)
|
|
297
|
-
// Small durations have too much variance for meaningful ratio comparison
|
|
298
|
-
if duration1 >= 10.0 && duration2 >= 10.0 {
|
|
299
|
-
let durationRatio = duration2 /. duration1
|
|
300
|
-
|
|
301
|
-
Console.log(
|
|
302
|
-
` Size ratio: ${Float.toString(sizeRatio)}x, Duration ratio: ${Float.toString(
|
|
303
|
-
durationRatio,
|
|
304
|
-
)}x`,
|
|
305
|
-
)
|
|
306
|
-
|
|
307
|
-
// Duration ratio should be within 0.5x to 2.5x of size ratio
|
|
308
|
-
// (allowing for variance due to fixed overhead and JIT optimization)
|
|
309
|
-
t->expect(durationRatio)->Expect.Float.toBeLessThan(sizeRatio *. 2.5)
|
|
310
|
-
} else {
|
|
311
|
-
Console.log(
|
|
312
|
-
` Size ratio: ${Float.toString(sizeRatio)}x, Duration ratio: N/A (durations too small)`,
|
|
313
|
-
)
|
|
314
|
-
// If durations are small, parsing is extremely fast which is good
|
|
315
|
-
pass
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
})
|
|
319
|
-
})
|
|
320
|
-
|
|
321
|
-
// Nested boxes performance test
|
|
322
|
-
describe("Nested Boxes Performance", t => {
|
|
323
|
-
test("handles deep nesting efficiently", t => {
|
|
324
|
-
// Test nesting depths 1-4
|
|
325
|
-
let depths = [1, 2, 3, 4]
|
|
326
|
-
|
|
327
|
-
depths->Array.forEach(depth => {
|
|
328
|
-
let wireframe = PerformanceFixtures.generateNestedBoxes(depth)
|
|
329
|
-
|
|
330
|
-
let (duration, result) = measureTime(() => {
|
|
331
|
-
Parser.parse(wireframe)
|
|
332
|
-
})
|
|
333
|
-
|
|
334
|
-
Console.log(`Nesting depth ${Int.toString(depth)}: ${Float.toString(duration)}ms`)
|
|
335
|
-
|
|
336
|
-
// Verify parse succeeded
|
|
337
|
-
switch result {
|
|
338
|
-
| Ok(_) => pass
|
|
339
|
-
| Error(errors) => {
|
|
340
|
-
Console.error(`Parse errors at depth ${Int.toString(depth)}:`)
|
|
341
|
-
Console.error(errors)
|
|
342
|
-
t->expect(true)->Expect.toBe(false) // fail: Expected successful parse
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// Even deep nesting should be fast (<100ms for simple structures)
|
|
347
|
-
t->expect(duration)->Expect.Float.toBeLessThan(100.0)
|
|
348
|
-
})
|
|
349
|
-
})
|
|
350
|
-
})
|
|
351
|
-
|
|
352
|
-
// Simple box generation test
|
|
353
|
-
describe("Simple Box Generation", t => {
|
|
354
|
-
test("generates and parses simple boxes", t => {
|
|
355
|
-
let box1 = PerformanceFixtures.generateSimpleBox()
|
|
356
|
-
let box2 = PerformanceFixtures.generateSimpleBox(~width=30, ~height=5, ~name=Some("TestBox"))
|
|
357
|
-
|
|
358
|
-
// Verify both boxes are valid
|
|
359
|
-
t->expect(PerformanceFixtures.validateWireframe(box1))->Expect.toBe(true)
|
|
360
|
-
t->expect(PerformanceFixtures.validateWireframe(box2))->Expect.toBe(true)
|
|
361
|
-
|
|
362
|
-
// Parse both
|
|
363
|
-
let result1 = Parser.parse(box1)
|
|
364
|
-
let result2 = Parser.parse(box2)
|
|
365
|
-
|
|
366
|
-
switch (result1, result2) {
|
|
367
|
-
| (Ok(_), Ok(_)) => pass
|
|
368
|
-
| _ => t->expect(true)->Expect.toBe(false) // fail: Expected successful parse for simple boxes
|
|
369
|
-
}
|
|
370
|
-
})
|
|
371
|
-
})
|