@rspack/test-tools 1.2.7-alpha.0 → 1.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -31,7 +31,8 @@ const creator = new creator_1.BasicCaseCreator({
31
31
  compilerType: type_1.ECompilerType.Rspack
32
32
  })
33
33
  ];
34
- }
34
+ },
35
+ concurrent: true
35
36
  });
36
37
  function createBuiltinCase(name, src, dist) {
37
38
  creator.create(name, src, dist);
@@ -20,7 +20,8 @@ function getCreator(target) {
20
20
  configFiles: ["rspack.config.js", "webpack.config.js"]
21
21
  })
22
22
  ],
23
- runner: runner_1.CacheRunnerFactory
23
+ runner: runner_1.CacheRunnerFactory,
24
+ concurrent: true
24
25
  }));
25
26
  }
26
27
  return creators.get(target);
@@ -27,7 +27,8 @@ const creator = new creator_1.BasicCaseCreator({
27
27
  configFiles: ["rspack.config.js", "webpack.config.js"]
28
28
  })
29
29
  ],
30
- runner: runner_1.MultipleRunnerFactory
30
+ runner: runner_1.MultipleRunnerFactory,
31
+ concurrent: 3
31
32
  });
32
33
  function createConfigCase(name, src, dist) {
33
34
  creator.create(name, src, dist);
@@ -19,7 +19,8 @@ const creator = new creator_1.BasicCaseCreator({
19
19
  return output.replace(/(│.* at ).*/g, "$1xxx");
20
20
  }
21
21
  })
22
- ]
22
+ ],
23
+ concurrent: true
23
24
  });
24
25
  function createDiagnosticCase(name, src, dist) {
25
26
  creator.create(name, src, dist);
package/dist/case/diff.js CHANGED
@@ -33,11 +33,10 @@ function createDiffCase(name, src, dist) {
33
33
  steps: [processor]
34
34
  });
35
35
  (0, rimraf_1.rimrafSync)(dist);
36
- const buildTask = tester.compile();
37
36
  const prefix = node_path_1.default.basename(name);
38
37
  describe(`${prefix}:check`, () => {
39
38
  beforeAll(async () => {
40
- await buildTask;
39
+ await tester.compile();
41
40
  compareMap.clear();
42
41
  await tester.check(env);
43
42
  });
@@ -20,7 +20,8 @@ function getCreator(target) {
20
20
  configFiles: ["rspack.config.js", "webpack.config.js"]
21
21
  })
22
22
  ],
23
- runner: runner_1.HotStepRunnerFactory
23
+ runner: runner_1.HotStepRunnerFactory,
24
+ concurrent: true
24
25
  }));
25
26
  }
26
27
  return creators.get(target);
package/dist/case/hot.js CHANGED
@@ -20,7 +20,8 @@ function getCreator(target) {
20
20
  configFiles: ["rspack.config.js", "webpack.config.js"]
21
21
  })
22
22
  ],
23
- runner: runner_1.HotRunnerFactory
23
+ runner: runner_1.HotRunnerFactory,
24
+ concurrent: true
24
25
  }));
25
26
  }
26
27
  return creators.get(target);
@@ -17,3 +17,4 @@ export * from "./watch";
17
17
  export * from "./new-incremental";
18
18
  export * from "./cache";
19
19
  export * from "./new-code-splitting";
20
+ export * from "./serial";
@@ -33,3 +33,4 @@ __exportStar(require("./watch"), exports);
33
33
  __exportStar(require("./new-incremental"), exports);
34
34
  __exportStar(require("./cache"), exports);
35
35
  __exportStar(require("./new-code-splitting"), exports);
36
+ __exportStar(require("./serial"), exports);
@@ -33,7 +33,8 @@ const configCreator = new creator_1.BasicCaseCreator({
33
33
  configFiles: ["rspack.config.js", "webpack.config.js"]
34
34
  });
35
35
  return [processor];
36
- }
36
+ },
37
+ concurrent: 3
37
38
  });
38
39
  class NewCodeSplittingProcessor extends processor_1.ConfigProcessor {
39
40
  constructor(_configOptions) {
@@ -29,7 +29,8 @@ function getHotCreator(target, documentType) {
29
29
  documentType
30
30
  })
31
31
  ],
32
- runner: runner_1.HotRunnerFactory
32
+ runner: runner_1.HotRunnerFactory,
33
+ concurrent: true
33
34
  }));
34
35
  }
35
36
  return hotCreators.get(key);
@@ -76,7 +77,8 @@ const watchCreator = new creator_1.BasicCaseCreator({
76
77
  compilerType: type_1.ECompilerType.Rspack,
77
78
  configFiles: ["rspack.config.js", "webpack.config.js"]
78
79
  }, watchState));
79
- }
80
+ },
81
+ concurrent: true
80
82
  });
81
83
  function createWatchNewIncrementalCase(name, src, dist, temp) {
82
84
  watchCreator.create(name, src, dist, temp);
@@ -21,7 +21,8 @@ const creator = new creator_1.BasicCaseCreator({
21
21
  compilerType: type_1.ECompilerType.Rspack
22
22
  })
23
23
  ],
