Skip to content

Commit

Permalink
test: add hook cases (#6241)
Browse files Browse the repository at this point in the history
* chore: init

* fix

* fix
  • Loading branch information
h-a-n-a authored Apr 24, 2024
1 parent c7ea917 commit 8ea3432
Show file tree
Hide file tree
Showing 44 changed files with 2,132 additions and 21 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,6 @@ esbuild.cpuprofile
!packages/rspack-test-tools/tests/treeShakingCases/node_modules
!packages/rspack/tests/cssExtract/cases/**/node_modules


# Binding artifacts
artifacts

Expand Down
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
],
"files.associations": {
"*.snap": "markdown",
"*.snap.txt": "markdown",
"*.json": "jsonc"
},
"cSpell.words": [
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"@types/rimraf": "3.0.2",
"commander": "12.0.0",
"cross-env": "^7.0.3",
"filenamify": "^4.3.0",
"husky": "^9.0.0",
"is-ci": "3.0.1",
"jest": "29.7.0",
Expand Down
11 changes: 11 additions & 0 deletions packages/rspack-test-tools/etc/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,17 @@ export enum EEsmMode {
// @public (undocumented)
export function formatCode(name: string, raw: string, options: IFormatCodeOptions): string;

// @public (undocumented)
export class HookTaskProcessor extends SnapshotProcessor<ECompilerType.Rspack> {
constructor(hookOptions: IHookProcessorOptions<ECompilerType.Rspack>);
// (undocumented)
config(context: ITestContext): Promise<void>;
// Warning: (ae-forgotten-export) The symbol "IHookProcessorOptions" needs to be exported by the entry point index.d.ts
//
// (undocumented)
protected hookOptions: IHookProcessorOptions<ECompilerType.Rspack>;
}

// @public (undocumented)
export class HotRunnerFactory<T extends ECompilerType> extends BasicRunnerFactory<T> {
// (undocumented)
Expand Down
1 change: 1 addition & 0 deletions packages/rspack-test-tools/jest.config.compat.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module.exports = {
"<rootDir>/tests/HotTestCasesNode.test.js",
"<rootDir>/tests/HotTestCasesWebWorker.test.js",
"<rootDir>/tests/Diagnostics.test.js",
"<rootDir>/tests/HookCases.test.js",
"<rootDir>/tests/StatsTestCases.basictest.js"
]
};
26 changes: 26 additions & 0 deletions packages/rspack-test-tools/jest.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/// <reference types="jest" />

import { DiffOptions } from "jest-diff";

declare interface FileMatcherOptions {
diff?: DiffOptions;
}

declare global {
namespace jest {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Matchers<R, T> {
toMatchFileSnapshot: (
filename?: string,
options?: FileMatcherOptions
) => void;
}

interface Expect {
toMatchFileSnapshot: (
filename?: string,
options?: FileMatcherOptions
) => void;
}
}
}
47 changes: 47 additions & 0 deletions packages/rspack-test-tools/src/processor/hook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { ISnapshotProcessorOptions, SnapshotProcessor } from ".";
import { ECompilerType, ITestContext, TCompilerOptions } from "../type";

interface IHookProcessorOptions<T extends ECompilerType>
extends ISnapshotProcessorOptions<T> {
options?: (context: ITestContext) => TCompilerOptions<T>;
}

export class HookTaskProcessor extends SnapshotProcessor<ECompilerType.Rspack> {
constructor(
protected hookOptions: IHookProcessorOptions<ECompilerType.Rspack>
) {
super({
defaultOptions: context => {
return {
context: context.getSource(),
mode: "production",
target: "async-node",
devtool: false,
cache: false,
entry: "./hook",
output: {
path: context.getDist()
},
optimization: {
minimize: false
},
experiments: {
rspackFuture: {
newTreeshaking: true
}
}
};
},
...hookOptions,
runable: true
});
}

async config(context: ITestContext): Promise<void> {
await super.config(context);
const compiler = this.getCompiler(context);
if (typeof this.hookOptions.options === "function") {
compiler.mergeOptions(this.hookOptions.options(context));
}
}
}
1 change: 1 addition & 0 deletions packages/rspack-test-tools/src/processor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ export * from "./stats-api";
export * from "./snapshot";
export * from "./builtin";
export * from "./hot-step";
export * from "./hook";
10 changes: 6 additions & 4 deletions packages/rspack-test-tools/src/processor/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,12 @@ export class SnapshotProcessor<
});
fileContents.sort();
const content = fileContents.join("\n\n").replace(/\r\n/g, "\n").trim();
const snapshotPath = path.resolve(
context.getSource(),
`./snapshot/${this._snapshotOptions.snapshot}`
);
const snapshotPath = path.isAbsolute(this._snapshotOptions.snapshot)
? this._snapshotOptions.snapshot
: path.resolve(
context.getSource(),
`./snapshot/${this._snapshotOptions.snapshot}`
);

