Skip to content

Commit 64ef45f

Browse files
authored
Merge pull request #206 from JairusSW/jairus/coverage-improvements
Jairus/coverage improvements
2 parents 8e243e3 + 9a9872a commit 64ef45f

30 files changed

Lines changed: 1439 additions & 188 deletions

assembly/__tests__/array.spec.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,3 +445,82 @@ describe("Should serialize string[] of non-null elements through nullable type",
445445
'["a","b","c"]',
446446
);
447447
});
448+
449+
// ─── helpers ──────────────────────────────────────────────────────────────────
450+
451+
@json
452+
class BoolArr {
453+
flags: bool[] = [];
454+
}
455+
456+
457+
@json
458+
class Matrix {
459+
rows: i32[][] = [];
460+
}
461+
462+
// ─── Naive array success paths ───────────────────────────────────────────────
463+
464+
describe("NAIVE: bool[] round-trips", () => {
465+
expect(JSON.stringify(JSON.parse<bool[]>("[true,false,true]"))).toBe(
466+
"[true,false,true]",
467+
);
468+
});
469+
470+
describe("NAIVE: f64[] round-trips with negative and fractional", () => {
471+
expect(JSON.stringify(JSON.parse<f64[]>("[-1.5,0,2.5]"))).toBe(
472+
"[-1.5,0,2.5]",
473+
);
474+
});
475+
476+
describe("Serialize: f32 array elements round-trip", () => {
477+
expect(JSON.stringify<f32[]>([-1.5, 0.25, 3.75])).toBe("[-1.5,0.25,3.75]");
478+
});
479+
480+
describe("Serialize: empty i8[] array", () => {
481+
expect(JSON.stringify<i8[]>([])).toBe("[]");
482+
});
483+
484+
describe("Serialize: empty u8[] array", () => {
485+
expect(JSON.stringify<u8[]>([])).toBe("[]");
486+
});
487+
488+
// bool[] as @json field → deserializeBooleanArrayBody (SWAR)
489+
describe("SWAR: bool[] field with inner whitespace covers whitespace loops", () => {
490+
const r = JSON.parse<BoolArr>('{"flags":[ true , false , true ]}');
491+
expect(r.flags.length).toBe(3);
492+
expect(r.flags[0]).toBe(true);
493+
expect(r.flags[1]).toBe(false);
494+
});
495+
496+
describe("SWAR: bool[] field reparse with fewer elements (resize)", () => {
497+
const r = JSON.parse<BoolArr>('{"flags":[true,false,true]}');
498+
expect(r.flags.length).toBe(3);
499+
const r2 = JSON.parse<BoolArr>('{"flags":[true]}', r);
500+
expect(r2.flags.length).toBe(1);
501+
expect(r2.flags[0]).toBe(true);
502+
});
503+
504+
// Array of arrays → naive/array/array.ts
505+
describe("Naive: i32[][] round-trips in all modes", () => {
506+
expect(JSON.stringify(JSON.parse<i32[][]>("[[1,2],[3,4,5]]"))).toBe(
507+
"[[1,2],[3,4,5]]",
508+
);
509+
});
510+
511+
// naive/array/array.ts path 1: JSON.Value[][]
512+
describe("Naive: JSON.Value[][] covers path-1 arbitraryInner Reference branch", () => {
513+
const arr = JSON.parse<JSON.Value[][]>('[[1,2],[3,"a"]]');
514+
expect(arr.length).toBe(2);
515+
expect(arr[0].length).toBe(2);
516+
expect(arr[1].length).toBe(2);
517+
});
518+
519+
// swar/array/array.ts: shrink path when reparsing fewer inner arrays
520+
describe("SWAR: Matrix.rows reparse with fewer inner arrays covers array-array body shrink path", () => {
521+
const m1 = JSON.parse<Matrix>('{"rows":[[1,2],[3,4],[5,6]]}');
522+
expect(m1.rows.length).toBe(3);
523+
const m2 = JSON.parse<Matrix>('{"rows":[[7,8]]}', m1);
524+
expect(m2.rows.length).toBe(1);
525+
expect(m2.rows[0][0]).toBe(7);
526+
});

assembly/__tests__/box.spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,19 @@ describe("Extended regression coverage - nested and escaped payloads", () => {
8686
'"line\\nbreak"',
8787
);
8888
});
89+
90+
// ─── JSON.Box ─────────────────────────────────────────────────────────────────
91+
92+
describe("JSON.Box: fromValue() returns null for JSON null", () => {
93+
const b = JSON.Box.fromValue<i32>(JSON.parse<JSON.Value>("null"));
94+
expect(changetype<usize>(b) == 0 ? "null" : "set").toBe("null");
95+
});
96+
97+
describe("JSON.Box: fromValue() returns boxed value for non-null", () => {
98+
const b = JSON.Box.fromValue<f64>(JSON.parse<JSON.Value>("42"));
99+
expect(b!.value).toBe(42.0);
100+
});
101+
102+
describe("JSON.Box: toString() on null box serializes as null", () => {
103+
expect(JSON.stringify<JSON.Box<i32> | null>(null)).toBe("null");
104+
});