24
- runner: runner_1.NormalRunnerFactory
24
+ runner: runner_1.NormalRunnerFactory,
25
+ concurrent: true
25
26
  });
26
27
  function createNormalCase(name, src, dist) {
27
28
  creator.create(name, src, dist);
@@ -0,0 +1,3 @@
1
+ import { ECompilerType, type TTestConfig } from "../type";
2
+ export type TSerialCaseConfig = Omit<TTestConfig<ECompilerType.Rspack>, "validate">;
3
+ export declare function createSerialCase(name: string, src: string, dist: string): void;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSerialCase = createSerialCase;
4
+ const config_1 = require("../processor/config");
5
+ const runner_1 = require("../runner");
6
+ const creator_1 = require("../test/creator");
7
+ const type_1 = require("../type");
8
+ const creator = new creator_1.BasicCaseCreator({
9
+ clean: true,
10
+ describe: false,
11
+ testConfig: testConfig => {
12
+ const oldModuleScope = testConfig.moduleScope;
13
+ testConfig.moduleScope = (ms, stats) => {
14
+ let res = ms;
15
+ // TODO: modify runner module scope based on stats here
16
+ if (typeof oldModuleScope === "function") {
17
+ res = oldModuleScope(ms, stats);
18
+ }
19
+ return res;
20
+ };
21
+ },
22
+ steps: ({ name }) => [
23
+ new config_1.ConfigProcessor({
24
+ name,
25
+ runable: true,
26
+ compilerType: type_1.ECompilerType.Rspack,
27
+ configFiles: ["rspack.config.js", "webpack.config.js"]
28
+ })
29
+ ],
30
+ runner: runner_1.MultipleRunnerFactory
31
+ });
32
+ function createSerialCase(name, src, dist) {
33
+ creator.create(name, src, dist);
34
+ }
@@ -45,7 +45,8 @@ const creator = new creator_1.BasicCaseCreator({
45
45
  compilerType: type_1.ECompilerType.Rspack,
46
46
  configFiles: ["rspack.config.js", "webpack.config.js"]
47
47
  }, watchState));
48
- }
48
+ },
49
+ concurrent: true
49
50
  });