if (!fs.existsSync(snapshotPath) || global.updateSnapshot) {
fs.ensureDirSync(path.dirname(snapshotPath));
Expand Down
230 changes: 230 additions & 0 deletions packages/rspack-test-tools/tests/HookCases.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
const path = require("path");
const fs = require("fs");
const { Compiler, Compilation } = require("@rspack/core");
const { getSerializers } = require("jest-snapshot");
const pathSerializer = require("jest-serializer-path");
const prettyFormat = require("pretty-format");
const createLazyTestEnv = require("../src/helper/legacy/createLazyTestEnv");
const { Source } = require("webpack-sources");
const normalizePaths = pathSerializer.normalizePaths;
const srcDir = path.resolve(__dirname, "../../rspack/tests/fixtures");
const distDir = path.resolve(__dirname, "../../rspack/tests/js/HookTestCases");
const caseDir = path.resolve(__dirname, "./hookCases");
const {
HookTaskProcessor,
TestContext,
ECompilerType,
isValidCaseDirectory,
isDirectory,
BasicRunnerFactory
} = require("..");

const sourceSerializer = {
test(val) {
return val instanceof Source;
},
print(val) {
return val.source();
}
};

const internalSerializer = {
test(val) {
return val instanceof Compiler || val instanceof Compilation;
},
print(val) {
return JSON.stringify(`${val.constructor.name}(internal ignored)`);
}
};

const testPathSerializer = {
test(val) {
return typeof val === "string";
},
print(val) {
return JSON.stringify(
normalizePaths(
val
.replaceAll(srcDir, "<HOOK_SRC_DIR>")
.replaceAll(distDir, "<HOOK_DIST_DIR>")
)
);
}
};

const escapeRegex = true;
const printFunctionName = false;
const normalizeNewlines = string => string.replace(/\r\n|\r/g, "\n");
const serialize = (val, indent = 2, formatOverrides = {}) =>
normalizeNewlines(
prettyFormat.format(val, {
escapeRegex,
indent,
plugins: [
...getSerializers(),
sourceSerializer,
internalSerializer,
testPathSerializer
],
printFunctionName,
...formatOverrides
})
);

class HookCasesContext extends TestContext {
constructor(name, testName, options) {
super(options);
this.snapshots = {};
this.snapshotsList = [];
this.name = name;
this.testName = testName;
this.promises = [];
this.snapped = this.snapped.bind(this);
this.count = 0;
}

/**
* Snapshot function arguments and return value.
* Generated snapshot is located in the same directory with the test source.
* @example
* compiler.hooks.compilation("name", context.snapped((...args) => { ... }))
*/
snapped(cb, prefix = "") {
let context = this;
return function SNAPPED_HOOK(...args) {
let group = prefix ? prefix : context.count++;
context._addSnapshot(args, "input", group);
let output = cb.apply(this, args);
if (output && typeof output.then === "function") {
let resolve;
context.promises.push(new Promise(r => (resolve = r)));
return output
.then(o => {
context._addSnapshot(o, "output (Promise resolved)", group);
return o;
})
.catch(o => {
context._addSnapshot(o, "output (Promise rejected)", group);
return o;
})
.finally(resolve);
}
context._addSnapshot(output, "output", group);
return output;
};
}

/**
* @internal
*/
_addSnapshot(content, name, group) {
content = Buffer.isBuffer(content)
? content
: serialize(content, undefined, {
escapeString: true,
printBasicPrototype: true
}).replace(/\r\n/g, "\n");
(this.snapshots[group] = this.snapshots[group] || []).push([content, name]);
if (!this.snapshotsList.includes(group)) {
this.snapshotsList.push(group);
}
}

/**
* @internal
*/
async collectSnapshots(
options = {
diff: {}
}
) {
await Promise.allSettled(this.promises);
if (!this.snapshotsList.length) return;

let snapshots = this.snapshotsList.reduce((acc, group, index) => {
let block = this.snapshots[group || index].reduce(
(acc, [content, name]) => {
name = `## ${name || `test: ${index}`}\n\n`;
let block = "```javascript\n" + content + "\n```\n";
return (acc += name + block + "\n");
},
""
);
group = Number.isInteger(group) ? `Group: ${index}` : group;
group = `# ${group}\n\n`;
return (acc += group + block);
}, "");

expect(snapshots).toMatchFileSnapshot(
path.join(path.dirname(this.name), "hooks.snap.txt"),
options
);
}
}

describe("Hook", () => {
const categories = fs
.readdirSync(caseDir)
.filter(isValidCaseDirectory)
.filter(folder => isDirectory(path.join(caseDir, folder)))
.map(cat => {
return {
name: cat,
tests: fs
.readdirSync(path.join(caseDir, cat))
.map(i => {
if (isDirectory(path.join(caseDir, cat, i))) {
return i;
}
})
.filter(Boolean)
.sort()
};
});

for (let cat of categories) {
describe(cat.name, () => {
for (let name of cat.tests) {
async function run(_name, testName, processor) {
const context = new HookCasesContext(_name, testName, {
src: srcDir,
dist: path.join(distDir, cat.name, name),
runnerFactory: BasicRunnerFactory
});
try {
await processor.before(context);
await processor.config(context);
await processor.compiler(context);
await processor.build(context);
await processor.run(env, context);
} catch (e) {
throw e;
} finally {
await context.collectSnapshots();
await processor.check(null, context);
await processor.after(context);
}
}

let file = path.join(caseDir, cat.name, name, "test.js");
const caseConfig = require(file);
it(caseConfig.description, async () => {
await run(
file,
path.basename(name.slice(0, name.indexOf(path.extname(name)))),
new HookTaskProcessor({
name: file,
compilerType: ECompilerType.Rspack,
findBundle: function (i, options) {
return ["main.js"];
},
snapshot: path.join(caseDir, cat.name, name, "output.snap.txt"),
...caseConfig
})
);
});
const env = createLazyTestEnv(1000);
}
});
}
});
Loading

2 comments on commit 8ea3432

@rspack-bot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Ran ecosystem CI: Open

suite result
modernjs, self-hosted, Linux, ci ❌ failure
_selftest, ubuntu-latest ✅ success
nx, ubuntu-latest ✅ success
rspress, ubuntu-latest ❌ failure
rsbuild, ubuntu-latest ❌ failure
compat, ubuntu-latest ✅ success
examples, ubuntu-latest ✅ success

@rspack-bot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Benchmark detail: Open

Name Base (2024-04-24 332b127) Current Change
10000_development-mode + exec 2.66 s ± 28 ms 2.71 s ± 25 ms +1.80 %
10000_development-mode_hmr + exec 682 ms ± 3.7 ms 700 ms ± 2.1 ms +2.66 %
10000_production-mode + exec 2.46 s ± 31 ms 2.59 s ± 27 ms +5.17 %
arco-pro_development-mode + exec 2.48 s ± 86 ms 2.51 s ± 74 ms +1.23 %
arco-pro_development-mode_hmr + exec 429 ms ± 1.2 ms 431 ms ± 3.3 ms +0.45 %
arco-pro_development-mode_hmr_intercept-plugin + exec 441 ms ± 5.4 ms 443 ms ± 2.4 ms +0.54 %
arco-pro_development-mode_intercept-plugin + exec 3.22 s ± 88 ms 3.24 s ± 79 ms +0.75 %
arco-pro_production-mode + exec 3.95 s ± 74 ms 4.04 s ± 71 ms +2.29 %
arco-pro_production-mode_intercept-plugin + exec 4.66 s ± 238 ms 4.81 s ± 118 ms +3.20 %
threejs_development-mode_10x + exec 2.05 s ± 16 ms 2.08 s ± 17 ms +1.08 %
threejs_development-mode_10x_hmr + exec 753 ms ± 13 ms 753 ms ± 17 ms -0.08 %
threejs_production-mode_10x + exec 5.14 s ± 40 ms 5.29 s ± 33 ms +3.00 %

Threshold exceeded: ["10000_production-mode + exec"]

Please sign in to comment.