Skip to content

Commit 210f7a6

Browse files
chore: Don't test internals of wgslGenerator (#2531)
* Clean up wgslGenerator.test.ts * Clean up typeInference.test.ts * Allow for TgpuFn in snippet utils * Rewrite some tests to end-to-end tests * Move `convertToCommonType` tests to `getBestConversion` * Rewrite more tests to resolve * Split conversion into internal and e2e tests * Add more e2e tests * Update imports * Move tseynit tests to internal * Split consoleLog tests into internal and e2e * Move dualImpl tests to internal * Move generationHelpers to internal * Add console.warn mock tests * Revert import from 'typegpu'
1 parent 9e4fbab commit 210f7a6

11 files changed

Lines changed: 583 additions & 569 deletions
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { describe, expect } from 'vitest';
2+
import { deserializeAndStringify } from '../../src/tgsl/consoleLog/deserializers.ts';
3+
import { it } from 'typegpu-testing-utility';
4+
import { d } from '../../src/index.js';
5+
6+
describe('deserializeAndStringify', () => {
7+
it('works for string literals', () => {
8+
const data = new Uint32Array([]);
9+
const logInfo: (string | d.AnyWgslData)[] = ['String literal'];
10+
11+
expect(deserializeAndStringify(data, logInfo)).toMatchInlineSnapshot(
12+
`
13+
[
14+
"String literal",
15+
]
16+
`,
17+
);
18+
});
19+
20+
it('works for u32', () => {
21+
const data = new Uint32Array([123]);
22+
const logInfo: (string | d.AnyWgslData)[] = [d.u32];
23+
24+
expect(deserializeAndStringify(data, logInfo)).toMatchInlineSnapshot(
25+
`
26+
[
27+
"123",
28+
]
29+
`,
30+
);
31+
});
32+
33+
it('works for vec3u', () => {
34+
const data = new Uint32Array([1, 2, 3]);
35+
const logInfo: (string | d.AnyWgslData)[] = [d.vec3u];
36+
37+
expect(deserializeAndStringify(data, logInfo)).toMatchInlineSnapshot(
38+
`
39+
[
40+
"vec3u(1, 2, 3)",
41+
]
42+
`,
43+
);
44+
});
45+
46+
it('works for clumped vectors', () => {
47+
const data = new Uint32Array([1, 2, 3, 4, 5, 6]); // no alignment
48+
const logInfo: (string | d.AnyWgslData)[] = [d.vec3u, d.vec3u];
49+
50+
expect(deserializeAndStringify(data, logInfo)).toMatchInlineSnapshot(
51+
`
52+
[
53+
"vec3u(1, 2, 3)",
54+
"vec3u(4, 5, 6)",
55+
]
56+
`,
57+
);
58+
});
59+
60+
it('works for multiple arguments', () => {
61+
const data = new Uint32Array([1, 2, 3, 456]);
62+
const logInfo: (string | d.AnyWgslData)[] = ['GID:', d.vec3u, 'Result:', d.u32];
63+
64+
expect(deserializeAndStringify(data, logInfo)).toMatchInlineSnapshot(
65+
`
66+
[
67+
"GID:",
68+
"vec3u(1, 2, 3)",
69+
"Result:",
70+
"456",
71+
]
72+
`,
73+
);
74+
});
75+
76+
it('works for arrays', () => {
77+
const data = new Uint32Array([1, 2, 3, 4]);
78+
const logInfo: (string | d.AnyWgslData)[] = [d.arrayOf(d.u32, 4)];
79+
80+
expect(deserializeAndStringify(data, logInfo)).toMatchInlineSnapshot(
81+
`
82+
[
83+
"[1, 2, 3, 4]",
84+
]
85+
`,
86+
);
87+
});
88+
89+
it('works for nested arrays', () => {
90+
const data = new Uint32Array([1, 2, 3, 4]);
91+
const logInfo: (string | d.AnyWgslData)[] = [d.arrayOf(d.arrayOf(d.u32, 2), 2)];
92+
93+
expect(deserializeAndStringify(data, logInfo)).toMatchInlineSnapshot(
94+
`
95+
[
96+
"[[1, 2], [3, 4]]",
97+
]
98+
`,
99+
);
100+
});
101+
102+
it('works for structs', () => {
103+
const data = new Uint32Array([1, 2, 3, 4]);
104+
const logInfo: (string | d.AnyWgslData)[] = [d.struct({ vec: d.vec3u, num: d.u32 })];
105+
106+
expect(deserializeAndStringify(data, logInfo)).toMatchInlineSnapshot(
107+
`
108+
[
109+
"{ vec: vec3u(1, 2, 3), num: 4 }",
110+
]
111+
`,
112+
);
113+
});
114+
115+
it('works for nested structs', () => {
116+
const data = new Uint32Array([1, 2, 3, 4, 1]);
117+
const logInfo: (string | d.AnyWgslData)[] = [
118+
d.struct({
119+
nested: d.struct({ vec: d.vec3u, num: d.u32 }),
120+
bool: d.bool,
121+
}),
122+
];
123+
124+
expect(deserializeAndStringify(data, logInfo)).toMatchInlineSnapshot(
125+
`
126+
[
127+
"{ nested: { vec: vec3u(1, 2, 3), num: 4 }, bool: true }",
128+
]
129+
`,
130+
);
131+
});
132+
});