50
51
  function createWatchCase(name, src, dist, temp) {
51
52
  creator.create(name, src, dist, temp);
package/dist/compiler.js CHANGED
@@ -65,6 +65,10 @@ class TestCompilerManager {
65
65
  if (!this.compilerInstance)
66
66
  throw new Error("Compiler should be created before watch");
67
67
  this.compilerInstance.watch({
68
+ // IMPORTANT:
69
+ // This is a workaround for the issue that watchpack cannot detect the file change in time
70
+ // so we set the poll to 300ms to make it more sensitive to the file change
71
+ poll: 300,
68
72
  aggregateTimeout: timeout
69
73
  }, (error, newStats) => {
70
74
  this.emitter.emit(ECompilerEvent.Build, error, newStats);
@@ -17,7 +17,7 @@ export declare function toMatchFileSnapshot(this: {
17
17
  unmatched: number;
18
18
  _updateSnapshot: "none" | "new" | "all";
19
19
  };
20
- }, content: string | Buffer, filepath: string, options?: FileMatcherOptions): {
20
+ }, rawContent: string | Buffer, filepath: string, options?: FileMatcherOptions): {
21
21
  pass: boolean;
22
22
  message: () => string;
23
23
  };
@@ -11,6 +11,7 @@ const node_path_1 = __importDefault(require("node:path"));
11
11
  const chalk_1 = __importDefault(require("chalk"));
12
12
  const filenamify_1 = __importDefault(require("filenamify"));
13
13
  const jest_diff_1 = require("jest-diff");
14
+ const { serialize } = require(node_path_1.default.join(node_path_1.default.dirname(require.resolve("jest-snapshot")), "./utils.js"));
14
15
  /**
15
16
  * Check if 2 strings or buffer are equal
16
17
  */
@@ -25,7 +26,10 @@ const isEqual = (a, b) => {
25
26
  * @param filepath Path to the file to match against
26
27
  * @param options Additional options for matching
27
28
  */
28
- function toMatchFileSnapshot(content, filepath, options = {}) {
29
+ function toMatchFileSnapshot(rawContent, filepath, options = {}) {
30
+ const content = Buffer.isBuffer(rawContent) || typeof rawContent === "string"
31
+ ? rawContent
32
+ : serialize(rawContent);
29
33
  const { isNot, snapshotState } = this;
30
34
  const filename = filepath === undefined
31
35
  ? // If file name is not specified, generate one from the test title
@@ -1 +1 @@
1
- export { undefined as step };
1
+ export const step: {};
@@ -1,2 +1,2 @@
1
1
  "use strict";
2
- exports.step = undefined;
2
+ exports.step = {};
@@ -71,6 +71,11 @@ class ConfigProcessor extends multi_1.MultiTaskProcessor {
71
71
  options.output ??= {};
72
72
  options.output.filename = `bundle${index}${outputModule ? ".mjs" : ".js"}`;
73
73
  }
74
+ if (!global.printLogger) {
75
+ options.infrastructureLogging = {
76
+ level: "error"
77
+ };
78
+ }
74
79
  }
75
80
  }
76
81
  exports.ConfigProcessor = ConfigProcessor;
@@ -121,6 +121,11 @@ class HotProcessor extends basic_1.BasicProcessor {
121
121
  options.plugins ??= [];
122
122
  options.plugins.push(new core_1.rspack.LoaderOptionsPlugin(this.updateOptions));
123
123
  }
124
+ if (!global.printLogger) {
125
+ options.infrastructureLogging = {
126
+ level: "error"
127
+ };
128
+ }
124
129
  }
125
130
  }
126
131
  exports.HotProcessor = HotProcessor;
@@ -36,7 +36,8 @@ class WatchProcessor extends multi_1.MultiTaskProcessor {
36
36
  async build(context) {
37
37
  const compiler = this.getCompiler(context);
38
38
  const currentWatchStepModule = require(currentWatchStepModulePath);
39
- currentWatchStepModule.step = this._watchOptions.stepName;
39
+ currentWatchStepModule.step[this._options.name] =
40
+ this._watchOptions.stepName;
40
41
  node_fs_1.default.mkdirSync(this._watchOptions.tempDir, { recursive: true });
41
42
  (0, copyDiff_1.default)(node_path_1.default.join(context.getSource(), this._watchOptions.stepName), this._watchOptions.tempDir, true);
42
43
  const task = new Promise((resolve, reject) => {
@@ -173,7 +174,7 @@ class WatchProcessor extends multi_1.MultiTaskProcessor {
173
174
  if (!options.output.path)
174
175
  options.output.path = context.getDist();
175
176
  if (typeof options.output.pathinfo === "undefined")
176
- options.output.pathinfo = true;
177
+ options.output.pathinfo = false;
177
178
  if (!options.output.filename)
178
179
  options.output.filename = "bundle.js";
179
180
  if (options.cache && options.cache.type === "filesystem") {
@@ -203,6 +204,11 @@ class WatchProcessor extends multi_1.MultiTaskProcessor {
203
204
  options.experiments.rspackFuture ??= {};
204
205
  options.experiments.rspackFuture.bundlerInfo ??= {};
205
206
  options.experiments.rspackFuture.bundlerInfo.force ??= false;
207
+ if (!global.printLogger) {
208
+ options.infrastructureLogging = {
209
+ level: "error"
210
+ };
211
+ }
206
212
  };
207
213
  }
208
214
  static findBundle(index, context, options) {
@@ -226,7 +232,8 @@ class WatchStepProcessor extends WatchProcessor {
226
232
  async build(context) {
227
233
  const compiler = this.getCompiler(context);
228
234
  const currentWatchStepModule = require(currentWatchStepModulePath);
229
- currentWatchStepModule.step = this._watchOptions.stepName;
235
+ currentWatchStepModule.step[this._options.name] =
236
+ this._watchOptions.stepName;
230
237
  const task = new Promise((resolve, reject) => {
231
238
  compiler.getEmitter().once(compiler_1.ECompilerEvent.Build, (e, stats) => {
232
239
  if (e)
@@ -1,6 +1,9 @@
1
1
  import type { ECompilerType } from "../../type";
2
2
  import type { IBasicGlobalContext, IBasicModuleScope, TBasicRunnerFile, TModuleObject, TRunnerRequirer } from "../type";
3
3
  import { BasicRunner } from "./basic";
4
+ declare global {
5
+ var printLogger: boolean;
6
+ }
4
7
  export declare class CommonJsRunner<T extends ECompilerType = ECompilerType.Rspack> extends BasicRunner<T> {
5
8
  protected createGlobalContext(): IBasicGlobalContext;
6
9
  protected createBaseModuleScope(): IBasicModuleScope;
@@ -14,7 +14,42 @@ const define = (...args) => {
14
14
  class CommonJsRunner extends basic_1.BasicRunner {
15
15
  createGlobalContext() {
16
16
  return {
17
- console: console,
17
+ console: {
18
+ log: (...args) => {
19
+ if (printLogger) {
20
+ console.log(...args);
21
+ }
22
+ },
23
+ warn: (...args) => {
24
+ if (printLogger) {
25
+ console.warn(...args);
26
+ }
27
+ },
28
+ error: (...args) => {
29
+ console.error(...args);
30
+ },
31
+ info: (...args) => {
32
+ if (printLogger) {
33
+ console.info(...args);
34
+ }
35
+ },
36
+ debug: (...args) => {
37
+ if (printLogger) {
38
+ console.info(...args);
39
+ }
40
+ },
41
+ trace: (...args) => {
42
+ if (printLogger) {
43
+ console.info(...args);
44
+ }
45
+ },
46
+ assert: (...args) => {
47
+ console.assert(...args);
48
+ },
49
+ clear: () => {
50
+ console.clear();
51
+ }
52
+ },
18
53
  setTimeout: ((cb, ms, ...args) => {
19
54
  const timeout = setTimeout(cb, ms, ...args);
20
55
  timeout.unref();
@@ -34,6 +69,7 @@ class CommonJsRunner extends basic_1.BasicRunner {
34
69
  });
35
70
  return m;
36
71
  },
72
+ __SNAPSHOT__: node_path_1.default.join(this._options.source, "__snapshot__"),
37
73
  ...this._options.env
38
74
  };
39
75
  return baseModuleScope;
@@ -1,10 +1,11 @@
1
1
  import type { ECompilerType } from "../../../type";
2
- import type { TRunnerRequirer } from "../../type";
2
+ import type { TBasicRunnerFile, TRunnerRequirer } from "../../type";
3
3
  import type { IBasicRunnerOptions } from "../basic";
4
4
  import { CommonJsRunner } from "../cjs";
5
5
  export declare class JSDOMWebRunner<T extends ECompilerType = ECompilerType.Rspack> extends CommonJsRunner<T> {
6
6
  protected _webOptions: IBasicRunnerOptions<T>;
7
7
  private dom;
8
+ private requireCache;
8
9
  constructor(_webOptions: IBasicRunnerOptions<T>);
9
10
  run(file: string): Promise<unknown>;
10
11
  getGlobal(name: string): unknown;
@@ -14,6 +15,12 @@ export declare class JSDOMWebRunner<T extends ECompilerType = ECompilerType.Rspa
14
15
  }): any;
15
16
  };
16
17
  protected createBaseModuleScope(): import("../../type").IBasicModuleScope;
18
+ protected getModuleContent(file: TBasicRunnerFile): [
19
+ {
20
+ exports: Record<string, unknown>;
21
+ },
22
+ string
23
+ ];
17
24
  protected createJSDOMRequirer(): TRunnerRequirer;
18
25
  protected createRunner(): void;
19
26
  }
@@ -12,10 +12,13 @@ const EventSourceForNode_1 = __importDefault(require("../../../helper/legacy/Eve
12
12
  const createFakeWorker_1 = __importDefault(require("../../../helper/legacy/createFakeWorker"));
13
13
  const urlToRelativePath_1 = __importDefault(require("../../../helper/legacy/urlToRelativePath"));
14
14
  const cjs_1 = require("../cjs");
15
+ // Compatibility code to suppress iconv-lite warnings
16
+ require("iconv-lite").skipDecodeWarning = true;
15
17
  class JSDOMWebRunner extends cjs_1.CommonJsRunner {
16
18
  constructor(_webOptions) {
17
19
  super(_webOptions);
18
20
  this._webOptions = _webOptions;
21
+ this.requireCache = Object.create(null);
19
22
  const virtualConsole = new jsdom_1.VirtualConsole();
20
23
  virtualConsole.sendTo(console);
21
24
  this.dom = new jsdom_1.JSDOM(`
@@ -74,10 +77,25 @@ class JSDOMWebRunner extends cjs_1.CommonJsRunner {
74
77
  .resolve(this._webOptions.dist, `./${url.startsWith("https://test.cases/path/") ? url.slice(24) : url}`)
75
78
  .split("?")[0];
76
79
  };
80
+ const that = this;
77
81
  class CustomResourceLoader extends jsdom_1.ResourceLoader {
78
82
  fetch(url, _) {
83
+ const filePath = urlToPath(url);
84
+ let finalCode;
85
+ if (node_path_1.default.extname(filePath) === ".js") {
86
+ const currentDirectory = node_path_1.default.dirname(filePath);
87
+ const file = that.getFile(filePath, currentDirectory);
88
+ if (!file) {
89
+ throw new Error(`File not found: ${filePath}`);
90
+ }
91
+ const [_m, code] = that.getModuleContent(file);
92
+ finalCode = code;
93
+ }
94
+ else {
95
+ finalCode = node_fs_1.default.readFileSync(filePath);
96
+ }
79
97
  try {
80
- return Promise.resolve(node_fs_1.default.readFileSync(urlToPath(url)));
98
+ return Promise.resolve(finalCode);
81
99
  }
82
100
  catch (err) {
83
101
  console.error(err);
@@ -135,56 +153,61 @@ class JSDOMWebRunner extends cjs_1.CommonJsRunner {
135
153
  };
136
154
  return moduleScope;
137
155
  }
156
+ getModuleContent(file) {
157
+ const m = {
158
+ exports: {}
159
+ };
160
+ const currentModuleScope = this.createModuleScope(this.getRequire(), m, file);
161
+ if (this._options.testConfig.moduleScope) {
162
+ this._options.testConfig.moduleScope(currentModuleScope);
163
+ }
164
+ const scopeKey = (0, helper_1.escapeSep)(file.path);
165
+ const args = Object.keys(currentModuleScope).filter(arg => !["window", "self", "globalThis", "console"].includes(arg));
166
+ const argValues = args
167
+ .map(arg => `window["${scopeKey}"]["${arg}"]`)
168
+ .join(", ");
169
+ this.dom.window[scopeKey] = currentModuleScope;
170
+ return [
171
+ m,
172
+ `
173
+ // hijack document.currentScript for auto public path
174
+ var $$g$$ = new Proxy(window, {
175
+ get(target, prop, receiver) {
176
+ if (prop === "document") {
177
+ return new Proxy(window.document, {
178
+ get(target, prop, receiver) {
179
+ if (prop === "currentScript") {
180
+ var script = target.createElement("script");
181
+ script.src = "https://test.cases/path/${(0, helper_1.escapeSep)(file.subPath)}index.js";
182
+ return script;
183
+ }
184
+ return Reflect.get(target, prop, receiver);
185
+ }
186
+ });
187
+ }
188
+ return Reflect.get(target, prop, receiver);
189
+ }
190
+ });
191
+ (function(window, self, globalThis, console, ${args.join(", ")}) {
192
+ ${file.content}
193
+ })($$g$$, $$g$$, $$g$$, window["console"], ${argValues});
194
+ `
195
+ ];
196
+ }
138
197
  createJSDOMRequirer() {
139
- const requireCache = Object.create(null);
140
198
  return (currentDirectory, modulePath, context = {}) => {
141
199
  const file = context.file || this.getFile(modulePath, currentDirectory);
142
200
  if (!file) {
143
201
  return this.requirers.get("miss")(currentDirectory, modulePath);
144
202
  }
145
- if (file.path in requireCache) {
146
- return requireCache[file.path].exports;
203
+ if (file.path in this.requireCache) {
204
+ return this.requireCache[file.path].exports;
147
205
  }
148
- const m = {
149
- exports: {}
150
- };
151
- requireCache[file.path] = m;
152
- const currentModuleScope = this.createModuleScope(this.getRequire(), m, file);
153
- if (this._options.testConfig.moduleScope) {
154
- this._options.testConfig.moduleScope(currentModuleScope);
155
- }
156
- const scopeKey = (0, helper_1.escapeSep)(file.path);
157
- const args = Object.keys(currentModuleScope).filter(arg => !["window", "self", "globalThis", "console"].includes(arg));
158
- const argValues = args
159
- .map(arg => `window["${scopeKey}"]["${arg}"]`)
160
- .join(", ");
161
- const code = `
162
- // hijack document.currentScript for auto public path
163
- var $$g$$ = new Proxy(window, {
164
- get(target, prop, receiver) {
165
- if (prop === "document") {
166
- return new Proxy(window.document, {
167
- get(target, prop, receiver) {
168
- if (prop === "currentScript") {
169
- var script = target.createElement("script");
170
- script.src = "https://test.cases/path/${(0, helper_1.escapeSep)(file.subPath)}index.js";
171
- return script;
172
- }
173
- return Reflect.get(target, prop, receiver);
174
- }
175
- });
176
- }
177
- return Reflect.get(target, prop, receiver);
178
- }
179
- });
180
- (function(window, self, globalThis, console, ${args.join(", ")}) {
181
- ${file.content}
182
- })($$g$$, $$g$$, $$g$$, window["console"], ${argValues});
183
- `;
206
+ const [m, code] = this.getModuleContent(file);
184
207
  this.preExecute(code, file);
185
- this.dom.window[scopeKey] = currentModuleScope;
186
208
  this.dom.window.eval(code);
187
209
  this.postExecute(m, file);
210
+ this.requireCache[file.path] = m;
188
211
  return m.exports;
189
212
  };
190
213
  }
@@ -14,12 +14,12 @@ export declare enum EEsmMode {
14
14
  Unlinked = 2
15
15
  }
16
16
  export interface IBasicModuleScope extends ITestEnv {
17
- console: Console;
17
+ console: Record<string, (...args: any[]) => void>;
18
18
  expect: jest.Expect;
19
19
  [key: string]: any;
20
20
  }
21
21
  export interface IBasicGlobalContext {
22
- console: Console;
22
+ console: Record<string, (...args: any[]) => void>;
23
23
  setTimeout: typeof setTimeout;
24
24
  clearTimeout: typeof clearTimeout;
25
25
  [key: string]: any;
@@ -1,4 +1,11 @@
1
1
  import type { ECompilerType, ITestContext, ITestEnv, ITestProcessor, ITester, TRunnerFactory, TTestConfig } from "../type";
2
+ declare global {
3
+ var testFilter: string | undefined;
4
+ }
5
+ interface IConcurrentTestEnv {
6
+ clear: () => void;
7
+ run: () => Promise<void>;
8
+ }
2
9
  export interface IBasicCaseCreatorOptions<T extends ECompilerType> {
3
10
  clean?: boolean;
4
11
  describe?: boolean;
@@ -14,16 +21,26 @@ export interface IBasicCaseCreatorOptions<T extends ECompilerType> {
14
21
  description?: (name: string, step: number) => string;
15
22
  runner?: new (name: string, context: ITestContext) => TRunnerFactory<ECompilerType>;
16
23
  [key: string]: unknown;
24
+ concurrent?: boolean | number;
17
25
  }
18
26
  export declare class BasicCaseCreator<T extends ECompilerType> {
19
27
  protected _options: IBasicCaseCreatorOptions<T>;
28
+ protected currentConcurrent: number;
29
+ protected tasks: [string, () => void][];
20
30
  constructor(_options: IBasicCaseCreatorOptions<T>);
21
31
  create(name: string, src: string, dist: string, temp?: string): ITester | undefined;
32
+ protected shouldRun(name: string): boolean;
33
+ protected describeConcurrent(name: string, tester: ITester, testConfig: TTestConfig<T>): void;
22
34
  protected describe(name: string, tester: ITester, testConfig: TTestConfig<T>): void;
35
+ protected createConcurrentEnv(): ITestEnv & IConcurrentTestEnv;
23
36
  protected createEnv(testConfig: TTestConfig<T>): ITestEnv;
24
37
  protected clean(folders: string[]): void;
25
38
  protected skip(name: string, reason: string | boolean): void;
26
39
  protected readTestConfig(src: string): TTestConfig<T>;
27
40
  protected checkSkipped(src: string, testConfig: TTestConfig<T>): boolean | string;
28
41
  protected createTester(name: string, src: string, dist: string, temp: string | void, testConfig: TTestConfig<T>): ITester;
42
+ protected tryRunTask(): void;
43
+ protected getMaxConcurrent(): number;
44
+ protected registerConcurrentTask(name: string, starter: () => void): () => void;
29
45
  }
46
+ export {};
@@ -9,9 +9,12 @@ const node_path_1 = __importDefault(require("node:path"));
9
9
  const rimraf_1 = require("rimraf");
10
10
  const createLazyTestEnv_1 = __importDefault(require("../helper/legacy/createLazyTestEnv"));
11
11
  const tester_1 = require("./tester");
12
+ const DEFAULT_MAX_CONCURRENT = 5;
12
13
  class BasicCaseCreator {
13
14
  constructor(_options) {
14
15
  this._options = _options;
16
+ this.currentConcurrent = 0;
17
+ this.tasks = [];
15
18
  }
16
19
  create(name, src, dist, temp) {
17
20
  const testConfig = this.readTestConfig(src);
@@ -26,24 +29,113 @@ class BasicCaseCreator {
26
29
  if (this._options.clean) {
27
30
  this.clean([dist, temp || ""].filter(Boolean));
28
31
  }
32
+ const run = this.shouldRun(name);
29
33
  const tester = this.createTester(name, src, dist, temp, testConfig);
34
+ const concurrent = testConfig.concurrent ?? this._options.concurrent ?? false;
30
35
  if (this._options.describe) {
31
- describe(name, () => this.describe(name, tester, testConfig));
36
+ if (run) {
37
+ if (concurrent) {
38
+ describe(name, () => this.describeConcurrent(name, tester, testConfig));
39
+ }
40
+ else {
41
+ describe(name, () => this.describe(name, tester, testConfig));
42
+ }
43
+ }
44
+ else {
45
+ describe.skip(name, () => {
46
+ it.skip("skipped", () => { });
47
+ });
48
+ }
32
49
  }
33
50
  else {
34
- this.describe(name, tester, testConfig);
51
+ if (run) {
52
+ if (concurrent) {
53
+ this.describeConcurrent(name, tester, testConfig);
54
+ }
55
+ else {
56
+ this.describe(name, tester, testConfig);
57
+ }
58
+ }
59
+ else {
60
+ it.skip("skipped", () => { });
61
+ }
35
62
  }
36
63
  return tester;
37
64
  }
65
+ shouldRun(name) {
66
+ // TODO: more flexible filter
67
+ if (typeof global.testFilter !== "string" || !global.testFilter) {
68
+ return true;
69
+ }
70
+ return name.includes(global.testFilter);
71
+ }
72
+ describeConcurrent(name, tester, testConfig) {
73
+ beforeAll(async () => {
74
+ await tester.prepare();
75
+ });
76
+ let starter = null;
77
+ let chain = new Promise((resolve, reject) => {
78
+ starter = resolve;
79
+ });
80
+ const ender = this.registerConcurrentTask(name, starter);
81
+ const env = this.createConcurrentEnv();
82
+ let bailout = false;
83
+ for (let index = 0; index < tester.total; index++) {
84
+ let stepSignalResolve = null;
85
+ let stepSignalReject = null;
86
+ const stepSignal = new Promise((resolve, reject) => {
87
+ stepSignalResolve = resolve;
88
+ stepSignalReject = reject;
89
+ });
90
+ const description = typeof this._options.description === "function"
91
+ ? this._options.description(name, index)
92
+ : index
93
+ ? `step [${index}] should pass`
94
+ : "should pass";
95
+ it(description, async () => {
96
+ await stepSignal;
97
+ }, this._options.timeout || 180000);
98
+ chain = chain.then(async () => {
99
+ try {
100
+ if (bailout) {
101
+ throw `Case "${name}" step ${index + 1} bailout because ${tester.step + 1} failed`;
102
+ }
103
+ env.clear();
104
+ await tester.compile();
105
+ await tester.check(env);
106
+ await env.run();
107
+ const context = tester.getContext();
108
+ if (!tester.next() && context.hasError()) {
109
+ bailout = true;
110
+ const errors = context
111
+ .getError()
112
+ .map(i => `${i.stack}`.split("\n").join("\t\n"))
113
+ .join("\n\n");
114
+ throw new Error(`Case "${name}" failed at step ${tester.step + 1}:\n${errors}`);
115
+ }
116
+ stepSignalResolve();
117
+ }
118
+ catch (e) {
119
+ stepSignalReject(e);
120
+ }
121
+ });
122
+ }
123
+ chain.finally(() => {
124
+ ender();
125
+ });
126
+ afterAll(async () => {
127
+ await tester.resume();
128
+ });
129
+ }
38
130
  describe(name, tester, testConfig) {
39
131
  beforeAll(async () => {
40
132
  await tester.prepare();
41
133
  });
134
+ let bailout = false;
42
135
  for (let index = 0; index < tester.total; index++) {
43
136
  const description = typeof this._options.description === "function"
44
137
  ? this._options.description(name, index)
45
138
  : `step ${index ? `[${index}]` : ""} should pass`;
46
- let bailout = false;
47
139
  it(description, async () => {
48
140
  if (bailout) {
49
141
  throw `Case "${name}" step ${index + 1} bailout because ${tester.step + 1} failed`;
@@ -66,6 +158,69 @@ class BasicCaseCreator {
66
158
  await tester.resume();
67
159
  });
68
160
  }
161
+ createConcurrentEnv() {
162
+ const tasks = [];
163
+ const beforeTasks = [];
164
+ const afterTasks = [];
165
+ return {
166
+ clear: () => {
167
+ tasks.length = 0;
168
+ beforeTasks.length = 0;
169
+ afterTasks.length = 0;
170
+ },
171
+ run: async () => {
172
+ const runFn = async (fn) => {
173
+ if (fn.length) {
174
+ await new Promise((resolve, reject) => {
175
+ fn(e => {
176
+ if (e) {
177
+ reject(e);
178
+ }
179
+ else {
180
+ resolve();
181
+ }
182
+ });
183
+ });
184
+ }
185
+ else {
186
+ const res = fn();
187
+ if (typeof res?.then === "function") {
188
+ await res;
189
+ }
190
+ }
191
+ };
192
+ for (const [description, fn] of tasks) {
193
+ for (const before of beforeTasks) {
194
+ await runFn(before);
195
+ }
196
+ try {
197
+ await runFn(fn);
198
+ }
199
+ catch (e) {
200
+ throw new Error(`Error: ${description} failed\n${e.stack}`);
201
+ }
202
+ for (const after of afterTasks) {
203
+ await runFn(after);
204
+ }
205
+ }
206
+ },
207
+ expect,
208
+ it: (description, fn) => {
209
+ expect(typeof description === "string");
210
+ expect(typeof fn === "function");
211
+ tasks.push([description, fn]);
212
+ },
213
+ beforeEach: (fn) => {
214
+ expect(typeof fn === "function");
215
+ beforeTasks.push(fn);
216
+ },
217
+ afterEach: (fn) => {
218
+ expect(typeof fn === "function");
219
+ afterTasks.push(fn);
220
+ },
221
+ jest
222
+ };
223
+ }
69
224
  createEnv(testConfig) {
70
225
  if (typeof this._options.runner === "function" && !testConfig.noTest) {
71
226
  return (0, createLazyTestEnv_1.default)(10000);
@@ -115,5 +270,26 @@ class BasicCaseCreator {
115
270
  })
116
271
  });
117
272
  }
273
+ tryRunTask() {
274
+ while (this.tasks.length !== 0 &&
275
+ this.currentConcurrent < this.getMaxConcurrent()) {
276
+ const [_name, starter] = this.tasks.shift();
277
+ this.currentConcurrent++;
278
+ starter();
279
+ }
280
+ }
281
+ getMaxConcurrent() {
282
+ return typeof this._options.concurrent === "number"
283
+ ? this._options.concurrent
284
+ : DEFAULT_MAX_CONCURRENT;
285
+ }
286
+ registerConcurrentTask(name, starter) {
287
+ this.tasks.push([name, starter]);
288
+ this.tryRunTask();
289
+ return () => {
290
+ this.currentConcurrent--;
291
+ this.tryRunTask();
292
+ };
293
+ }
118
294
  }
119
295
  exports.BasicCaseCreator = BasicCaseCreator;
package/dist/type.d.ts CHANGED
@@ -141,12 +141,13 @@ export type TTestConfig<T extends ECompilerType> = {
141
141
  beforeExecute?: () => void;
142
142
  afterExecute?: () => void;
143
143
  moduleScope?: (ms: IBasicModuleScope, stats?: TCompilerStatsCompilation<T>) => IBasicModuleScope;
144
- checkStats?: (stepName: string, jsonStats: TCompilerStatsCompilation<T>, stringStats: String) => boolean;
144
+ checkStats?: (stepName: string, jsonStats: TCompilerStatsCompilation<T> | undefined, stringStats: String) => boolean;
145
145
  findBundle?: (index: number, options: TCompilerOptions<T>, stepName?: string) => string | string[];
146
146
  bundlePath?: string[];
147
147
  nonEsmThis?: (p: string | string[]) => Object;
148
148
  modules?: Record<string, Object>;
149
149
  timeout?: number;
150
+ concurrent?: boolean;
150
151
  };
151
152
  export type TTestFilter<T extends ECompilerType> = (creatorConfig: Record<string, unknown>, testConfig: TTestConfig<T>) => boolean | string;
152
153
  export interface ITestRunner {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rspack/test-tools",
3
- "version": "1.2.7-alpha.0",
3
+ "version": "1.2.8",
4
4
  "license": "MIT",
5
5
  "description": "Test tools for rspack",
6
6
  "main": "dist/index.js",
@@ -34,7 +34,7 @@
34
34
  "@babel/generator": "7.26.9",
35
35
  "@babel/helpers": "7.26.9",
36
36
  "@babel/parser": "7.26.9",
37
- "@babel/template": "7.25.9",
37
+ "@babel/template": "7.26.9",
38
38
  "@babel/traverse": "7.26.9",
39
39
  "@babel/types": "7.26.9",
40
40
  "cross-env": "^7.0.3",
@@ -53,7 +53,7 @@
53
53
  "pretty-format": "29.7.0",
54
54
  "rimraf": "^5.0.10",
55
55
  "source-map": "^0.7.4",
56
- "terser-webpack-plugin": "^5.3.11",
56
+ "terser-webpack-plugin": "^5.3.13",
57
57
  "webpack": "5.98.0",
58
58
  "webpack-merge": "5.9.0",
59
59
  "webpack-sources": "3.2.3"
@@ -74,17 +74,17 @@
74
74
  "@types/webpack": "5.28.5",
75
75
  "@types/webpack-sources": "3.2.3",
76
76
  "@webdiscus/pug-loader": "^2.11.1",
77
- "acorn": "^8.14.0",
78
- "babel-loader": "^9.2.1",
77
+ "acorn": "^8.14.1",
78
+ "babel-loader": "^10.0.0",
79
79
  "babel-plugin-import": "^1.13.8",
80
80
  "chalk": "^4.1.2",
81
81
  "coffee-loader": "^5.0.0",
82
82
  "coffeescript": "^2.7.0",
83
- "core-js": "3.40.0",
83
+ "core-js": "3.41.0",
84
84
  "css-loader": "^7.1.2",
85
85
  "file-loader": "^6.2.0",
86
86
  "graceful-fs": "^4.2.11",
87
- "html-loader": "^5.0.0",
87
+ "html-loader": "^5.1.0",
88
88
  "html-webpack-plugin": "^5.6.3",
89
89
  "less-loader": "^12.2.0",
90
90
  "lodash": "^4.17.21",
@@ -106,9 +106,9 @@
106
106
  "typescript": "^5.7.3",
107
107
  "wast-loader": "^1.14.1",
108
108
  "worker-rspack-loader": "^3.1.2",
109
- "@rspack/cli": "1.2.7-alpha.0",
110
- "@rspack/core": "1.2.7-alpha.0",
111
- "@rspack/test-tools": "1.2.7-alpha.0"
109
+ "@rspack/cli": "1.2.8",
110
+ "@rspack/core": "1.2.8",
111
+ "@rspack/test-tools": "1.2.8"
112
112
  },
113
113
  "peerDependencies": {
114
114
  "@rspack/core": ">=1.0.0"
@@ -120,9 +120,9 @@
120
120
  "dev": "tsc -b -w",
121
121
  "test": "pnpm run --stream /^test:.*/",
122
122
  "testu": "pnpm run --stream /^test:.*/ -u",
123
- "test:base": "cross-env NO_COLOR=1 node --expose-gc --max-old-space-size=8192 --experimental-vm-modules ../../node_modules/jest-cli/bin/jest --logHeapUsage --colors --config ./jest.config.js --passWithNoTests",
124
- "test:hot": "cross-env RSPACK_HOT_TEST=true NO_COLOR=1 node --expose-gc --max-old-space-size=8192 --experimental-vm-modules ../../node_modules/jest-cli/bin/jest --logHeapUsage --colors --config ./jest.config.hot.js --passWithNoTests",
125
- "test:diff": "cross-env RSPACK_DIFF=true NO_COLOR=1 node --expose-gc --max-old-space-size=8192 --experimental-vm-modules ../../node_modules/jest-cli/bin/jest --logHeapUsage --colors --config ./jest.config.diff.js --passWithNoTests",
123
+ "test:base": "cross-env node --no-warnings --expose-gc --max-old-space-size=8192 --experimental-vm-modules ../../node_modules/jest-cli/bin/jest --logHeapUsage --colors --config ./jest.config.js --passWithNoTests",
124
+ "test:hot": "cross-env RSPACK_HOT_TEST=true node --no-warnings --expose-gc --max-old-space-size=8192 --experimental-vm-modules ../../node_modules/jest-cli/bin/jest --logHeapUsage --colors --config ./jest.config.hot.js --passWithNoTests",
125
+ "test:diff": "cross-env RSPACK_DIFF=true node --no-warnings --expose-gc --max-old-space-size=8192 --experimental-vm-modules ../../node_modules/jest-cli/bin/jest --logHeapUsage --colors --config ./jest.config.diff.js --passWithNoTests",
126
126
  "api-extractor": "api-extractor run --verbose",
127
127
  "api-extractor:ci": "api-extractor run --verbose || diff temp/test-tools.api.md etc/test-tools.api.md"
128
128
  }