Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: hook case creator #6419

Merged
merged 2 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading