diff --git a/packages/rspack-test-tools/etc/api.md b/packages/rspack-test-tools/etc/api.md index 433b52fcee8..362cb398e5a 100644 --- a/packages/rspack-test-tools/etc/api.md +++ b/packages/rspack-test-tools/etc/api.md @@ -643,8 +643,6 @@ export interface IStatsAPITaskProcessorOptions { // (undocumented) compilerType: T; // (undocumented) - cwd?: string; - // (undocumented) name: string; // (undocumented) options?: (context: ITestContext) => TCompilerOptions; diff --git a/packages/rspack-test-tools/src/processor/stats-api.ts b/packages/rspack-test-tools/src/processor/stats-api.ts index 80e13cdb782..eff1096b468 100644 --- a/packages/rspack-test-tools/src/processor/stats-api.ts +++ b/packages/rspack-test-tools/src/processor/stats-api.ts @@ -15,7 +15,6 @@ const serializer = require("jest-serializer-path"); export interface IStatsAPITaskProcessorOptions { options?: (context: ITestContext) => TCompilerOptions; name: string; - cwd?: string; compilerType: T; compiler?: (context: ITestContext, compiler: TCompiler) => Promise; build?: (context: ITestContext, compiler: TCompiler) => Promise; diff --git a/webpack-test/README.md b/webpack-test/README.md index 365c90d0619..3dae78ab236 100644 --- a/webpack-test/README.md +++ b/webpack-test/README.md @@ -1,46 +1,54 @@ ->**Note** +>**Note** > This package is heavily based on [webpack/test](https://github.com/webpack/webpack/tree/main/test) -# Progressively migrate webpack test +# Progressively migrate webpack test -Originally, we use this formula to calculate the compatibility `passedTestCaseCount / totalTestCount`, totalTestCount = passedTestCaseCount + failedTestCount + skippedTestCount , but sometimes it maybe hard to compatible with all webpack test cases for some reasons (e.g. performance, legacy feature that we don't want to support), we need a method to skip these tests that we will not support. Thus, we adjust the original formula to `(passedTestCaseCount + willNotSupportTestCount) / totalTestCount`. +Originally, we use this formula to calculate the compatibility `passedTestCaseCount / totalTestCount`, totalTestCount = passedTestCaseCount + failedTestCount + skippedTestCount , but sometimes it maybe hard to compatible with all webpack test cases for some reasons (e.g. performance, legacy feature that we don't want to support), we need a method to skip these tests that we will not support. Thus, we adjust the original formula to `(passedTestCaseCount + willNotSupportTestCount) / totalTestCount`. -Currently, we use a `test.filter.js` under each failed test case directory to skip failed test case, using this method could let us migrate webpack test case progressively without affect the real compatibility (Because this method will not influence the real `passedTestCaseCount`). +Currently, we use a `test.filter.js` under each failed test case directory to skip failed test case, using this method could let us migrate webpack test case progressively without affect the real compatibility (Because this method will not influence the real `passedTestCaseCount`). e.g. + ```js -// test.fitler.js +// test.filter.js module.exports = () => { return false; // false means this testcase is skipped for now, but maybe we will support in the future, `-1` means this test case we don't want to compatible with, this related to `willNotSupportTest`. } ``` -When you find that we have passed some failed testcases which is skipped for now, you could change the `test.filter.js` to + +When you find that we have passed some failed testcases which is skipped for now, you could change the `test.filter.js` to + ```js module.exports = () => { return true } ``` + or delete the `test.filter.js` +# Welcome to the webpack test suite -# Welcome to the webpack test suite!!!! Every pull request that you submit to webpack (besides README and spelling corrections in comments) requires tests that are created. But don't give up hope!!! Although our tests may appear complex and overwhelming, once you become familiar with the test suite and structure, adding and creating tests will be fun and beneficial as you work inside the codebase! ❤ ## tl;dr + Run all tests (this automatically runs the setup): + ```sh pnpm test ``` Run an individual suite: + ```sh pnpm jest ConfigTestCases ``` Watch mode: + ```sh pnpm jest --watch ConfigTestCases ``` @@ -48,16 +56,20 @@ pnpm jest --watch ConfigTestCases See also: [Jest CLI docs](https://jestjs.io/docs/cli) ## Test suite overview + We use Jest for our tests. For more information on Jest you can visit their [homepage](https://jestjs.io/)! ### Class Tests + All test files can be found in *.test.js. There are many tests that simply test APIs of a specific class/file (such as `Compiler`, `Errors`, Integration, `Parser`, `RuleSet`, Validation). If the feature you are contributing involves one of those classes, then best to start there to understand the structure. ### xCases + In addition to Class specific tests, there are also directories that end in "Cases". The suites for these cases also have corresponding *.test.js files. #### cases (`TestCases.test.js`) 1 + Cases are a set of general purpose tests that will run against a variety of permutations of webpack configurations. When you are making a general purpose change that doesn't require you to have a special configuration, you would likely add your tests here. Inside of the `./test/cases` directory you will find tests are broken into thematic sub directories. Take a moment to explore the different options. To add a new case, create a new directory inside of the top level test groups, and then add an `index.js` file (and any other supporting files). @@ -65,11 +77,13 @@ To add a new case, create a new directory inside of the top level test groups, a By default this file will be the entry point for the test suite and you can add your `it()`'s there. This will also become bundled so that node env support happens as well. #### configCases (`ConfigTestCases.basictest.js`) 1 + If you are trying to solve a bug which is reproducible when x and y properties are used together in a config, then configCases is the place to be!!!! In addition to an `index.js`, these configCases require a `webpack.config.js` is located inside of your test suite. This will run this specific config through `webpack` just as you were building individually. They will use the same loading/bundling technique of your `it()` tests, however you now have a more specific config use cases that you can write even before you start coding. #### statsCases (`StatsTestCases.basictest.js`) + Stats cases are similar to configCases except specifically focusing on the `expected` output of your stats. Instead of writing to the console, however the output of stats will be written to disk. By default, the "expected" outcome is a pain to write by hand so instead when statsCases are run, runner is checking output using jest's awesome snapshot functionality. @@ -83,6 +97,7 @@ Please follow the approach described below: ```javascript import("./someModule"); ``` + * don't forget the `webpack.config.js` * run the test * jest will automatically add the output from your test code to `StatsTestCases.test.js.snap` and you can always check your results there @@ -91,16 +106,16 @@ import("./someModule"); You can read more about SnapShot testing [right here](https://jestjs.io/docs/snapshot-testing) ## Questions? Comments? -If you are still nervous or don't quite understand, please submit an issue and tag us in it, and provide a relevant PR while working on! +If you are still nervous or don't quite understand, please submit an issue and tag us in it, and provide a relevant PR while working on! ## Footnotes -1 webpack's parser supports the use of ES2015 features like arrow functions, harmony exports, etc. However as a library we follow Node.js' timeline for dropping older versions of node. Because of this we expect your tests on GitHub Actions to pass all the way back to NodeJS v10; Therefore if you would like specific tests that use these features to be ignored if they are not supported, then you should add a `test.filter.js` file. This allows you to import the syntax needed for that test, meanwhile ignoring it on node versions (during CI) that don't support it. webpack has a variety of helpful examples you can refer to if you are just starting out. See the `./helpers` folder to find a list of the versions. +1 webpack's parser supports the use of ES2015 features like arrow functions, harmony exports, etc. However as a library we follow Node.js' timeline for dropping older versions of node. Because of this we expect your tests on GitHub Actions to pass all the way back to NodeJS v10; Therefore if you would like specific tests that use these features to be ignored if they are not supported, then you should add a `test.filter.js` file. This allows you to import the syntax needed for that test, meanwhile ignoring it on node versions (during CI) that don't support it. webpack has a variety of helpful examples you can refer to if you are just starting out. See the `./helpers` folder to find a list of the versions. ## Credits Thanks to: -- [The webpack team and community](https://webpack.js.org/) for creating a great bundler and ecosystem from which we draw a lot of inspiration. -- [@sokra](https://github.com/sokra) for the great work on the [webpack](https://github.com/webpack/webpack) project. +* [The webpack team and community](https://webpack.js.org/) for creating a great bundler and ecosystem from which we draw a lot of inspiration. +* [@sokra](https://github.com/sokra) for the great work on the [webpack](https://github.com/webpack/webpack) project. diff --git a/website/docs/en/contribute/development/_meta.json b/website/docs/en/contribute/development/_meta.json index 148d67a6f17..73c545f9117 100644 --- a/website/docs/en/contribute/development/_meta.json +++ b/website/docs/en/contribute/development/_meta.json @@ -1 +1,10 @@ -["prerequisites", "building", "testing", "debugging", "profiling", "releasing"] +[ + "prerequisites", + "building", + "testing", + "testing-webpack", + "testing-rspack", + "debugging", + "profiling", + "releasing" +] diff --git a/website/docs/en/contribute/development/testing-rspack.mdx b/website/docs/en/contribute/development/testing-rspack.mdx new file mode 100644 index 00000000000..a7daaac9078 --- /dev/null +++ b/website/docs/en/contribute/development/testing-rspack.mdx @@ -0,0 +1,405 @@ +# Testing Rspack + +Rspack's test cases include the following: + +- Rspack core test cases are stored in the `packages/rspack-test-tools/tests` folder and will run the test cases by simulating the build process. In general, test cases should be added in this folder. +- Test cases for other Rspack packages are stored in the `packages/{name}/tests` folder and should only be added or modified when modifying that package. + +## Running Tests + +You can run these test cases in the following ways: + +- Run `./x test unit` or `pnpm run test:unit` from the root directory. +- Or run `npm run test` from the `packages/rspack-test-tools` directory. +- To update snapshots, run `npm run test -- -u` from the `packages/rspack-test-tools` directory. +- To pass specific jest cli arguments, run `npm run test -- {args}` from the `packages/rspack-test-tools` directory. + +## Directory Structure + +The structure of the `packages/rspack-test-tools/tests` folder is as follows: + +```bash +. +├── js # Used to store build artifacts and temporary files +├── __snapshots__ # Used to store test snapshots +├── {Name}.test.js # Entry for normal testing +├── {Name}.hottest.js # Entry for hot snapshot testing +├── {Name}.difftest.js # Entry for diff testing +├── {name}Cases # Directory to store test cases +└── fixtures # General test files +``` + +The `{Name}.test.js` is the entry file for tests, which will walk the `{name}Cases` folder and run cases in it. Therefore, when you need to add or modify test cases, add them to the relevant `{name}Cases` folder based on the type of testing. + +## Test Types + +The existing test types are: + +- [Normal](#normal): Used to test core build processes without configuration changes. This type is used when testing does not require adding `rspack.config.js`. +- [Config](#config): Used to test build configuration options. If your test needs specific configuration added through `rspack.config.js` to run and does not fit other scenarios, use this test type. +- [Hot](#hot): Used to test whether Hot Module Replacement (HMR) runs correctly. This type includes HotNode with a fixed `target=async-node`, HotWeb with a fixed `target=web`, and HotWorker with a fixed `target=webworker`. +- [HotSnapshot](#hotsnapshot): Used to test whether HMR can generate correct intermediate artifacts. This test type shares test cases with the Hot type and generates snapshots for incremental artifacts for each HMR. +- [Watch](#watch): Used to test incremental compilation after modifying files in Watch mode. +- [StatsOutput](#statsoutput): Used to test the console output log after the build ends. +- [StatsAPI](#stats-api): Used to test the Stats object generated after the build ends. +- [Diagnostic](#diagnostic): Used to test the formatted output information for warnings/errors generated during the build process. +- [Hash](#hash): Used to test whether hash generation works correctly. +- [Compiler](#compiler): Used to test Compiler/Compilation object APIs. +- [Defaults](#defaults): Used to test the interaction between configuration options. +- [Error](#error): Used to test the interaction between `compilation.errors` and `compilation.warnings`. +- [Hook](#hook): Used to test various hook functionalities. +- [TreeShaking](#treeshaking): Used to test Tree Shaking-related features. +- [Builtin](#builtin): Used to test plugins with built-in native implementations. + +Please prioritize adding test cases within the above test types. + +{/* If there are no suitable types, please follow the guidelines for [adding new test types](./test-advanced). */} + +## Normal + +| Test Entry | `tests/Normal.test.js` | +| --------------------- | --------------------------------------------------------------------------------------------------------------------------- | +| Case Directory | [`tests/normalCases`](https://github.com/web-infra-dev/rspack/tree/main/packages/rspack-test-tools/tests/normalCases) | +| Output Directory | `tests/js/normal` | +| Default Configuration | [NormalProcessor](https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-test-tools/src/processor/normal.ts#L35) | +| Run Output | `Yes` | + +The writing of the case is the same as a regular rspack project, but it does not include the `rspack.config.js` file and will use the provided configuration for building. + +## Config + +| Test Entry | `tests/Config.test.js` | +| --------------------- | --------------------------------------------------------------------------------------------------------------------------- | +| Case Directory | [`tests/configCases`](https://github.com/web-infra-dev/rspack/tree/main/packages/rspack-test-tools/tests/configCases) | +| Output Directory | `tests/js/config` | +| Default Configuration | [ConfigProcessor](https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-test-tools/src/processor/config.ts#L51) | +| Run Output | `Yes` | + +This test case is similar to a regular rspack project. You can specify the build configuration by adding a `rspack.config.js` and control various behaviors during testing by adding a `test.config.js`. The structure of the `test.config.js` file is as follows: + +```typescript {16-20} title="test.config.js" +type TConfigCaseConfig = { + noTest?: boolean; // Do not run the test output and end the test + beforeExecute?: () => void; // Callback before running the output + afterExecute?: () => void; // Callback after running the output + moduleScope?: (ms: IBasicModuleScope) => IBasicModuleScope; // Module context variables when running the output + findBundle?: ( + // Function for obtaining output when running the output, can control the output at a finer granularity + index: number, // Compiler index in multi-compiler scenario + options: TCompilerOptions, // Build configuration object + ) => string | string[]; + bundlePath?: string[]; // Output file name when running the output (prior to findBundle) + nonEsmThis?: (p: string | string[]) => Object; // this object during CJS output runtime, defaults to current module's module.exports if not specified + modules?: Record; // Pre-added modules when running the output, will be prioritized when required + timeout?: number; // Timeout for the test case +}; + +/** @type {import("../../../..").TConfigCaseConfig} */ +module.exports = { + // ... +}; +``` + +## Hot + +| Test Entry | `Hot{Target}.test.js` | +| --------------------- | --------------------------------------------------------------------------------------------------------------------- | +| Case Directory | [`tests/hotCases`](https://github.com/web-infra-dev/rspack/tree/main/packages/rspack-test-tools/tests/hotCases) | +| Output Directory | `tests/js/hot-{target}` | +| Default Configuration | [HotProcessor](https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-test-tools/src/processor/hot.ts#L86) | +| Run Output | `Yes` | + +This test case is similar to a regular rspack project. You can specify the build configuration by adding a `rspack.config.js`, but you also need to add a `changed-file.js` in the case directory to specify the files that have changed, as shown below: + +```js title="changed-file.js" +const path = require('path'); +module.exports = [ + path.resolve(__dirname, './file.js'), // file.js has changed and triggers hot update +]; +``` + +And also, within the file that has changed, use `---` to separate the code before and after the change: + +```js file.js title="file.js" +module.exports = 1; // Initial build +--- +module.exports = 2; // First hot update +--- +module.exports = 3; // Second hot update +``` + +In the test case code, use the `NEXT` method to control the timing of file changes and add test code within it: + +```js title="index.js" +import value from './file'; + +it('should hot update', done => { + expect(value).toBe(1); + // Use packages/rspack-test-tools/tests/hotCases/update.js to trigger update + NEXT( + require('../../update')(done, true, () => { + expect(value).toBe(2); + NEXT( + require('../../update')(done, true, () => { + expect(value).toBe(3); + done(); + }), + ); + }), + ); +}); + +module.hot.accept('./file'); +``` + +## HotSnapshot + +| Test Entry | `HotSnapshot.hottest.js` | +| --------------------- | --------------------------------------------------------------------------------------------------------------- | +| Case Directory | [`tests/hotCases`](https://github.com/web-infra-dev/rspack/tree/main/packages/rspack-test-tools/tests/hotCases) | +| Output Directory | `tests/js/hot-snapshot` | +| Default Configuration | Same as [Hot](#hot) | +| Run Output | `Yes` | + +Uses the same test cases as `Hot{Target}`, and generates a `__snapshots__/{target}/{step}.snap.txt` file in the case folder to perform snapshot testing on the incremental artifacts of each HMR. + +The snapshot structure is as follows: + +- **Changed Files**: Source code files that trigger this HMR build +- **Asset Files**: Artifact files of this HMR build +- **Manifest**: Contents of the `hot-update.json` metadata file for this HMR build, where: + - `"c"`: Id of the chunks to be updated in this HMR + - `"r"`: Id of the chunks to be removed in this HMR + - `"m"`: Id of the modules to be removed in this HMR +- **Update**: Information about the `hot-update.js` patch file for this HMR build, including: + - **Changed Modules**: List of modules included in the patch + - **Changed Runtime Modules**: List of runtime modules included in the patch + - **Changed Content**: Snapshot of the patch code + +## Watch + +| Entry File | `Watch.test.js` | +| --------------------- | ------------------------------------------------------------------------------------------------------------------------- | +| Case Directory | [`tests/watchCases`](https://github.com/web-infra-dev/rspack/tree/main/packages/rspack-test-tools/tests/watchCases) | +| Output Directory | `tests/js/watch` | +| Default Configuration | [WatchProcessor](https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-test-tools/src/processor/watch.ts#L99) | +| Run Output | `Yes` | + +As the Watch build needs to be performed in multiple steps, you can specify the build configuration by adding a `rspack.config.js`. The directory structure of its cases is special and will use incrementing numbers to represent change batches: + +```bash +. +├── 0 # WATCH_STEP=0, initial code for the case +├── 1 # WATCH_STEP=1, diff files for the first change +├── 2 # WATCH_STEP=2, diff files for the second change +└── rspack.config.js +``` + +In the test code, you can use the `WATCH_STEP` variable to get the current batch number of changes. + +## StatsOutput + +| Test Entry | `StatsOutput.test.js` | +| --------------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| Case Directory | [`tests/statsOutputCases`](https://github.com/web-infra-dev/rspack/tree/main/packages/rspack-test-tools/tests/statsOutputCases) | +| Output Directory | `tests/js/statsOutput` | +| Default Configuration | [StatsProcessor](https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-test-tools/src/processor/stats.ts#L190) | +| Run Output | `No` | + +The writing of the cases is the same as in a regular rspack project. After running, the console output information will be captured in snapshots and stored in `rspack-test-tools/tests/__snapshots__/StatsOutput.test.js.snap`. + +:::info Tip +As some StatsOutput test cases contain hashes, when you modify the output code, please use the `-u` parameter to update the snapshots for these cases. +::: + +## Stats API + +| Entry File | `StatsAPI.test.js` | +| --------------------- | ----------------------------------------------------------------------------------------------------------------- | +| Case Directory | `tests/statsAPICases` | +| Output Directory | `None` | +| Default Configuration | [`None`](https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-test-tools/src/processor/stats-api.ts) | +| Run Output | `No` | + +This test uses `rspack-test-tools/tests/fixtures` as the source code for the build, so the test case is written as a single file. Its structure is as follows: + +```js {8-11} title="{case}.js" +type TStatsAPICaseConfig = { + description: string; // Case description + options?: (context: ITestContext) => TCompilerOptions; // Case build configuration + build?: (context: ITestContext, compiler: TCompiler) => Promise; // Case build method + check?: (stats: TCompilerStats, compiler: TCompiler) => Promise; // Function to check the stats for the case +} + +/** @type {import('../..').TStatsAPICaseConfig} */ +module.exports = { + // ... +} +``` + +## Diagnostic + +| Entry File | `Diagnostics.test.js` | +| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| Case Directory | [`tests/diagnosticsCases`](https://github.com/web-infra-dev/rspack/tree/main/packages/rspack-test-tools/tests/diagnosticsCases) | +| Output Directory | `tests/js/diagnostics` | +| Default Configuration | [DiagnosticProcessor](https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-test-tools/src/processor/diagnostic.ts#L71) | +| Run Output | `No` | + +This test case is similar to a typical rspack project and can specify build configurations by adding a `rspack.config.js`. Additionally, it will add a `stats.err` file in the case directory to store snapshots of warnings/errors. To refresh, use the `-u` parameter. + +## Hash + +| Entry File | `Hash.test.js` | +| --------------------- | ----------------------------------------------------------------------------------------------------------------------- | +| Case Directory | [`tests/hashCases`](https://github.com/web-infra-dev/rspack/tree/main/packages/rspack-test-tools/tests/hashCases) | +| Output Directory | None | +| Default Configuration | [HashProcessor](https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-test-tools/src/processor/hash.ts#L53) | +| Run Output | No | + +This test case is similar to a typical rspack project, but it will add a `test.config.js` file in the case directory and specify a `validate()` method to check the hash information in the `stats` object after the build is complete: + +```javascript {5-8} title="test.config.js" +type THashCaseConfig = { + validate?: (stats: TCompilerStats) => void; +}; + +/** @type {import('../..').THashCaseConfig} */ +module.exports = { + // ... +}; +``` + +## Compiler + +| Entry File | `Compiler.test.js` | +| --------------------- | ------------------------------------------------------------------------------------------------------------------------- | +| Case Directory | [`tests/compilerCases`](https://github.com/web-infra-dev/rspack/tree/main/packages/rspack-test-tools/tests/compilerCases) | +| Output Directory | None | +| Default Configuration | [None](https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-test-tools/src/processor/simple.ts) | +| Run Output | No | + +This test uses `rspack-test-tools/tests/fixtures` as the source code for the build, so the test case is written as a single file. Its structure is as follows: + +```javascript {9-12} title="{case.js}" +interface TCompilerCaseConfig { + description: string; // Description of the test case + options?: (context: ITestContext) => TCompilerOptions; // Test case build configuration + compiler?: (context: ITestContext, compiler: TCompiler) => Promise; // How the compiler is created for the test case + build?: (context: ITestContext, compiler: TCompiler) => Promise; // Build method for the test case + check?: (context: ITestContext, compiler: TCompiler, stats: TCompilerStats) => Promise; // Check function for the test case +} + +/** @type {import('../..').TCompilerCaseConfig} */ +module.exports = { + // ... +}; +``` + +## Defaults + +| Entry File | `Defaults.test.js` | +| --------------------- | ----------------------------------------------------------------------------------------------------------------------- | +| Case Directory | [`tests/defaultCases`](https://github.com/web-infra-dev/rspack/tree/main/packages/rspack-test-tools/tests/defaultCases) | +| Output Directory | None | +| Default Configuration | [None](https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-test-tools/src/processor/defaults.ts) | +| Run Output | No | + +This test does not execute real builds; it only generates build configurations and observes the differences from the default configuration. The basic default configuration will be snapshot and stored in `rspack-test-tools/tests/__snapshots__/Defaults.test.js.snap`. + +This test uses `rspack-test-tools/tests/fixtures` as the source code for the build, so the test case is written as a single file. Its structure is as follows: + +```javascript {8-11} title="{case}.js" +interface TDefaultsCaseConfig { + description: string; // Description of the test case + cwd?: string; // process.cwd for generating the build configuration of the test case, default is the `rspack-test-tools` directory + options?: (context: ITestContext) => TCompilerOptions; // Test case build configuration + diff: (diff: jest.JestMatchers, defaults: jest.JestMatchers>) => Promise; // Differences from the default configuration +} + +/** @type {import('../..').TDefaultsCaseConfig} */ +module.exports = { + // ... +}; +``` + +The details for the Error test are as follows: + +## Error + +| Entry File | `Error.test.js` | +| --------------------- | ------------------------------------------------------------------------------------------------------------------------- | +| Case Directory | [`tests/errorCases`](https://github.com/web-infra-dev/rspack/tree/main/packages/rspack-test-tools/tests/errorCases) | +| Output Directory | None | +| Default Configuration | [ErrorProcessor](https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-test-tools/src/processor/error.ts#L84) | +| Run Output | No | + +This test uses `rspack-test-tools/tests/fixtures` as the source code for the build, so the test case is written as a single file. Its structure is as follows: + +```javascript {8-11} title="{case}.js" +interface TErrorCaseConfig { + description: string; // Description of the test case + options?: (options: TCompilerOptions, context: ITestContext) => TCompilerOptions; // Test case configuration + build?: (context: ITestContext, compiler: TCompiler) => Promise; // Test case build method + check?: (stats: TStatsDiagnostics) => Promise; // Function to check the test case +} + +/** @type {import('../..').TErrorCaseConfig} */ +module.exports = { + // ... +}; +``` + +## Hook + +| Entry File | `Hook.test.js` | +| --------------------- | ------------------------------------------------------------------------------------------------------------------------ | +| Case Directory | [`tests/hookCases`](https://github.com/web-infra-dev/rspack/tree/main/packages/rspack-test-tools/tests/hookCases) | +| Output Directory | None | +| Default Configuration | [HookProcessor](https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-test-tools/src/processor/hook.ts#L190) | +| Run Output | No | + +This test records the input and output of the hook and stores it in the snapshot `hooks.snap.txt`. The snapshot of the final product code is stored in `output.snap.txt`. + +This test uses `rspack-test-tools/tests/fixtures` as the source code for the build, so the test case is written as a single file. Its structure is as follows: + +```javascript {8-11} title="{case}/test.js" +interface THookCaseConfig { + description: string; // Description of the test case + options?: (options: TCompilerOptions, context: ITestContext) => TCompilerOptions; // Test case configuration + compiler?: (context: ITestContext, compiler: TCompiler) => Promise; // Callback after creating the compiler instance + check?: (context: ITestContext) => Promise; // Callback after the build is completed +} + +/** @type {import("../../../..").THookCaseConfig} */ +module.exports = { + // ... +}; +``` + +## TreeShaking + +| Entry File | `TreeShaking.test.js` | +| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| Case Directory | `tests/treeShakingCases` | +| Output Directory | `tests/js/treeShaking` | +| Default Configuration | [TreeShakingProcessor](https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-test-tools/src/processor/treeshaking.ts#L19) | +| Run Output | No | + +In this test case, the configuration is similar to a regular rspack project. You can specify the build configuration by adding a `rspack.config.js`, but the final product is snapshot and stored in `__snapshots__/treeshaking.snap.txt`. + +## Builtin + +| Entry File | `Builtin.test.js` | +| --------------------- | ----------------------------------------------------------------------------------------------------------------------------- | +| Case Directory | `tests/builtinCases` | +| Output Directory | `tests/js/builtin` | +| Default Configuration | [BuiltinProcessor](https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-test-tools/src/processor/builtin.ts#L27) | +| Run Output | No | + +This test case is similar to a regular rspack project, and you can specify the build configuration by adding a `rspack.config.js`. However, depending on the directory, different snapshots of the products will be generated and stored in `__snapshots__/output.snap.txt`: + +- **plugin-css**: Snapshots of files with a `.css` extension +- **plugin-css-modules**: Snapshots of files with `.css` and `.js` extensions +- **plugin-html**: Snapshots of files with `.html` extension +- **Other**: Snapshots of files with `.js` extension diff --git a/website/docs/en/contribute/development/testing-webpack.mdx b/website/docs/en/contribute/development/testing-webpack.mdx new file mode 100644 index 00000000000..dc13665bf0d --- /dev/null +++ b/website/docs/en/contribute/development/testing-webpack.mdx @@ -0,0 +1,61 @@ +# Testing Webpack + +## Testing Webpack Cases + +> **Note**: The `rspack/webpack-test` is heavily based on [webpack/test](https://github.com/webpack/webpack/tree/main/test) + +### Progressively migrate webpack test + +Originally, we use this formula to calculate the compatibility `passedTestCaseCount / totalTestCount`, totalTestCount = passedTestCaseCount + failedTestCount + skippedTestCount , but sometimes it maybe hard to compatible with all webpack test cases for some reasons (e.g. performance, legacy feature that we don't want to support), we need a method to skip these tests that we will not support. Thus, we adjust the original formula to `(passedTestCaseCount + willNotSupportTestCount) / totalTestCount`. + +Currently, we use a `test.filter.js` under each failed test case directory to skip failed test case, using this method could let us migrate webpack test case progressively without affect the real compatibility (Because this method will not influence the real `passedTestCaseCount`). + +e.g. + +```js +// test.filter.js +module.exports = () => { + return false; // false means this testcase is skipped for now, but maybe we will support in the future, `-1` means this test case we don't want to compatible with, this related to `willNotSupportTest`. +}; +``` + +When you find that we have passed some failed testcases which is skipped for now, you could change the `test.filter.js` to + +```js +module.exports = () => { + return true; +}; +``` + +or delete the `test.filter.js` + +## Testing Webpack Plugins Cases + +Based on implementation differences and performance considerations, Rspack will integrate some Webpack plugins internally. The test suite for the plugins will also be copied to the `plugin-test` folder for testing plugin compatibility. + +Therefore, in order to maintain consistency with the original repository, it is not recommended to modify these test cases, except in the following scenarios: + +- When a new Webpack plugin is integrated into Rspack, the test cases for that plugin need to be copied. +- When there are differences between the artifacts of Rspack and Webpack (e.g., different hashes), some test cases may need modification. + +In scenarios other than those mentioned above, please follow the [Rspack Testing](./testing-rspack) guidelines for adding test cases. + +### Running Tests + +You can run these test cases in the following ways: + +- Run `./x test plugin` or `pnpm run test:plugin` in the root directory. +- Or run `npm run test` in the `rspack/plugin-test` directory. +- To update snapshots, run `npm run test -- -u` in the `plugin-test` directory. +- To pass specific jest cli parameters, run `npm run test -- {args}` in the `plugin-test` directory. + +### Adding Test Cases + +1. Create a `plugin-test/{plugin-name}` folder and copy the test cases for that plugin into the folder. +2. Adapt the test configuration in `plugin-test/jest.config.js`. If there are special configurations, follow the process below: + 1. Create `plugin-test/jest.{plugin-name}.config.js`, import `jest.config.js`, and modify it based on that. + 2. Add the `test:{plugin-name}` command to the `scripts` property of `plugin-test/package.json`. + 3. If snapshot testing is included, use the `global.updateSnapshot` to determine whether to update the snapshot. +3. Update License information: + 1. Add a `plugin-test/{plugin-name}/README.md` file and include the License information from the source repository of the test cases. + 2. Update the `plugin-test/README.md` file to include the link and credits information from the source repository of the test cases. diff --git a/website/docs/en/contribute/development/testing.md b/website/docs/en/contribute/development/testing.md deleted file mode 100644 index 675ab1b5b9e..00000000000 --- a/website/docs/en/contribute/development/testing.md +++ /dev/null @@ -1,149 +0,0 @@ -# Testing - -We currently have two sets of test suites, one for Rust and one for Node.js. - -## Rust Testing - -- `cargo test` will run all the rust side tests, which includes standalone tests for core functionality and plugins. -- `UPDATE=1 cargo test` will automatically update the failed snapshot - -## Node Testing - -We are maintaining two test suites for Node Testing in Rspack, Rspack Testing and Webpack Testing - -### Webpack Testing - -We copy the whole webpack test suites into [webpack-test](https://github.com/web-infra-dev/rspack/tree/main/webpack-test#progressively-migrate-webpack-test) folder to check the compatibility with webpack. If you add features or fix bugs we recommend you check whether this feature or bug is covered in webpack test suites first. If it's covered and testable in Webpack Testing, you can enable specific test case by setting return value to true in [`test.filter.js`](https://github.com/web-infra-dev/rspack/blob/80e97477483fcb912473ae339c37d5a5e247f7b1/webpack-test/cases/compile/error-hide-stack/test.filter.js#L2C33-L2C84) in this case folder to enable this case. See more details in https://github.com/web-infra-dev/rspack/blob/main/webpack-test/README.md, Please note that don't modify original test code in Webpack Testing, if you find difficulties in running test suites without modifying original code, you can copy this test code in the following \[Rspack Testing\](#Rspack Testing). - -#### Run Tests - -```sh -# In root path -./x build -a # build binding and js part -./x test webpack # run webpack test suites -``` - -### Rspack Testing - -We maintain test suites in Rspack Testing which is not coverable or need to be modified in Webpack Testing. The test suites lies in [rspack-test](https://github.com/web-infra-dev/rspack/tree/main/packages/rspack/tests). This folder structure is similar with Webpack Testing. - -#### Run Tests - -```sh -# In root path -./x build -a -./x test js -``` - -Or only test the package that you made the changes: - -```sh -# In the Node.js package path -pnpm run build && pnpm run test -``` - -To update snapshots: - -```sh -pnpm --filter '@rspack/*' test -- -u -``` - -### Node Testing Suite Overview - -We use jest for Node.js tests, The most important test cases are the case in the `packages/rspack`. most of these cases comes from webpack https://github.com/webpack/webpack/tree/main/test because we want to make sure that Rspack can work as same as webpack. - -There are three kinds of integration cases in `@rspack/core`. - -#### case.test.ts - -Cases are used to test normal build behavior, we use these cases to test against bundler core functionality, like `entry`, `output`, `module` `resolve`, etc. it will first build your test file to test whether the input could be compiled successfully, then it will use the bundled test file to run test cases in the test file to test bundler's all kinds of behavior. - -#### configCase.test.ts - -Cases are used to test custom build behavior, you could use custom `webpack.config.js` to override default build behavior, you can use these cases to test against behavior related to specific config. - -##### statsTestCase.test.ts - -Cases are used to test your stats, By Default we will use jest's snapshot to snapshot your stats, and we **highly** recommend to **avoid** snapshot except statsCase. you can use statsCase to test behaviors like code splitting | bundle splitting, which is hard to test by just running code. - -## E2E - -The `packages/playground` provides e2e testing feature. We use [playwright](https://github.com/Microsoft/playwright) as the e2e testing framework. - -### Cases - -The entry point of a test case is a file ending with `.test.ts`, and the parent directory of this file is the project directory. - -Here are some rules about test cases: - -- The project directory must contain `rspack.config.js` to start the dev server. -- The project directory can contain multi `*.test.ts`. -- All test cases share dependencies, so just add dependencies in `packages/playground/package.json`. -- The cases folder should contain the category folders and then is the project folders. In principle, there should be no third-level directory. - -### Fixtures - -The `fixtures` is a feature of playwright, in short it provides a variable that is generated in before{Each|All} and destroyed in after{Each|All}. More information see [test-fixtures](https://playwright.dev/docs/test-fixtures) - -Here are some rules when defining a new fixture: - -- Private fixtures should start with `_` and are used only in the current file. -- A file only provides fixtures with the same name. -- A file can only provide one option and starts with `default` -- Register fixtures in `fixtures/index.ts` and export only necessary variables and types. - -Here are some existing fixtures: - -#### pathInfo - -This fixture will generate test environment, and calculate the PathInfo. - -```ts -type PathInfo = { - // test file path - testFile: string; - // project dir - testProjectDir: string; - // temporary project directory to be copied into - tempProjectDir: string; -}; -``` - -#### rspack - -This fixture will start the rspack dev server and provide some useful methods. - -```ts -type Rspack = { - // rspack running project directory - projectDir: string; - // rspack compiler - compiler: Compiler; - // rspack dev server - devServer: DevServer; - // waiting for rspack build finish - waitingForBuild: () => Promise; - // waiting for hmr finish, the poll function is used to check - waitingForHmr: (poll: () => Promise) => Promise; -}; -``` - -#### fileAction - -This fixture will provide file change operations. - -```ts -type fileAction = { - updateFile(relativePath: string, fn: (content: string) => string): void; - deleteFile(relativePath: string): void; -}; -``` - -### How it works - -- playwright scan all test case and allocates a worker to run each case. -- `pathInfo` copy the project directory corresponding to the current case to `temp/${worker_index}`. -- `rspack` rewrite dev server port to `8000 + worker_index` and start compiler and dev server in `temp/${worker_index}`. -- run current tests. -- `rspack` close dev server and compiler. -- `pathInfo` clear `temp/${worker_index}` diff --git a/website/docs/en/contribute/development/testing.mdx b/website/docs/en/contribute/development/testing.mdx new file mode 100644 index 00000000000..1c51858e035 --- /dev/null +++ b/website/docs/en/contribute/development/testing.mdx @@ -0,0 +1,77 @@ +# Testing + +Because Rspack uses a mix of Rust and Node.js code, different testing strategies are used for each. + +## Rust Testing + +:::info Tip +Rust test cases are only suitable for unit testing. To test the complete build process, please add Node.js test cases. +::: + +### Running Rust Tests + +You can run the Rust code's test cases using `./x test rust` or `cargo test`. + +### Writing Rust Tests + +Test cases are written within the Rust code. For example: + +```rust +fn add(a: u8, b: u8) -> u8 { + a + b +} + +#[test] +fn test_add() { + assert_eq!(add(1, 2), 3); +} +``` + +> For more information, please refer to: [Rust: How to Write Tests](https://doc.rust-lang.org/book/ch11-01-writing-tests.html) + +## Node.js Testing + +Node.js testing provides three test suites: + +- **Webpack Testing**: Running Webpack test cases using Rspack +- **Webpack Plugin Testing**: Running test cases of native supported plugins using Rspack +- **Rspack Testing**: Running test cases of Rspack itself + +### Webpack Testing + +Rspack copied the whole Webpack test suite to the `webpack-test` folder to check the compatibility of webpack. If you need to add new test cases, it is recommended to first check if the case has been exists in this folder. You can enable a test case by removing the `test.filter.js` file or modifying its return value to `true`. + +You can run Webpack tests by running `./x test webpack` or `pnpm run test:webpack` at the root folder. + +:::warning Notice +If you encounter any problems, please do not modify the original code of the Webpack test cases. You can copy it and follow the [Rspack Testing](./testing-rspack) to create new test cases in `packages/rspack-test-tools/tests`. +::: + +> For more details, please refer to: [Webpack Testing](./testing-webpack#testing-webpack-cases). + +### Rspack Testing + +Rspack's test cases are stored in the `packages/rspack-test-tools/tests` folder, including unique test cases and cases that require modification for Webpack and Webpack plugins. + +You can run Rspack tests by running `./x test unit` or `pnpm run test:unit` at the root folder. + +You can also go to the `packages/rspack-test-tools` folder and run `npm run test` to run test cases and add some arguments: + +- **When refreshing test snapshots is needed**: Add `-u`, like `npm run test -- -u` +- **When filtering test cases is needed**: Add `-t`, like `npm run test -- -t config/asset` to only run test cases from the `packages/rspack-test-tools/configCases/asset` folder (`config` will be automatically mapped to `configCases`, and other folders work similarly). Pattern matching supports regex, see [jest](https://jestjs.io/docs/cli#--testnamepatternregex) for details. + +> For more details, please refer to: [Rspack Testing](./testing-rspack). + +### Webpack Plugin Testing + +Due to implementation differences and performance considerations, Rspack will internally support some Webpack plugins. Similarly, the test suites for these plugins are copied to the `plugin-test` folder to test the compatibility of the plugins. + +You can run Webpack plugin tests by running `./x test plugin` or `pnpm run test:plugin` at the root folder. + +:::warning Notice +In most cases, even if you modify the functionality of the corresponding plugin, you only need to follow [Rspack Testing](./testing-rspack) to add new test cases. + +Only when you native implement a new Webpack plugin and need to copy its test cases, you can add them to this suite. +::: + +> For more details, please refer to: [Webpack Plugin Testing](./testing-webpack#testing-webpack-plugins-cases). diff --git a/website/docs/zh/contribute/development/testing-rspack.mdx b/website/docs/zh/contribute/development/testing-rspack.mdx new file mode 100644 index 00000000000..2213878ac2c --- /dev/null +++ b/website/docs/zh/contribute/development/testing-rspack.mdx @@ -0,0 +1,403 @@ +# Rspack 测试 + +Rspack 的测试用例包括如下: + +- Rspack 核心测试用例,存放在 `packages/rspack-test-tools/tests` 文件夹下,会通过模拟构建流程以运行测试用例。通常情况下,都在此文件夹下添加测试用例。 +- Rspack 其他包的测试用例,存放在 `packages/{name}/tests` 文件夹下,仅当修改对应包时添加/修改。 + +## 运行测试 + +可以通过如下方式运行这些测试用例: + +- 根目录下运行 `./x test unit` 或 `pnpm run test:unit`。 +- 或在 `packages/rspack-test-tools` 目录下运行 `npm run test`。 +- 如需更新 snapshot,在 `packages/rspack-test-tools` 目录下运行 `npm run test -- -u`。 +- 如需传入特定 jest cli 参数,在 `packages/rspack-test-tools` 目录下运行 `npm run test -- {args}`。 + +## 目录规范 + +文件夹 `packages/rspack-test-tools/tests` 的结构如下所示: + +```bash +. +├── js #用于存放构建生成的产物和临时文件 +├── __snapshots__ #用于存放测试快照 +├── {Name}.test.js #常规测试的入口 +├── {Name}.hottest.js #热更新流程测试入口 +├── {Name}.difftest.js #产物对比测试入口 +├── {name}Cases #测试用例存放目录 +└── fixtures #通用测试文件 +``` + +`{Name}.test.js` 作为测试的入口文件,会遍历 `{name}Cases` 并运行其中的用例。因此当您需要添加/修改测试用例时,请根据需要测试的功能类型在相应的 `{name}Cases` 文件夹下添加用例。 + +## 测试类型 + +目前已有的测试类型有: + +- [Normal](#normal):用于测试配置无关的核心构建流程。当您的测试无需添加 `rspack.config.js` 时可使用此测试类型。 +- [Config](#config):用于测试构建配置项。如果你的测试需要通过 `rspack.config.js` 添加特定配置才可以运行时,且不符合其他场景时可使用此测试类型。 +- [Hot](#hot):用于测试 Hot Module Replacement(HMR)是否正确运行。HotNode 会固定使用 `target=async-node`,HotWeb 会固定使用 `target=web`,HotWorker 会固定使用 `target=webworker`。 +- [HotSnapshot](#hotsnapshot):用于测试 Hot Module Replacement(HMR) 能否生成正确的中间产物。与[Hot](#Hot)类型测试共用测试用例。 +- [Watch](#watch):用于测试 Watch 模式下修改文件后的增量编译。 +- [StatsOutput](#statsoutput):用于测试构建结束后控制台输出的日志。 +- [StatsAPI](#stats-api):用于测试构建结束后生成的 Stats 对象。 +- [Diagnostic](#diagnostic):用于测试构建过程中产生的警告/错误的格式化输出信息。 +- [Hash](#hash):用于测试 Hash 能否正确生成。 +- [Compiler](#compiler):用于测试 Compiler/Compilation 对象的 API。 +- [Defaults](#defaults):用于测试配置项之间的联动。 +- [Error](#error):用于测试 `compilation.errors` 和 `compilation.warnings` 的互操作。 +- [Hook](#hook):用于测试各种 hook 能否正确工作。 +- [TreeShaking](#treeshaking):用于测试 Tree Shaking 相关功能。 +- [Builtin](#builtin):用于测试内置原生实现的插件。 + +请优先在以上测试类型中添加用例。{/* 如果没有符合的类型,请按照[添加测试类型](./test-advanced)添加新类型。 */} + +## Normal + +| 测试入口 | `tests/Normal.test.js` | +| -------- | --------------------------------------------------------------------------------------------------------------------------- | +| 用例目录 | [`tests/normalCases`](https://github.com/web-infra-dev/rspack/tree/main/packages/rspack-test-tools/tests/normalCases) | +| 产物目录 | `tests/js/normal` | +| 默认配置 | [NormalProcessor](https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-test-tools/src/processor/normal.ts#L35) | +| 产物运行 | `是` | + +用例的编写与常规的 rspack 项目相同,但不包含 `rspack.config.js` 文件,会使用固定的配置构建。 + +## Config + +| 测试入口 | `tests/Config.test.js` | +| -------- | --------------------------------------------------------------------------------------------------------------------------- | +| 用例目录 | [`tests/configCases`](https://github.com/web-infra-dev/rspack/tree/main/packages/rspack-test-tools/tests/configCases) | +| 产物目录 | `tests/js/config` | +| 默认配置 | [ConfigProcessor](https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-test-tools/src/processor/config.ts#L51) | +| 产物运行 | `是` | + +此测试用例与常规的 rspack 项目相同,可通过添加 `rspack.config.js` 来指定构建配置,并可通过添加 `test.config.js` 来控制测试运行时的各种行为,其结构如下: + +```typescript {16-20} title="test.config.js" +type TConfigCaseConfig = { + noTest?: boolean; // 不运行测试产物并结束测试 + beforeExecute?: () => void; // 运行产物前回调 + afterExecute?: () => void; // 运行产物后回调 + moduleScope?: (ms: IBasicModuleScope) => IBasicModuleScope; // 运行产物时的模块上下文变量 + findBundle?: ( + // 运行产物时的产物获取函数,可以更细粒度的控制产物 + index: number, // muli compiler 场景时的 compiler 序号 + options: TCompilerOptions, // 构建配置对象 + ) => string | string[]; + bundlePath?: string[]; // 运行产物时的产物文件名称(优先级低于 findBundle) + nonEsmThis?: (p: string | string[]) => Object; // CJS 产物运行时的 this 对象,若不指定则默认为当前模块的 module.exports + modules?: Record; // 运行产物时预先添加的模块,require 时会优先从此处读取 + timeout?: number; // 用例的超时时间 +}; + +/** @type {import("../../../..").TConfigCaseConfig} */ +module.exports = { + // ... +}; +``` + +## Hot + +| 测试入口 | `Hot{Target}.test.js` | +| -------- | --------------------------------------------------------------------------------------------------------------------- | +| 用例目录 | [`tests/hotCases`](https://github.com/web-infra-dev/rspack/tree/main/packages/rspack-test-tools/tests/hotCases) | +| 产物目录 | `tests/js/hot-{target}` | +| 默认配置 | [HotProcessor](https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-test-tools/src/processor/hot.ts#L86) | +| 产物运行 | `是` | + +此测试用例与常规的 rspack 项目相同,可通过添加 `rspack.config.js` 来指定构建配置,但需要额外在用例目录下添加 `changed-file.js` 来指定变更的文件,如下所示: + +```js title="changed-file.js" +const path = require('path'); +module.exports = [ + path.resolve(__dirname, './file.js'), // file.js 变化并触发热更新 +]; +``` + +对应的在变更的文件内通过 `---` 分割变更前后的代码: + +```js file.js title="file.js" +module.exports = 1; // 初始构建 +--- +module.exports = 2; // 首次热更新 +--- +module.exports = 3; // 第二次热更新 +``` + +在用例的代码中,通过 `NEXT` 方法控制文件变更时机,并在其中添加测试代码: + +```js title="index.js" +import value from './file'; + +it('should hot update', done => { + expect(value).toBe(1); + // 使用 packages/rspack-test-tools/tests/hotCases/update.js 触发更新 + NEXT( + require('../../update')(done, true, () => { + expect(value).toBe(2); + NEXT( + require('../../update')(done, true, () => { + expect(value).toBe(3); + done(); + }), + ); + }), + ); +}); + +module.hot.accept('./file'); +``` + +## HotSnapshot + +| 测试入口 | `HotSnapshot.hottest.js` | +| -------- | --------------------------------------------------------------------------------------------------------------- | +| 用例目录 | [`tests/hotCases`](https://github.com/web-infra-dev/rspack/tree/main/packages/rspack-test-tools/tests/hotCases) | +| 产物目录 | `tests/js/hot-snapshot` | +| 默认配置 | 与 Hot 相同 | +| 产物运行 | `是` | + +与 `Hot{Target}` 测试使用相同的测试用例。并在用例文件夹下生成 `__snapshots__/{target}/{step}.snap.txt` 文件,用于对每一次 HMR 的增量产物进行 snapshot 测试。 + +Snapshot 结构如下: + +- **Changed Files**:引发本次 HMR 构建的源码文件 +- **Asset Files**:本次 HMR 构建的产物文件 +- **Manifest**:本次 HMR 构建的 `hot-update.json` 元数据文件内容,其中 + - `"c"`:本次 HMR 需要更新的 chunk 的 id + - `"r"`:本次 HMR 需要移除的 chunk 的 id + - `"m"`:本次 HMR 需要移除的 module 的 id +- **Update**:本次 HMR 构建的 `hot-update.js` 补丁文件信息,其中: + - **Changed Modules**:补丁中包含的模块列表 + - **Changed Runtime Modules**:补丁中包含的 runtime 模块列表 + - **Changed Content**:补丁代码的快照 + +## Watch + +| 入口文件 | `Watch.test.js` | +| -------- | ------------------------------------------------------------------------------------------------------------------------- | +| 用例目录 | [`tests/watchCases`](https://github.com/web-infra-dev/rspack/tree/main/packages/rspack-test-tools/tests/watchCases) | +| 产物目录 | `tests/js/watch` | +| 默认配置 | [WatchProcessor](https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-test-tools/src/processor/watch.ts#L99) | +| 产物运行 | `是` | + +由于 Watch 构建需要分多步进行,可通过添加 `rspack.config.js` 来指定构建配置。其用例的目录结构较为特殊,会以自增的数字表示变更批次: + +```bash title="用例目录" +. +├── 0 # WATCH_STEP=0,用例初始代码 +├── 1 # WATCH_STEP=1,第一次变更的差异文件 +├── 2 # WATCH_STEP=2,第二次变更的差异文件 +└── rspack.config.js +``` + +同时在测试的代码中,可以通过 `WATCH_STEP` 变量获取当前的变更批次数字。 + +## StatsOutput + +| 测试入口 | `StatsOutput.test.js` | +| -------- | ------------------------------------------------------------------------------------------------------------------------------- | +| 用例目录 | [`tests/statsOutputCases`](https://github.com/web-infra-dev/rspack/tree/main/packages/rspack-test-tools/tests/statsOutputCases) | +| 产物目录 | `tests/js/statsOutput` | +| 默认配置 | [StatsProcessor](https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-test-tools/src/processor/stats.ts#L190) | +| 产物运行 | `否` | + +用例的编写与常规的 rspack 项目相同,运行后会将控制台输出信息生成快照并存放在 `rspack-test-tools/tests/__snapshots__/StatsOutput.test.js.snap` 中。 + +:::info Tip +由于部分 StatsOutput 测试用例包含 hash。因此当你修改了产物代码时,请通过 `-u` 参数刷新这些用例的快照。 +::: + +## Stats API + +| 入口文件 | `StatsAPI.test.js` | +| -------- | --------------------------------------------------------------------------------------------------------------- | +| 用例目录 | `tests/statsAPICases` | +| 产物目录 | `无` | +| 默认配置 | [`无`](https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-test-tools/src/processor/stats-api.ts) | +| 产物运行 | `否` | + +此测试固定使用 `rspack-test-tools/tests/fixtures` 作为构建的源码,因此用例以单文件编写,其输出结构如下: + +```js {8-11} title="{case}.js" +type TStatsAPICaseConfig = { + description: string; // 用例描述 + options?: (context: ITestContext) => TCompilerOptions; // 用例构建配置 + build?: (context: ITestContext, compiler: TCompiler) => Promise; // 用例构建方式 + check?: (stats: TCompilerStats, compiler: TCompiler) => Promise; // 用例的 stats 检测函数 +} + +/** @type {import('../..').TStatsAPICaseConfig} */ +module.exports = { + // ... +} +``` + +## Diagnostic + +| 入口文件 | `Diagnostics.test.js` | +| -------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| 用例目录 | [`tests/diagnosticsCases`](https://github.com/web-infra-dev/rspack/tree/main/packages/rspack-test-tools/tests/diagnosticsCases) | +| 产物目录 | `tests/js/diagnostics` | +| 默认配置 | [DiagnosticProcessor](https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-test-tools/src/processor/diagnostic.ts#L71) | +| 产物运行 | `否` | + +此测试用例与常规的 rspack 项目相同,可通过添加 `rspack.config.js` 来指定构建配置,但额外会在用例目录下添加 `stats.err` 文件用于存储警告/错误的快照,如需刷新请使用 `-u` 参数。 + +## Hash + +| 入口文件 | `Hash.test.js` | +| -------- | ----------------------------------------------------------------------------------------------------------------------- | +| 用例目录 | [`tests/hashCases`](https://github.com/web-infra-dev/rspack/tree/main/packages/rspack-test-tools/tests/hashCases) | +| 产物目录 | `无` | +| 默认配置 | [HashProcessor](https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-test-tools/src/processor/hash.ts#L53) | +| 产物运行 | `否` | + +此测试用例与常规的 rspack 项目相同,但额外会在用例目录下添加 `test.config.js` 文件,并指定 `validate()` 方法用于在构建结束后检测 stats 对象中的 hash 信息: + +```js {5-8} title="test.config.js" +type THashCaseConfig = { + validate?: (stats: TCompilerStats) => void; +}; + +/** @type {import('../..').THashCaseConfig} */ +module.exports = { + // ... +}; +``` + +## Compiler + +| 入口文件 | `Compiler.test.js` | +| -------- | ------------------------------------------------------------------------------------------------------------------------- | +| 用例目录 | [`tests/compilerCases`](https://github.com/web-infra-dev/rspack/tree/main/packages/rspack-test-tools/tests/compilerCases) | +| 产物目录 | `无` | +| 默认配置 | [`无`](https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-test-tools/src/processor/simple.ts) | +| 产物运行 | `否` | + +此测试固定使用 `rspack-test-tools/tests/fixtures` 作为构建的源码,因此用例以单文件编写,其输出结构如下: + +```js {9-12} title="{case.js}" +interface TCompilerCaseConfig { + description: string; // 用例描述 + options?: (context: ITestContext) => TCompilerOptions; // 用例构建配置 + compiler?: (context: ITestContext, compiler: TCompiler) => Promise; // 用例 compiler 创建方式 + build?: (context: ITestContext, compiler: TCompiler) => Promise; // 用例构建方式 + check?: (context: ITestContext, compiler: TCompiler, stats: TCompilerStats) => Promise; // 用例的检测函数 +} + +/** @type {import('../..').TCompilerCaseConfig} */ +module.exports = { + // ... +}; +``` + +## Defaults + +| 入口文件 | `Defaults.test.js` | +| -------- | ----------------------------------------------------------------------------------------------------------------------- | +| 用例目录 | [`tests/defaultCases`](https://github.com/web-infra-dev/rspack/tree/main/packages/rspack-test-tools/tests/defaultCases) | +| 产物目录 | `无` | +| 默认配置 | [`无`](https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-test-tools/src/processor/defaults.ts) | +| 产物运行 | `否` | + +此测试不会执行真实的构建,仅会生成构建配置并观察与默认配置的差异。基础的默认配置会生成快照并存放在 `rspack-test-tools/tests/__snapshots__/Defaults.test.js.snap` 中。 + +此测试固定使用 `rspack-test-tools/tests/fixtures` 作为构建的源码,因此用例以单文件编写,其输出结构如下: + +```js {8-11} title="{case}.js" +interface TDefaultsCaseConfig { + description: string; // 用例描述 + cwd?: string; // 用例的生成构建配置时的 process.cwd,默认为 `rspack-test-tools` 目录 + options?: (context: ITestContext) => TCompilerOptions; // 用例构建配置 + diff: (diff: jest.JestMatchers, defaults: jest.JestMatchers>) => Promise; // 与默认配置之间的差异 +} + +/** @type {import('../..').TDefaultsCaseConfig} */ +module.exports = { + // ... +}; +``` + +## Error + +| 入口文件 | `Error.test.js` | +| -------- | ------------------------------------------------------------------------------------------------------------------------- | +| 用例目录 | [`tests/errorCases`](https://github.com/web-infra-dev/rspack/tree/main/packages/rspack-test-tools/tests/errorCases) | +| 产物目录 | `无` | +| 默认配置 | [ErrorProcessor](https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-test-tools/src/processor/error.ts#L84) | +| 产物运行 | `否` | + +此测试的用例固定使用 `rspack-test-tools/tests/fixtures` 作为构建的源码,因此测试用例以特定的配置结构编写: + +```js {8-11} title="{case}.js" +interface TErrorCaseConfig { + description: string; // 用例描述 + options?: (options: TCompilerOptions, context: ITestContext) => TCompilerOptions; // 用例配置 + build?: (context: ITestContext, compiler: TCompiler) => Promise; // 用例构建方式 + check?: (stats: TStatsDiagnostics) => Promise; // 用例的检测函数 +} + +/** @type {import('../..').TErrorCaseConfig} */ +module.exports = { + // ... +}; +``` + +## Hook + +| 入口文件 | `Hook.test.js` | +| -------- | ------------------------------------------------------------------------------------------------------------------------ | +| 用例目录 | [`tests/hookCases`](https://github.com/web-infra-dev/rspack/tree/main/packages/rspack-test-tools/tests/hookCases) | +| 产物目录 | `无` | +| 默认配置 | [HookProcessor](https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-test-tools/src/processor/hook.ts#L190) | +| 产物运行 | `否` | + +会记录 hook 的出入参并存放在快照 `hooks.snap.txt` 中,最终产物代码的快照存放在 `output.snap.txt` 中。 + +此测试的用例固定使用 `rspack-test-tools/tests/fixtures` 作为构建的源码,因此测试用例以特定的配置结构编写: + +```js {8-11} title="{case}/test.js" +interface THookCaseConfig { + description: string; // 用例描述 + options?: (options: TCompilerOptions, context: ITestContext) => TCompilerOptions; // 用例配置 + compiler?: (context: ITestContext, compiler: TCompiler) => Promise; // 创建 compiler 实例后回调 + check?: (context: ITestContext) => Promise; // 构建完成后回调 +} + +/** @type {import("../../../..").THookCaseConfig} */ +module.exports = { + // ... +}; +``` + +## TreeShaking + +| 入口文件 | `TreeShaking.test.js` | +| -------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| 用例目录 | `tests/treeShakingCases` | +| 产物目录 | `tests/js/treeShaking` | +| 默认配置 | [TreeShakingProcessor](https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-test-tools/src/processor/treeshaking.ts#L19) | +| 产物运行 | `否` | + +此测试用例与常规的 rspack 项目相同,可通过添加 `rspack.config.js` 来指定构建配置,但会将最终产物生成快照并存放在 `__snapshots__/treeshaking.snap.txt` 中。 + +## Builtin + +| 入口文件 | `Builtin.test.js` | +| -------- | ----------------------------------------------------------------------------------------------------------------------------- | +| 用例目录 | `tests/builtinCases` | +| 产物目录 | `tests/js/builtin` | +| 默认配置 | [BuiltinProcessor](https://github.com/web-infra-dev/rspack/blob/main/packages/rspack-test-tools/src/processor/builtin.ts#L27) | +| 产物运行 | `否` | + +此测试用例与常规的 rspack 项目相同,可通过添加 `rspack.config.js` 来指定构建配置。 + +但根据目录的不同,会将不同的产物生成快照并存放在 `__snapshots__/output.snap.txt` 中: + +- **plugin-css**:将 `.css` 后缀文件生成快照 +- **plugin-css-modules**:将 `.css` 和 `.js` 后缀文件生成快照 +- **plugin-html**:将 `.html` 后缀文件生成快照 +- **其他**:将 `.js` 后缀文件生成快照 diff --git a/website/docs/zh/contribute/development/testing-webpack.mdx b/website/docs/zh/contribute/development/testing-webpack.mdx new file mode 100644 index 00000000000..dde7b28b479 --- /dev/null +++ b/website/docs/zh/contribute/development/testing-webpack.mdx @@ -0,0 +1,62 @@ +# Webpack 测试 + +## 运行 Webpack 用例 + +> **注意**: `rspack/webpack-test` 完全基于 [webpack/test](https://github.com/webpack/webpack/tree/main/test) + +### **持续迁移 webpack 测试** + +在迁移 webpack 测试的过程中,我们使用以下公式来计算兼容性:`passedTestCaseCount / totalTestCount`,其中 `totalTestCount = passedTestCaseCount + failedTestCount + skippedTestCount`。 + +但是有时由于一些原因(例如性能、部分历史遗留功能),可能很难与所有的 webpack 测试用例保持兼容。因此,我们需要一种方法来跳过这些不希望支持的测试用例。我们将原始公式调整为 `(passedTestCaseCount + willNotSupportTestCount) / totalTestCount`。 + +目前,可在用例目录下添加 `test.filter.js` 文件,用于跳过失败的测试用例。使得我们可以逐步迁移 webpack 测试用例,而不影响实际的兼容性(因为这种方法不影响真实的 `passedTestCaseCount`)。 + +例如: + +```javascript title="test.filter.js" +module.exports = () => { + return false; // false 表示当前测试用例暂时被跳过,但也许我们将来会支持它;-1 表示我们不希望兼容这个测试用例,这与 `willNotSupportTest` 相关。 +}; +``` + +如果您发现测试用例已可以通过,那么将 `test.filter.js` 更改为: + +```javascript title="test.filter.js" +module.exports = () => { + return true; +}; +``` + +或者直接删除 `test.filter.js` 文件。 + +## 运行 Webpack 插件用例 + +基于实现差异和性能考虑,Rspack 会将部分 Webpack 生态插件内置。同样会将插件的测试套件复制到 `plugin-test` 文件夹下,用于测试插件的兼容性。 + +因此,为了保持这些测试用例与其原仓库的一致性,不建议对测试用例进行修改,仅当如下场景满足时可变更: + +- 在 Rspack 中内置了新的 Webpack 生态插件,此时需要复制该插件的用例。 +- 因 Rspack 与 Webpack 的产物差异(如 hash 不同),需要修改部分用例。 + +非以上场景时,请遵循 [Rspack 测试](./testing-rspack) 添加测试用例 + +### 运行测试 + +可以通过如下方式运行这些测试用例: + +- 根目录下运行 `./x test plugin` 或 `pnpm run test:plugin`。 +- 或在 `plugin-test` 目录下运行 `npm run test`。 +- 如需更新 snapshot,在 `plugin-test` 目录下运行 `npm run test -- -u`。 +- 如需传入特定 jest cli 参数,在 `plugin-test` 目录下运行 `npm run test -- {args}`。 + +### 添加用例 + +1. 建立 `plugin-test/{插件名}` 文件夹,并将该插件的测试用例复制到该文件夹中。 +2. 适配 `plugin-test/jest.config.js` 中的测试配置,如有特殊配置遵循如下流程: + 1. 建立 `plugin-test/jest.{plugin-name}.config.js`,引入 `jest.config.js` 并在其基础上修改。 + 2. 在 `plugin-test/package.json` 的 `scripts` 中添加 `test:{plugin-name}` 命令 + 3. 如包含 snapshot 测试,使用 `global.updateSnapshot` 全局变量判断是否刷新 snapshot +3. 更新 License 信息: + 1. 添加 `plugin-test/{插件名}/README.md` 文件,添加测试用例来源仓库的 License 信息。 + 2. 更新 `plugin-test/README.md` 文件,添加测试用例来源仓库的链接和感谢信息。 diff --git a/website/docs/zh/contribute/development/testing.mdx b/website/docs/zh/contribute/development/testing.mdx new file mode 100644 index 00000000000..c42ecbf766e --- /dev/null +++ b/website/docs/zh/contribute/development/testing.mdx @@ -0,0 +1,77 @@ +# 测试 + +由于 Rspack 使用 Rust + TypeScript 代码混合编写,因此会针对两者使用不同的测试方案。 + +## Rust 测试 + +:::info Tip +Rust 测试仅适用于原子功能的单元测试,无法测试完整构建流程。如需测试完整构建流程,请编写 Node 测试用例 +::: + +### 运行 Rust 测试 + +通过 `./x test rust` 或者 `cargo test` 可运行 Rust 代码中的测试用例。 + +### 编写 Rust 测试 + +用例编写在 Rust 代码内部,用于对当前文件内的函数提供单元测试。如: + +```rust +fn add(a: u8, b: u8) -> u8 { + a + b +} + +#[test] +fn test_add() { + assert_eq!(add(1, 2), 3); +} +``` + +> 更多信息请参考:[Rust: How to Write Tests](https://doc.rust-lang.org/book/ch11-01-writing-tests.html) + +## Node 测试 + +Node 测试提供了三组测试套件: + +- **Webpack 测试**:使用 Rspack 运行 Webpack 的测试用例 +- **Webpack Plugin 测试**:使用 Rspack 运行部分内置支持的 Webpack 插件的测试用例 +- **Rspack 测试**:运行 Rspack 自身的测试用例 + +### Webpack 测试 + +Rspack 将整个 Webpack 测试套件拷贝到 `webpack-test` 文件夹中,用于检查 webpack 的兼容性。 如果您需要添加新的用例,建议首先在这个文件夹下查找相应的用例是否存在。移除文件夹中的 `test.filter.js` 或修改其返回值为 `true` 即可启用该用例。 + +通过在根文件夹下运行 `./x test webpack` 或 `pnpm run test:webpack` 即可运行 Webpack 测试。 + +:::warning 注意 +如果您遇到问题,请不要修改 Webpack 用例的原始代码。你可以拷贝用例代码,并遵循 [Rspack 测试](./testing-rspack) 来创建新的用例。 +::: + +> 更多细节请参考:[Webpack 测试](./testing-webpack)。 + +### Rspack 测试 + +Rspack 的测试用例存放在在 `packages/rspack-test-tools/tests` 文件夹下,包括独有的测试用例,以及需要修改的 Webpack 和 Webpack 插件的用例。 + +通过在根文件夹下运行 `./x test unit` 或 `pnpm run test:unit` 即可运行 Rspack 测试。 + +也可以进入 `packages/rspack-test-tools` 文件夹并运行 `npm run test` 来运行测试用例,并且对测试流程进行更精细的控制: + +- **需要刷新测试快照时**:添加 `-u` 参数,如 `npm run test -- -u` +- **需要过滤测试用例时**:添加 `-t` 参数,如 `npm run test -- -t config/asset` 即可仅运行 `packages/rspack-test-tools/configCases/asset` 文件夹下的用例(`config` 会自动映射到 `configCases`,其他文件夹类似)。匹配支持正则,详见 [jest](https://jestjs.io/docs/cli#--testnamepatternregex) + +> 更多细节请参考:[Rspack 测试](./testing-rspack) + +### Webpack 插件测试 + +基于实现差异和性能考虑,Rspack 会将部分 Webpack 生态插件内置。同样会将插件的测试套件拷贝到 `plugin-test` 文件夹下,用于测试插件的兼容性。 + +通过在根文件夹下运行 `./x test plugin` 或 `pnpm run test:plugin` 即可运行 Webpack 插件测试。 + +:::warning 注意 +通常情况下,即使您修改了对应插件的功能,也仅需要遵循 [Rspack 测试](./testing-rspack) 来添加新的用例。 + +仅当您内置实现了新的 Webpack 插件并需要拷贝它的测试用例时,可将用例添加到此测试中。 +::: + +> 更多细节请参考:[Webpack 插件测试](./testing-webpack)。