Skip to content

Commit 8ea3432

Browse files
authored
test: add hook cases (#6241)
* chore: init * fix * fix
1 parent c7ea917 commit 8ea3432

File tree

44 files changed

+2132
-21
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+2132
-21
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,6 @@ esbuild.cpuprofile
194194
!packages/rspack-test-tools/tests/treeShakingCases/node_modules
195195
!packages/rspack/tests/cssExtract/cases/**/node_modules
196196

197-
198197
# Binding artifacts
199198
artifacts
200199

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
],
2727
"files.associations": {
2828
"*.snap": "markdown",
29+
"*.snap.txt": "markdown",
2930
"*.json": "jsonc"
3031
},
3132
"cSpell.words": [

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
"@types/rimraf": "3.0.2",
5959
"commander": "12.0.0",
6060
"cross-env": "^7.0.3",
61+
"filenamify": "^4.3.0",
6162
"husky": "^9.0.0",
6263
"is-ci": "3.0.1",
6364
"jest": "29.7.0",

packages/rspack-test-tools/etc/api.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,17 @@ export enum EEsmMode {
224224
// @public (undocumented)
225225
export function formatCode(name: string, raw: string, options: IFormatCodeOptions): string;
226226

227+
// @public (undocumented)
228+
export class HookTaskProcessor extends SnapshotProcessor<ECompilerType.Rspack> {
229+
constructor(hookOptions: IHookProcessorOptions<ECompilerType.Rspack>);
230+
// (undocumented)
231+
config(context: ITestContext): Promise<void>;
232+
// Warning: (ae-forgotten-export) The symbol "IHookProcessorOptions" needs to be exported by the entry point index.d.ts
233+
//
234+
// (undocumented)
235+
protected hookOptions: IHookProcessorOptions<ECompilerType.Rspack>;
236+
}
237+
227238
// @public (undocumented)
228239
export class HotRunnerFactory<T extends ECompilerType> extends BasicRunnerFactory<T> {
229240
// (undocumented)

packages/rspack-test-tools/jest.config.compat.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ module.exports = {
1515
"<rootDir>/tests/HotTestCasesNode.test.js",
1616
"<rootDir>/tests/HotTestCasesWebWorker.test.js",
1717
"<rootDir>/tests/Diagnostics.test.js",
18+
"<rootDir>/tests/HookCases.test.js",
1819
"<rootDir>/tests/StatsTestCases.basictest.js"
1920
]
2021
};
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/// <reference types="jest" />
2+
3+
import { DiffOptions } from "jest-diff";
4+
5+
declare interface FileMatcherOptions {
6+
diff?: DiffOptions;
7+
}
8+
9+
declare global {
10+
namespace jest {
11+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
12+
interface Matchers<R, T> {
13+
toMatchFileSnapshot: (
14+
filename?: string,
15+
options?: FileMatcherOptions
16+
) => void;
17+
}
18+
19+
interface Expect {
20+
toMatchFileSnapshot: (
21+
filename?: string,
22+
options?: FileMatcherOptions
23+
) => void;
24+
}
25+
}
26+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { ISnapshotProcessorOptions, SnapshotProcessor } from ".";
2+
import { ECompilerType, ITestContext, TCompilerOptions } from "../type";
3+
4+
interface IHookProcessorOptions<T extends ECompilerType>
5+
extends ISnapshotProcessorOptions<T> {
6+
options?: (context: ITestContext) => TCompilerOptions<T>;
7+
}
8+
9+
export class HookTaskProcessor extends SnapshotProcessor<ECompilerType.Rspack> {
10+
constructor(
11+
protected hookOptions: IHookProcessorOptions<ECompilerType.Rspack>
12+
) {
13+
super({
14+
defaultOptions: context => {
15+
return {
16+
context: context.getSource(),
17+
mode: "production",
18+
target: "async-node",
19+
devtool: false,
20+
cache: false,
21+
entry: "./hook",
22+
output: {
23+
path: context.getDist()
24+
},
25+
optimization: {
26+
minimize: false
27+
},
28+
experiments: {
29+
rspackFuture: {
30+
newTreeshaking: true
31+
}
32+
}
33+
};
34+
},
35+
...hookOptions,
36+
runable: true
37+
});
38+
}
39+
40+
async config(context: ITestContext): Promise<void> {
41+
await super.config(context);
42+
const compiler = this.getCompiler(context);
43+
if (typeof this.hookOptions.options === "function") {
44+
compiler.mergeOptions(this.hookOptions.options(context));
45+
}
46+
}
47+
}

packages/rspack-test-tools/src/processor/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ export * from "./stats-api";
1313
export * from "./snapshot";
1414
export * from "./builtin";
1515
export * from "./hot-step";
16+
export * from "./hook";

packages/rspack-test-tools/src/processor/snapshot.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,12 @@ export class SnapshotProcessor<
6565
});
6666
fileContents.sort();
6767
const content = fileContents.join("\n\n").replace(/\r\n/g, "\n").trim();
68-
const snapshotPath = path.resolve(
69-
context.getSource(),
70-
`./snapshot/${this._snapshotOptions.snapshot}`
71-
);
68+
const snapshotPath = path.isAbsolute(this._snapshotOptions.snapshot)
69+
? this._snapshotOptions.snapshot
70+
: path.resolve(
71+
context.getSource(),
72+
`./snapshot/${this._snapshotOptions.snapshot}`
73+
);
7274

7375
if (!fs.existsSync(snapshotPath) || global.updateSnapshot) {
7476
fs.ensureDirSync(path.dirname(snapshotPath));
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
const path = require("path");
2+
const fs = require("fs");
3+
const { Compiler, Compilation } = require("@rspack/core");
4+
const { getSerializers } = require("jest-snapshot");
5+
const pathSerializer = require("jest-serializer-path");
6+
const prettyFormat = require("pretty-format");
7+
const createLazyTestEnv = require("../src/helper/legacy/createLazyTestEnv");
8+
const { Source } = require("webpack-sources");
9+
const normalizePaths = pathSerializer.normalizePaths;
10+
const srcDir = path.resolve(__dirname, "../../rspack/tests/fixtures");
11+
const distDir = path.resolve(__dirname, "../../rspack/tests/js/HookTestCases");
12+
const caseDir = path.resolve(__dirname, "./hookCases");
13+
const {
14+
HookTaskProcessor,
15+
TestContext,
16+
ECompilerType,
17+
isValidCaseDirectory,
18+
isDirectory,
19+
BasicRunnerFactory
20+
} = require("..");
21+
22+
const sourceSerializer = {
23+
test(val) {
24+
return val instanceof Source;
25+
},
26+
print(val) {
27+
return val.source();
28+
}
29+
};
30+
31+
const internalSerializer = {
32+
test(val) {
33+
return val instanceof Compiler || val instanceof Compilation;
34+
},
35+
print(val) {
36+
return JSON.stringify(`${val.constructor.name}(internal ignored)`);
37+
}
38+
};
39+
40+
const testPathSerializer = {
41+
test(val) {
42+
return typeof val === "string";
43+
},
44+
print(val) {
45+
return JSON.stringify(
46+
normalizePaths(
47+
val
48+
.replaceAll(srcDir, "<HOOK_SRC_DIR>")
49+
.replaceAll(distDir, "<HOOK_DIST_DIR>")
50+
)
51+
);
52+
}
53+
};
54+
55+
const escapeRegex = true;
56+
const printFunctionName = false;
57+
const normalizeNewlines = string => string.replace(/\r\n|\r/g, "\n");
58+
const serialize = (val, indent = 2, formatOverrides = {}) =>
59+
normalizeNewlines(
60+
prettyFormat.format(val, {
61+
escapeRegex,
62+
indent,
63+
plugins: [
64+
...getSerializers(),
65+
sourceSerializer,
66+
internalSerializer,
67+
testPathSerializer
68+
],
69+
printFunctionName,
70+
...formatOverrides
71+
})
72+
);
73+
74+
class HookCasesContext extends TestContext {
75+
constructor(name, testName, options) {
76+
super(options);
77+
this.snapshots = {};
78+
this.snapshotsList = [];
79+
this.name = name;
80+
this.testName = testName;
81+
this.promises = [];
82+
this.snapped = this.snapped.bind(this);
83+
this.count = 0;
84+
}
85+
86+
/**
87+
* Snapshot function arguments and return value.
88+
* Generated snapshot is located in the same directory with the test source.
89+
* @example
90+
* compiler.hooks.compilation("name", context.snapped((...args) => { ... }))
91+
*/
92+
snapped(cb, prefix = "") {
93+
let context = this;
94+
return function SNAPPED_HOOK(...args) {
95+
let group = prefix ? prefix : context.count++;
96+
context._addSnapshot(args, "input", group);
97+
let output = cb.apply(this, args);
98+
if (output && typeof output.then === "function") {
99+
let resolve;
100+
context.promises.push(new Promise(r => (resolve = r)));
101+
return output
102+
.then(o => {
103+
context._addSnapshot(o, "output (Promise resolved)", group);
104+
return o;
105+
})
106+
.catch(o => {
107+
context._addSnapshot(o, "output (Promise rejected)", group);
108+
return o;
109+
})
110+
.finally(resolve);
111+
}
112+
context._addSnapshot(output, "output", group);
113+
return output;
114+
};
115+
}
116+
117+
/**
118+
* @internal
119+
*/
120+
_addSnapshot(content, name, group) {
121+
content = Buffer.isBuffer(content)
122+
? content
123+
: serialize(content, undefined, {
124+
escapeString: true,
125+
printBasicPrototype: true
126+
}).replace(/\r\n/g, "\n");
127+
(this.snapshots[group] = this.snapshots[group] || []).push([content, name]);
128+
if (!this.snapshotsList.includes(group)) {
129+
this.snapshotsList.push(group);
130+
}
131+
}
132+
133+
/**
134+
* @internal
135+
*/
136+
async collectSnapshots(
137+
options = {
138+
diff: {}
139+
}
140+
) {
141+
await Promise.allSettled(this.promises);
142+
if (!this.snapshotsList.length) return;
143+
144+
let snapshots = this.snapshotsList.reduce((acc, group, index) => {
145+
let block = this.snapshots[group || index].reduce(
146+
(acc, [content, name]) => {
147+
name = `## ${name || `test: ${index}`}\n\n`;
148+
let block = "```javascript\n" + content + "\n```\n";
149+
return (acc += name + block + "\n");
150+
},
151+
""
152+
);
153+
group = Number.isInteger(group) ? `Group: ${index}` : group;
154+
group = `# ${group}\n\n`;
155+
return (acc += group + block);
156+
}, "");
157+
158+
expect(snapshots).toMatchFileSnapshot(
159+
path.join(path.dirname(this.name), "hooks.snap.txt"),
160+
options
161+
);
162+
}
163+
}
164+
165+
describe("Hook", () => {
166+
const categories = fs
167+
.readdirSync(caseDir)
168+
.filter(isValidCaseDirectory)
169+
.filter(folder => isDirectory(path.join(caseDir, folder)))
170+
.map(cat => {
171+
return {
172+
name: cat,
173+
tests: fs
174+
.readdirSync(path.join(caseDir, cat))
175+
.map(i => {
176+
if (isDirectory(path.join(caseDir, cat, i))) {
177+
return i;
178+
}
179+
})
180+
.filter(Boolean)
181+
.sort()
182+
};
183+
});
184+
185+
for (let cat of categories) {
186+
describe(cat.name, () => {
187+
for (let name of cat.tests) {
188+
async function run(_name, testName, processor) {
189+
const context = new HookCasesContext(_name, testName, {
190+
src: srcDir,
191+
dist: path.join(distDir, cat.name, name),
192+
runnerFactory: BasicRunnerFactory
193+
});
194+
try {
195+
await processor.before(context);
196+
await processor.config(context);
197+
await processor.compiler(context);
198+
await processor.build(context);
199+
await processor.run(env, context);
200+
} catch (e) {
201+
throw e;
202+
} finally {
203+
await context.collectSnapshots();
204+
await processor.check(null, context);
205+
await processor.after(context);
206+
}
207+
}
208+
209+
let file = path.join(caseDir, cat.name, name, "test.js");
210+
const caseConfig = require(file);
211+
it(caseConfig.description, async () => {
212+
await run(
213+
file,
214+
path.basename(name.slice(0, name.indexOf(path.extname(name)))),
215+
new HookTaskProcessor({
216+
name: file,
217+
compilerType: ECompilerType.Rspack,
218+
findBundle: function (i, options) {
219+
return ["main.js"];
220+
},
221+
snapshot: path.join(caseDir, cat.name, name, "output.snap.txt"),
222+
...caseConfig
223+
})
224+
);
225+
});
226+
const env = createLazyTestEnv(1000);
227+
}
228+
});
229+
}
230+
});

0 commit comments

Comments
 (0)