assembly/__tests__/float.spec.ts

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,3 +196,174 @@ describe("Extended regression coverage - nested and escaped payloads", () => {
196196
'"line\\nbreak"',
197197
);
198198
});
199+
200+
// ─── helpers ──────────────────────────────────────────────────────────────────
201+
202+
@json
203+
class Vec2CovGapFloat {
204+
x: f64 = 0;
205+
y: f64 = 0;
206+
}
207+
208+
209+
@json
210+
class F32Holder {
211+
v: f32 = 0.0;
212+
}
213+
214+
215+
@json
216+
class FloatArr {
217+
values: f64[] = [];
218+
}
219+
220+
// ─── Serialize: @json class with f32 field ────────────────────────────────────
221+
222+
describe("Serialize: @json class with f32 field", () => {
223+
const h = new F32Holder();
224+
h.v = 1.5;
225+
expect(JSON.stringify(h)).toBe('{"v":1.5}');
226+
expect(JSON.parse<F32Holder>('{"v":3.14}').v.toString()).toBe(
227+
(<f32>3.14).toString(),
228+
);
229+
});
230+
231+
// ─── swar/float.ts: deserializeFloatField_SWAR exponent paths ─────────────────
232+
233+
describe("SWAR: f64 struct field with positive exponent covers exponent block", () => {
234+
const v = JSON.parse<Vec2CovGapFloat>('{"x":1.5e2,"y":3.0e0}');
235+
expect(v.x).toBe(150.0);
236+
expect(v.y).toBe(3.0);
237+
});
238+
239+
describe("SWAR: f64 struct field with plus-sign exponent covers ASCII_PLUS branch", () => {
240+
const v = JSON.parse<Vec2CovGapFloat>('{"x":2.0e+1,"y":5.0e+0}');
241+
expect(v.x).toBe(20.0);
242+
expect(v.y).toBe(5.0);
243+
});
244+
245+
describe("SWAR: f64 struct field with negative exponent covers ASCII_MINUS branch", () => {
246+
const v = JSON.parse<Vec2CovGapFloat>('{"x":1.5e-2,"y":2.0e-1}');
247+
expect(v.x).toBeCloseTo(0.015);
248+
expect(v.y).toBeCloseTo(0.2);
249+
});
250+
251+
describe("SWAR: f64 struct field with large exponent covers scientific fallback path", () => {
252+
const v = JSON.parse<Vec2CovGapFloat>('{"x":1e100,"y":2e-100}');
253+
expect(isFinite(v.x)).toBe(true);
254+
expect(v.x).toBeCloseTo(1e100);
255+
});
256+
257+
describe("SWAR: f64 struct field with >19 mantissa digits covers fallbackField", () => {
258+
const v = JSON.parse<Vec2CovGapFloat>('{"x":1.23456789012345678901,"y":0.0}');
259+
expect(v.x).toBeCloseTo(1.2345678901234568);
260+
expect(v.y).toBe(0.0);
261+
});
262+
263+
// ─── swar/float.ts: deserializeFloat_SWAR exponent loop non-digit break ──────
264+
265+
describe("SWAR: standalone f64 with trailing space after exponent covers d>9 break", () => {
266+
expect(JSON.parse<f64>("1e5 ").toString()).toBe((1e5).toString());
267+
});
268+
269+
describe("SWAR: standalone f64 with 5-digit exponent covers expDigits>4 standalone path", () => {
270+
expect(!isFinite(JSON.parse<f64>("1e55555"))).toBe(true);
271+
});
272+
273+
// ─── swar/float.ts: fallbackField f32 path ────────────────────────────────────
274+
275+
describe("SWAR: f32 struct field with >19 mantissa digits covers fallbackField f32 branch", () => {
276+
const h = JSON.parse<F32Holder>('{"v":1.23456789012345678901}');
277+
expect(h.v).toBeCloseTo(<f32>1.2345678901234568);
278+
});
279+
280+
// ─── swar/float.ts: deserializeFloatField_SWAR 5-digit exponent path ──────────
281+
282+
describe("SWAR: f64 struct field with 5-digit exponent covers expDigits>4 in struct", () => {
283+
const v = JSON.parse<Vec2CovGapFloat>('{"x":1e55555,"y":0.0}');
284+
expect(!isFinite(v.x)).toBe(true);
285+
expect(v.y).toBe(0.0);
286+
});
287+
288+
// ─── f64[] as @json field → deserializeFloatArrayBody ────────────────────────
289+
290+
describe("SWAR: f64[] as @json class field round-trips", () => {
291+
const f = JSON.parse<FloatArr>('{"values":[1.5,-2.5,0]}');
292+
expect(f.values.length).toBe(3);
293+
expect(f.values[0]).toBe(1.5);
294+
});
295+
296+
describe("SWAR: f64[] empty array as @json class field", () => {
297+
const f = JSON.parse<FloatArr>('{"values":[]}');
298+
expect(f.values.length).toBe(0);
299+
});
300+
301+
describe("SWAR: f64[] with large exponent triggers scientific() path", () => {
302+
const f = JSON.parse<FloatArr>('{"values":[1e25,5e-25]}');
303+
expect(f.values.length).toBe(2);
304+
expect(f.values[0]).toBe(1e25);
305+
});
306+
307+
describe("SWAR: f64[] reparse with fewer elements (resize)", () => {
308+
const f = JSON.parse<FloatArr>('{"values":[1.1,2.2,3.3]}');
309+
expect(f.values.length).toBe(3);
310+
const f2 = JSON.parse<FloatArr>('{"values":[9.9]}', f);
311+
expect(f2.values.length).toBe(1);
312+
expect(f2.values[0]).toBe(9.9);
313+
});
314+
315+
// swar/array/float.ts: fallbackStore via >19-digit mantissa
316+
describe("SWAR: f64[] with >19 mantissa digits triggers fallbackStore", () => {
317+
const f = JSON.parse<f64[]>("[1.12345678901234567890]");
318+
expect(f.length).toBe(1);
319+
});
320+
321+
// swar/array/float.ts: parseFloatElementSWAR exponent paths
322+
describe("SWAR: f64[] with e+ notation covers parseFloatElementSWAR positive-exponent path", () => {
323+
const a = JSON.parse<f64[]>("[1e5,2e+3,3e-1]");
324+
expect(a.length).toBe(3);
325+
expect(a[0]).toBe(100000.0);
326+
expect(a[1]).toBe(2000.0);
327+
expect<f64>(a[2]).toBeCloseTo(0.3);
328+
});
329+
330+
describe("SWAR: f32[] with >19 mantissa digits covers fallbackStore f32 path", () => {
331+
const a = JSON.parse<f32[]>("[1.12345678901234567890]");
332+
expect(a.length).toBe(1);
333+
expect<f32>(a[0]).toBeCloseTo(1.1234568);
334+
});
335+
336+
describe("SWAR: f64[] with 5-digit exponent covers parseFloatElementSWAR expDigits>4 fallback", () => {
337+
const a = JSON.parse<f64[]>("[1e55555]");
338+
expect(a.length).toBe(1);
339+
expect(!isFinite(a[0])).toBe(true);
340+
});
341+
342+
// swar/float.ts + simd/float.ts: expDigits == 0 return expStart
343+
// A struct float field like "1e," has a bare exponent with no following digits.
344+
// In SWAR/SIMD mode the field parser reaches the `if (expDigits == 0)` guard and
345+
// returns expStart (the fallback picks up `1` as the value).
346+
describe("SWAR/SIMD: float field with bare exponent (no digits) covers expDigits==0 return path", () => {
347+
const v = JSON.parse<Vec2CovGapFloat>('{"x":1e,"y":0}');
348+
expect(v.y).toBe(0.0);
349+
});
350+
351+
// simd/float.ts: 16-digit SIMD mantissa stride loop
352+
// deserializeFloat_SIMD enters the `while (p+30 < srcEnd && intDigits+fracDigits<=3)`
353+
// loop only when the fractional part has ≥16 digits ahead. A 35-digit mantissa
354+
// satisfies both guards and drives the loop body.
355+
describe("SIMD: very long float covers 16-digit SIMD mantissa stride loop (lines 79-84)", () => {
356+
const v = JSON.parse<f64>("1.12345678901234567890123456789012345");
357+
expect(v > 1.0).toBe(true);
358+
expect(v < 2.0).toBe(true);
359+
});
360+
361+
// simd/float.ts:81: parse16Digits boundary break
362+
// For "1.23456789012345e6": intDigits=1, fracDigits=0, so
363+
// (intDigits+fracDigits<=3) is TRUE and (p+30 < srcEnd) is TRUE (32 bytes
364+
// remain). parse16Digits_SIMD reads 16 chars "23456789012345e6"; the 'e' at
365+
// position 14 is not a digit → parsed==U64.MAX_VALUE → line 81 break → fallback.
366+
describe("SIMD: 16-digit parse boundary break covers simd/float.ts:81", () => {
367+
const v = JSON.parse<f64>("1.23456789012345e6");
368+
expect(v).toBe(1234567.89012345);
369+
});

0 commit comments

Comments
 (0)