Skip to content

Commit

Permalink
chore: hook case creator (#6419)
Browse files Browse the repository at this point in the history
* chore: add hook case creator

* chore: add hook case creator
  • Loading branch information
LingyuCoder authored Apr 30, 2024
1 parent ea79a64 commit 42527c5
Show file tree
Hide file tree
Showing 11 changed files with 286 additions and 258 deletions.
36 changes: 35 additions & 1 deletion packages/rspack-test-tools/etc/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ export function createErrorCase(name: string, src: string, dist: string, testCon
// @public (undocumented)
export function createHashCase(name: string, src: string, dist: string): void;

// @public (undocumented)
export function createHookCase(name: string, src: string, dist: string, source: string): void;

// @public (undocumented)
export function createHotCase(name: string, src: string, dist: string, target: TCompilerOptions<ECompilerType.Rspack>["target"]): void;

Expand Down Expand Up @@ -260,12 +263,43 @@ export function escapeSep(str: string): string;
export function formatCode(name: string, raw: string, options: IFormatCodeOptions): string;

// @public (undocumented)
export function getSimpleProcessorRunner(src: string, dist: string, env: ITestEnv): (name: string, processor: ITestProcessor) => Promise<void>;
export function getSimpleProcessorRunner(src: string, dist: string, options?: {
env?: () => ITestEnv;
context?: (src: string, dist: string) => ITestContext;
}): (name: string, processor: ITestProcessor) => Promise<void>;

// @public (undocumented)
export class HookCasesContext extends TestContext {
constructor(src: string, testName: string, options: TTestContextOptions);
// @internal (undocumented)
_addSnapshot(content: unknown, name: string, group: string | number): void;
// @internal (undocumented)
collectSnapshots(options?: {
diff: {};
}): Promise<void>;
// (undocumented)
protected count: number;
// (undocumented)
protected options: TTestContextOptions;
// (undocumented)
protected promises: Promise<void>[];
snapped(cb: (...args: unknown[]) => Promise<unknown>, prefix?: string): (this: any, ...args: unknown[]) => Promise<unknown>;
// (undocumented)
protected snapshots: Record<string | number, Array<[string | Buffer, string]>>;
// (undocumented)
protected snapshotsList: Array<string | number>;
// (undocumented)
protected src: string;
// (undocumented)
protected testName: string;
}

// @public (undocumented)
export class HookTaskProcessor extends SnapshotProcessor<ECompilerType.Rspack> {
constructor(hookOptions: IHookProcessorOptions<ECompilerType.Rspack>);
// (undocumented)
check(env: ITestEnv, context: HookCasesContext): Promise<void>;
// (undocumented)
config(context: ITestContext): Promise<void>;
// (undocumented)
protected hookOptions: IHookProcessorOptions<ECompilerType.Rspack>;
Expand Down
7 changes: 1 addition & 6 deletions packages/rspack-test-tools/src/case/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,7 @@ export function createCompilerCase(
testConfig: string
) {
const caseConfig: TCompilerCaseConfig = require(testConfig);

const runner = getSimpleProcessorRunner(src, dist, {
it,
beforeEach,
afterEach
});
const runner = getSimpleProcessorRunner(src, dist);

it(caseConfig.description, async () => {
await runner(
Expand Down
6 changes: 1 addition & 5 deletions packages/rspack-test-tools/src/case/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,7 @@ export function createErrorCase(
addedSerializer = true;
}
const caseConfig = require(testConfig);
const runner = getSimpleProcessorRunner(src, dist, {
it,
beforeEach,
afterEach
});
const runner = getSimpleProcessorRunner(src, dist);

it(caseConfig.description, async () => {
await runner(
Expand Down
46 changes: 46 additions & 0 deletions packages/rspack-test-tools/src/case/hook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import path from "path";
import { getSimpleProcessorRunner } from "../test/simple";
import { HookCasesContext, HookTaskProcessor } from "../processor";
import { BasicRunnerFactory } from "../runner";
import { ECompilerType, TCompilerOptions } from "../type";
import createLazyTestEnv from "../helper/legacy/createLazyTestEnv";

export function createHookCase(
name: string,
src: string,
dist: string,
source: string
) {
const caseConfig = require(path.join(src, "test.js"));
const testName = path.basename(
name.slice(0, name.indexOf(path.extname(name)))
);
const runner = getSimpleProcessorRunner(source, dist, {
env: () => env,
context: () =>
new HookCasesContext(src, testName, {
src: source,
dist: dist,
runnerFactory: BasicRunnerFactory
})
});

it(caseConfig.description, async () => {
await runner(
name,
new HookTaskProcessor({
name,
compilerType: ECompilerType.Rspack,
findBundle: function (
i: number,
options: TCompilerOptions<ECompilerType.Rspack>
) {
return ["main.js"];
},
snapshot: path.join(src, "output.snap.txt"),
...caseConfig
})
);
});
const env = createLazyTestEnv(10000);
}
1 change: 1 addition & 0 deletions packages/rspack-test-tools/src/case/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ export * from "./hot-step";
export * from "./compiler";
export * from "./stats-api";
export * from "./error";
export * from "./hook";
6 changes: 1 addition & 5 deletions packages/rspack-test-tools/src/case/stats-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,7 @@ export function createStatsAPICase(
addedSerializer = true;
}
const caseConfig: TStatsAPICaseConfig = require(testConfig);
const runner = getSimpleProcessorRunner(src, dist, {
it,
beforeEach,
afterEach
});
const runner = getSimpleProcessorRunner(src, dist);

it(caseConfig.description, async () => {
await runner(
Expand Down
3 changes: 2 additions & 1 deletion packages/rspack-test-tools/src/processor/diagnostic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ export class RspackDiagnosticProcessor extends BasicTaskProcessor<ECompilerType.
);
// TODO: change to stats.errorStack
output = output
.replaceAll("│", "")
.split("│")
.join("")
.split(/\r?\n/)
.map((s: string) => s.trim())
.join("");
Expand Down
182 changes: 180 additions & 2 deletions packages/rspack-test-tools/src/processor/hook.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,178 @@
import { ISnapshotProcessorOptions, SnapshotProcessor } from ".";
import { ECompilerType, ITestContext, TCompilerOptions } from "../type";
import path from "path";
import {
ECompilerType,
ITestContext,
ITestEnv,
TCompilerOptions
} from "../type";
import { Source } from "webpack-sources";
import { Compilation, Compiler } from "@rspack/core";
import { format as prettyFormat, PrettyFormatOptions } from "pretty-format";
import { getSerializers } from "jest-snapshot";
import { TTestContextOptions, TestContext } from "../test/context";
import { ISnapshotProcessorOptions, SnapshotProcessor } from "./snapshot";

const pathSerializer = require("jest-serializer-path");
const normalizePaths = pathSerializer.normalizePaths;
const srcDir = path.resolve(__dirname, "../../tests/fixtures");
const distDir = path.resolve(__dirname, "../../tests/js/hook");

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

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

const testPathSerializer = {
test(val: unknown) {
return typeof val === "string";
},
print(val: string) {
return JSON.stringify(
normalizePaths(
// @ts-ignore
val
.split(srcDir)
.join("<HOOK_SRC_DIR>")
.split(distDir)
.join("<HOOK_DIST_DIR>")
)
);
}
};

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

export class HookCasesContext extends TestContext {
protected promises: Promise<void>[] = [];
protected count: number = 0;
protected snapshots: Record<
string | number,
Array<[string | Buffer, string]>
> = {};
protected snapshotsList: Array<string | number> = [];

constructor(
protected src: string,
protected testName: string,
protected options: TTestContextOptions
) {
super(options);
this.snapped = this.snapped.bind(this);
}

/**
* 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: (...args: unknown[]) => Promise<unknown>, prefix = "") {
// eslint-disable-next-line
let context = this;
return function SNAPPED_HOOK(this: any, ...args: unknown[]) {
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: unknown) => {
context._addSnapshot(o, "output (Promise resolved)", group);
return o;
})
.catch((o: unknown) => {
context._addSnapshot(o, "output (Promise rejected)", group);
return o;
})
.finally(resolve);
}
context._addSnapshot(output, "output", group);
return output;
};
}

/**
* @internal
*/
_addSnapshot(content: unknown, name: string, group: string | number) {
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 as Buffer | string,
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);
}, "");

// @ts-ignore
expect(snapshots).toMatchFileSnapshot(
path.join(this.src, "hooks.snap.txt"),
options
);
}
}

interface IHookProcessorOptions<T extends ECompilerType>
extends ISnapshotProcessorOptions<T> {
Expand Down Expand Up @@ -44,4 +217,9 @@ export class HookTaskProcessor extends SnapshotProcessor<ECompilerType.Rspack> {
compiler.mergeOptions(this.hookOptions.options(context));
}
}

async check(env: ITestEnv, context: HookCasesContext) {
await (context as any).collectSnapshots();
await super.check(env, context);
}
}
20 changes: 12 additions & 8 deletions packages/rspack-test-tools/src/test/simple.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ITestEnv, ITestProcessor } from "../type";
import { ITestContext, ITestEnv, ITestProcessor } from "../type";
import { TestContext } from "./context";

const CONTEXT_MAP: Map<
Expand All @@ -9,14 +9,18 @@ const CONTEXT_MAP: Map<
export function getSimpleProcessorRunner(
src: string,
dist: string,
env: ITestEnv
options: {
env?: () => ITestEnv;
context?: (src: string, dist: string) => ITestContext;
} = {}
) {
const createEnv = options.env || (() => ({ it, beforeEach, afterEach }));
const createContext =
options.context ||
((src: string, dist: string) => new TestContext({ src, dist }));
const key = `src: ${src}, dist: ${dist}`;
if (!CONTEXT_MAP.has(key)) {
const context = new TestContext({
src,
dist
});
const context = createContext(src, dist);
const runner = async function run(name: string, processor: ITestProcessor) {
try {
await processor.beforeAll?.(context);
Expand All @@ -27,8 +31,8 @@ export function getSimpleProcessorRunner(
} catch (e: unknown) {
context.emitError(name, e as Error);
} finally {
await processor.run?.(env, context);
await processor.check?.(env, context);
await processor.run?.(createEnv(), context);
await processor.check?.(createEnv(), context);
await processor.after?.(context);
await processor.afterAll?.(context);
}
Expand Down
7 changes: 7 additions & 0 deletions packages/rspack-test-tools/tests/Hook.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const path = require("path");
const { createHookCase, describeByWalk } = require("../dist");
const source = path.resolve(__dirname, "./fixtures");

describeByWalk(__filename, (name, src, dist) => {
createHookCase(name, src, dist, source);
});
Loading

2 comments on commit 42527c5

@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-29 ec3c0df) Current Change
10000_development-mode + exec 2.8 s ± 43 ms 2.83 s ± 44 ms +1.40 %
10000_development-mode_hmr + exec 690 ms ± 16 ms 699 ms ± 12 ms +1.22 %
10000_production-mode + exec 2.49 s ± 18 ms 2.55 s ± 38 ms +2.44 %
arco-pro_development-mode + exec 2.54 s ± 39 ms 2.7 s ± 86 ms +5.99 %
arco-pro_development-mode_hmr + exec 431 ms ± 2.6 ms 433 ms ± 4.4 ms +0.44 %
arco-pro_development-mode_hmr_intercept-plugin + exec 443 ms ± 2.5 ms 447 ms ± 4.1 ms +0.97 %
arco-pro_development-mode_intercept-plugin + exec 3.25 s ± 72 ms 3.5 s ± 48 ms +7.71 %
arco-pro_production-mode + exec 3.99 s ± 103 ms 4.23 s ± 80 ms +6.06 %
arco-pro_production-mode_intercept-plugin + exec 4.75 s ± 102 ms 5.02 s ± 54 ms +5.63 %
threejs_development-mode_10x + exec 2.08 s ± 23 ms 2.11 s ± 22 ms +1.56 %
threejs_development-mode_10x_hmr + exec 771 ms ± 32 ms 778 ms ± 13 ms +0.96 %
threejs_production-mode_10x + exec 5.26 s ± 54 ms 5.34 s ± 80 ms +1.45 %

Threshold exceeded: ["arco-pro_development-mode + exec","arco-pro_development-mode_intercept-plugin + exec","arco-pro_production-mode + exec","arco-pro_production-mode_intercept-plugin + exec"]

@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 ✅ success
_selftest, ubuntu-latest ✅ success
nx, ubuntu-latest ✅ success
rspress, ubuntu-latest ❌ failure
rsbuild, ubuntu-latest ✅ success
compat, ubuntu-latest ✅ success
examples, ubuntu-latest ✅ success

Please sign in to comment.