packages/typegpu/tests/tgsl/dualImpl.test.ts renamed to packages/typegpu/tests/internal/dualImpl.test.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import { describe, expect, it } from 'vitest';
22
import { dualImpl, MissingCpuImplError } from '../../src/core/function/dualImpl.ts';
3-
import { Void } from '../../src/data/wgslTypes.ts';
4-
import tgpu from '../../src/index.js';
53
import { getName } from '../../src/shared/meta.ts';
4+
import tgpu, { d } from '../../src/index.js';
65

76
describe('dualImpl', () => {
87
it('names functions created by dualImpl', () => {
98
const dual = dualImpl({
109
normalImpl: () => {},
11-
signature: () => ({ argTypes: [], returnType: Void }),
10+
signature: () => ({ argTypes: [], returnType: d.Void }),
1211
codegenImpl: () => 'code',
1312
name: 'myDualImpl',
1413
});

packages/typegpu/tests/tgsl/generationHelpers.test.ts renamed to packages/typegpu/tests/internal/generationHelpers.test.ts

File renamed without changes.
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import { describe, expect } from 'vitest';
2+
import { abstractFloat, abstractInt } from '../../src/data/numeric.ts';
3+
import { getBestConversion } from '../../src/tgsl/conversion.ts';
4+
import { it } from 'typegpu-testing-utility';
5+
import { INTERNAL_createPtr } from '../../src/data/ptr.ts';
6+
import { d } from '../../src/index.js';
7+
8+
describe('getBestConversion', () => {
9+
// d.ptrPrivate(d.f32)
10+
const ptrF32 = INTERNAL_createPtr('private', d.f32, 'read-write', /* implicit */ true);
11+
12+
it('returns result for identical types', () => {
13+
const res = getBestConversion([d.f32, d.f32]);
14+
expect(res?.targetType).toBe(d.f32);
15+
expect(res?.actions).toEqual([
16+
{ sourceIndex: 0, action: 'none' },
17+
{ sourceIndex: 1, action: 'none' },
18+
]);
19+
expect(res?.hasImplicitConversions).toBeFalsy();
20+
});
21+
22+
it('handles abstract types automatically', () => {
23+
const resFloat = getBestConversion([abstractFloat, d.f32]);
24+
expect(resFloat?.targetType).toBe(d.f32);
25+
expect(resFloat?.actions).toEqual([
26+
{ sourceIndex: 0, action: 'none' },
27+
{ sourceIndex: 1, action: 'none' },
28+
]);
29+
expect(resFloat?.hasImplicitConversions).toBeFalsy();
30+
31+
const resInt = getBestConversion([abstractInt, d.i32]);
32+
expect(resInt?.targetType).toBe(d.i32);
33+
expect(resInt?.actions).toEqual([
34+
{ sourceIndex: 0, action: 'none' },
35+
{ sourceIndex: 1, action: 'none' },
36+
]);
37+
expect(resInt?.hasImplicitConversions).toBeFalsy();
38+
39+
const resMixed = getBestConversion([abstractInt, d.f32]);
40+
expect(resMixed?.targetType).toBe(d.f32); // abstractInt -> f32 (rank 6)
41+
expect(resMixed?.actions).toEqual([
42+
{ sourceIndex: 0, action: 'none' },
43+
{ sourceIndex: 1, action: 'none' },
44+
]);
45+
expect(resMixed?.hasImplicitConversions).toBeFalsy();
46+
47+
const resMixed2 = getBestConversion([abstractInt, abstractFloat, d.f16]);
48+
expect(resMixed2?.targetType).toBe(d.f16); // abstractInt -> f16 (rank 7), abstractFloat -> f16 (rank 2)
49+
expect(resMixed2?.actions).toEqual([
50+
{ sourceIndex: 0, action: 'none' },
51+
{ sourceIndex: 1, action: 'none' },
52+
{ sourceIndex: 2, action: 'none' },
53+
]);
54+
expect(resMixed2?.hasImplicitConversions).toBeFalsy();
55+
});
56+
57+
it('handles implicit casts', () => {
58+
const res = getBestConversion([d.i32, d.f32]);
59+
expect(res?.targetType).toBe(d.f32);
60+
expect(res?.actions).toEqual([
61+
{ sourceIndex: 0, action: 'cast', targetType: d.f32 },
62+
{ sourceIndex: 1, action: 'none' },
63+
]);
64+
expect(res?.hasImplicitConversions).toBe(true);
65+
66+
// Test case: [u32, f16, i32]
67+
// Potential targets (from input): u32, f16, i32
68+
// Preference: f32(0) > f16(1) > i32(2) > u32(3)
69+
//
70+
// Target f16 (pref 1):
71+
// u32 (3) -> f16 (1): dest < src => rank 10
72+
// f16 (1) -> f16 (1): rank 0
73+
// i32 (2) -> f16 (1): dest < src => rank 10
74+
// Total Rank = 10 + 0 + 10 = 20
75+
//
76+
// Target i32 (pref 2):
77+
// u32 (3) -> i32 (2): dest < src => rank 10
78+
// f16 (1) -> i32 (2): dest >= src => rank 20
79+
// i32 (2) -> i32 (2): rank 0
80+
// Total Rank = 10 + 20 + 0 = 30
81+
//
82+
// Target u32 (pref 3):
83+
// u32 (3) -> u32 (3): rank 0
84+
// f16 (1) -> u32 (3): dest >= src => rank 20
85+
// i32 (2) -> u32 (3): dest >= src => rank 20
86+
// Total Rank = 0 + 20 + 20 = 40
87+
//
88+
// Lowest rank is 20 for target f16.
89+
const res2Result = getBestConversion([d.u32, d.f16, d.i32]);
90+
expect(res2Result?.targetType).toBe(d.f16);
91+
expect(res2Result?.actions).toEqual([
92+
// Order corresponds to input [u32, f16, i32]
93+
{ sourceIndex: 0, action: 'cast', targetType: d.f16 }, // u32 -> f16
94+
{ sourceIndex: 1, action: 'none' }, // f16 -> f16
95+
{ sourceIndex: 2, action: 'cast', targetType: d.f16 }, // i32 -> f16
96+
]);
97+
expect(res2Result?.hasImplicitConversions).toBe(true);
98+
});
99+
100+
it('handles pointer dereferencing', () => {
101+
const res = getBestConversion([ptrF32, d.f32]);
102+
expect(res?.targetType).toBe(d.f32);
103+
expect(res?.actions).toEqual([
104+
{ sourceIndex: 0, action: 'deref' },
105+
{ sourceIndex: 1, action: 'none' },
106+
]);
107+
expect(res?.hasImplicitConversions).toBeFalsy();
108+
109+
const res2 = getBestConversion([ptrF32, d.i32, d.f32]); // Target f32: deref, cast, none
110+
expect(res2?.targetType).toBe(d.f32);
111+
expect(res2?.actions).toEqual([
112+
{ sourceIndex: 0, action: 'deref' },
113+
{ sourceIndex: 1, action: 'cast', targetType: d.f32 }, // Implicitly derefs then casts
114+
{ sourceIndex: 2, action: 'none' },
115+
]);
116+
expect(res2?.hasImplicitConversions).toBe(true); // Because of the cast
117+
});
118+
119+
it('returns undefined for incompatible types', () => {
120+
expect(getBestConversion([d.f32, d.vec2f])).toBeUndefined();
121+
expect(getBestConversion([d.struct({ a: d.f32 }), d.f32])).toBeUndefined();
122+
});
123+
124+
it('respects targetTypes restriction', () => {
125+
// abstractInt -> i32 (rank 3), u32 (rank 4), f32 (rank 6), f16 (rank 7)
126+
// i32 -> i32 (rank 0)
127+
// Common types without restriction: i32
128+
// Restrict to f32:
129+
const res = getBestConversion([abstractInt, d.i32], [d.f32]);
130+
expect(res?.targetType).toBe(d.f32);
131+
expect(res?.actions).toEqual([
132+
{ sourceIndex: 0, action: 'none' }, // abstractInt -> f32 is auto
133+
{ sourceIndex: 1, action: 'cast', targetType: d.f32 }, // i32 -> f32 is cast
134+
]);
135+
expect(res?.hasImplicitConversions).toBe(true);
136+
137+
// Restrict to incompatible type
138+
const resFail = getBestConversion([abstractInt, d.i32], [d.vec2f]);
139+
expect(resFail).toBeUndefined();
140+
141+
// Restrict to a type requiring implicit conversion for all
142+
const resImplicit = getBestConversion([d.i32, d.u32], [d.f32]);
143+
expect(resImplicit?.targetType).toBe(d.f32);
144+
expect(resImplicit?.actions).toEqual([
145+
{ sourceIndex: 0, action: 'cast', targetType: d.f32 },
146+
{ sourceIndex: 1, action: 'cast', targetType: d.f32 },
147+
]);
148+
expect(resImplicit?.hasImplicitConversions).toBe(true);
149+
});
150+
151+
it('can restrict abstractFloat to u32', () => {
152+
const res = getBestConversion([abstractFloat], [d.u32]);
153+
expect(res).toBeDefined();
154+
expect(res?.targetType).toBe(d.u32);
155+
expect(res?.actions).toEqual([{ sourceIndex: 0, action: 'cast', targetType: d.u32 }]);
156+
expect(res?.hasImplicitConversions).toBe(true);
157+
});
158+
159+
it('handles void gracefully', () => {
160+
const resFail = getBestConversion([d.f32, d.Void]);
161+
expect(resFail).toBeUndefined();
162+
});
163+
164+
it('handles void as target type gracefully', () => {
165+
const resFail = getBestConversion([d.f32], [d.Void]);
166+
expect(resFail).toBeUndefined();
167+
});
168+
169+
// TODO(#2519): This would require multiple passes of the conversion algorithm - maybe something to consider in the future
170+
// it('handles types needing deref and cast', () => {
171+
// const res = getBestConversion([ptrI32, f32]);
172+
// expect(res?.targetType).toBe(f32);
173+
// expect(res?.actions).toEqual([
174+
// { sourceIndex: 0, action: 'cast', targetType: f32 }, // Implicit deref + cast
175+
// { sourceIndex: 1, action: 'none' },
176+
// ]);
177+
// expect(res?.hasImplicitConversions).toBe(true);
178+
// });
179+
});

packages/typegpu/tests/tgsl/tseynit.test.ts renamed to packages/typegpu/tests/internal/tseynit.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest';
22
import * as tinyest from 'tinyest';
33
import { getFunctionMetadata } from '../../src/shared/meta.ts';
44
import { stringifyNode } from '../../src/shared/tseynit.ts';
5-
import tgpu, { d } from 'typegpu';
5+
import tgpu, { d } from '../../src/index.js';
66

77
function getBodyAst(fn: () => void) {
88
const meta = getFunctionMetadata(fn);

0 commit comments

Comments
 (0)