Skip to content

Commit

Permalink
chore: hot step test (#6182)
Browse files Browse the repository at this point in the history
* chore: hot step test

* chore: add hot step snapshot

* chore: add hot step snapshot

* chore: add hot step snapshot

* chore: add hot step snapshot

* chore: add hot step snapshot

* chore: add hot step snapshot

* chore: add hot step snapshot

* chore: add hot step snapshot

* fix: format error

* chore: add hot step snapshot

* chore: add hot step snapshot

* chore: add hot step snapshot

* chore: add hot step snapshot
  • Loading branch information
LingyuCoder authored Apr 15, 2024
1 parent b8ae65b commit 5b4cc6d
Show file tree
Hide file tree
Showing 150 changed files with 7,468 additions and 2 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/test-ng.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ jobs:

- name: Test new runner
run: |
OUTPUT=$(pnpm run test:ng --no-colors --silent=true 2>&1)
set -e;
OUTPUT=$((pnpm run test:ng --no-colors --silent=true 2>&1 || true) | tail --bytes=50000)
echo 'RESULT<<EOF' >> $GITHUB_ENV
echo "$OUTPUT" >> $GITHUB_ENV
echo 'EOF' >> $GITHUB_ENV
Expand Down
42 changes: 42 additions & 0 deletions packages/rspack-test-tools/src/case/hot-step.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { RspackHotStepProcessor } from "../processor/hot-step";
import { ECompilerType, TCompilerOptions } from "../type";
import { BasicCaseCreator } from "../test/creator";
import { HotStepRunnerFactory } from "../runner";

type TTarget = TCompilerOptions<ECompilerType.Rspack>["target"];

const creators: Map<
TTarget,
BasicCaseCreator<ECompilerType.Rspack>
> = new Map();

function getCreator(target: TTarget) {
if (!creators.has(target)) {
creators.set(
target,
new BasicCaseCreator({
clean: true,
describe: true,
target,
steps: ({ name, target }) => [
new RspackHotStepProcessor({
name,
target: target as TTarget
})
],
runner: HotStepRunnerFactory
})
);
}
return creators.get(target)!;
}

export function createHotStepCase(
name: string,
src: string,
dist: string,
target: TCompilerOptions<ECompilerType.Rspack>["target"]
) {
const creator = getCreator(target);
creator.create(name, src, dist);
}
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 @@ -9,3 +9,4 @@ export * from "./watch";
export * from "./treeshaking";
export * from "./defaults";
export * from "./builtin";
export * from "./hot-step";
282 changes: 282 additions & 0 deletions packages/rspack-test-tools/src/processor/hot-step.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
import {
ECompilerType,
ITestContext,
ITestEnv,
TCompilerOptions,
TCompilerStats
} from "../type";
import path from "path";
import { StatsCompilation } from "@rspack/core";
import {
IRspackHotProcessorOptions,
RspackHotProcessor,
TUpdateOptions
} from "./hot";
import fs from "fs-extra";

const escapeLocalName = (str: string) => str.split(/[-<>:"/|?*.]/).join("_");

declare var global: {
self?: {
[key: string]: (name: string, modules: Record<string, unknown>) => void;
};
updateSnapshot: boolean;
};

const SELF_HANDLER = (
file: string,
options: TCompilerOptions<ECompilerType.Rspack>
): string[] => {
let res: string[] = [];
const hotUpdateGlobal = (_: string, modules: Record<string, unknown>) => {
res = Object.keys(modules);
};
const hotUpdateGlobalKey = escapeLocalName(
`${options.output?.hotUpdateGlobal || "webpackHotUpdate"}${
options.output?.uniqueName || ""
}`
);
global["self"] ??= {};
global["self"][hotUpdateGlobalKey] = hotUpdateGlobal;
require(file);
delete global["self"][hotUpdateGlobalKey];
if (!Object.keys(global["self"]).length) {
delete global["self"];
}
return res;
};

const GET_MODULE_HANDLER = {
web: SELF_HANDLER,
"async-node": (file: string): string[] => {
return Object.keys(require(file).modules) || [];
},
webworker: SELF_HANDLER
};

type TSupportTarget = keyof typeof GET_MODULE_HANDLER;

export interface IRspackHotStepProcessorOptions
extends IRspackHotProcessorOptions {}

export class RspackHotStepProcessor extends RspackHotProcessor {
private hashes: string[] = [];
private entries: Record<string, string[]> = {};

constructor(protected _hotOptions: IRspackHotProcessorOptions) {
super(_hotOptions);
}

async run(env: ITestEnv, context: ITestContext) {
context.setValue(
this._options.name,
"hotUpdateStepChecker",
(
hotUpdateContext: TUpdateOptions,
stats: TCompilerStats<ECompilerType.Rspack>
) => {
const statsJson = stats.toJson({ assets: true, chunks: true });
for (let entry of (stats?.compilation.chunks || []).filter(i =>
i.hasRuntime()
)) {
if (!this.entries[entry.id!]) {
this.entries[entry.id!] = entry.runtime!;
}
}
this.matchStepSnapshot(
context,
hotUpdateContext.updateIndex,
statsJson
);
this.hashes.push(stats.hash!);
}
);
context.setValue(
this._options.name,
"hotUpdateStepErrorChecker",
(_: TUpdateOptions, stats: TCompilerStats<ECompilerType.Rspack>) => {
this.hashes.push(stats.hash!);
}
);
await super.run(env, context);
}

async check(env: ITestEnv, context: ITestContext) {
const compiler = this.getCompiler(context);
const stats = compiler.getStats();
if (!stats || !stats.hash) {
expect(false);
return;
}
const statsJson = stats.toJson({ assets: true, chunks: true });
for (let entry of (stats?.compilation.chunks || []).filter(i =>
i.hasRuntime()
)) {
this.entries[entry.id!] = entry.runtime!;
}
this.matchStepSnapshot(context, 0, statsJson);
this.hashes.push(stats.hash!);
await super.check(env, context);
}

protected matchStepSnapshot(
context: ITestContext,
step: number,
stats: StatsCompilation
) {
const compiler = this.getCompiler(context);
const compilerOptions = compiler.getOptions();
const getModuleHandler =
GET_MODULE_HANDLER[compilerOptions.target as TSupportTarget];
expect(typeof getModuleHandler).toBe("function");

const lastHash = this.hashes[this.hashes.length - 1];
const snapshotPath = context.getSource(
`snapshot/${compilerOptions.target}/${step}.snap.txt`
);
const title = `Case ${this._options.name}: Step ${step}`;
const hotUpdateFile: Array<{
name: string;
content: string;
modules: string[];
runtime: string[];
}> = [];
const hotUpdateManifest: Array<{ name: string; content: string }> = [];
const changedFiles: string[] = require(
context.getSource("changed-file.js")
).map((i: string) => path.relative(context.getSource(), i));

const hashes: Record<string, string> = {
[lastHash || "LAST_HASH"]: "LAST_HASH",
[stats.hash!]: "CURRENT_HASH"
};

// TODO: find a better way
// replace [runtime] to [runtime of id] to prevent worker hash
const runtimes: Record<string, string> = {};
for (let [id, runtime] of Object.entries(this.entries)) {
for (let r of runtime) {
if (r !== id) {
runtimes[r] = `[runtime of ${id}]`;
}
}
}

const replaceContent = (str: string) => {
for (let [raw, replacement] of Object.entries(hashes)) {
str = str.split(raw).join(replacement);
}
return str;
};

const replaceFileName = (str: string) => {
for (let [raw, replacement] of Object.entries({
...hashes,
...runtimes
})) {
str = str.split(raw).join(replacement);
}
return str;
};

const fileList = stats
.assets!.map(i => {
const fileName = i.name;
const renderName = replaceFileName(fileName);
const content = replaceContent(
fs.readFileSync(context.getDist(fileName), "utf-8")
);
if (fileName.endsWith("hot-update.js")) {
const modules = getModuleHandler(
context.getDist(fileName),
compilerOptions
);
const runtime: string[] = [];
for (let i of content.matchAll(
/\/\/ (webpack\/runtime\/[\w_-]+)\s*\n/g
)) {
runtime.push(i[1]);
}
modules.sort();
runtime.sort();
hotUpdateFile.push({
name: renderName,
content,
modules,
runtime
});
return `- Update: ${renderName}, size: ${i.size}`;
} else if (fileName.endsWith("hot-update.json")) {
hotUpdateManifest.push({
name: renderName,
content
});
return `- Manifest: ${renderName}, size: ${i.size}`;
} else if (fileName.endsWith(".js")) {
return `- Bundle: ${renderName}, size: ${i.size}`;
}
})
.filter(Boolean);

fileList.sort();
hotUpdateManifest.sort();
hotUpdateFile.sort();

let content = `
# ${title}
## Changed Files
${changedFiles.map(i => `- ${i}`).join("\n")}
## Asset Files
${fileList.join("\n")}
## Manifest
${hotUpdateManifest
.map(
i => `
### ${i.name}
\`\`\`json
${i.content}
\`\`\`
`
)
.join("\n\n")}
## Update
${hotUpdateFile
.map(
i => `
### ${i.name}
#### Changed Modules
${i.modules.map(i => `- ${i}`).join("\n")}
#### Changed Runtime Modules
${i.runtime.map(i => `- ${i}`).join("\n")}
#### Changed Content
\`\`\`js
${i.content}
\`\`\`
`
)
.join("\n\n")}
`.trim();

if (!fs.existsSync(snapshotPath) || global.updateSnapshot) {
fs.ensureDirSync(path.dirname(snapshotPath));
fs.writeFileSync(snapshotPath, content, "utf-8");
return;
}
const snapshotContent = fs
.readFileSync(snapshotPath, "utf-8")
.replace(/\r\n/g, "\n")
.trim();
expect(content).toBe(snapshotContent);
}
}
2 changes: 1 addition & 1 deletion packages/rspack-test-tools/src/processor/hot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export interface IRspackHotProcessorOptions {
target: TCompilerOptions<ECompilerType.Rspack>["target"];
}

type TUpdateOptions = {
export type TUpdateOptions = {
updateIndex: number;
};

Expand Down
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 @@ -12,3 +12,4 @@ export * from "./defaults";
export * from "./stats-api";
export * from "./snapshot";
export * from "./builtin";
export * from "./hot-step";
Loading

2 comments on commit 5b4cc6d

@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

task failure

@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 βœ… success
rsbuild, ubuntu-latest βœ… success
compat, ubuntu-latest βœ… success
examples, ubuntu-latest βœ… success

Please sign in to comment.