test: add integration tests for ChunkGraph and ChunkGroup integrity#20772
test: add integration tests for ChunkGraph and ChunkGroup integrity#20772alexander-akait merged 6 commits intowebpack:mainfrom
Conversation
|
There was a problem hiding this comment.
Pull request overview
Adds a new integration-level test to exercise core ChunkGraph / ChunkGroup graph-manipulation APIs during the compilation.optimize phase, aiming to validate integrity when plugins mutate graph relationships inside build hooks.
Changes:
- Introduces
test/GraphIntegrity.test.jsto run a production compilation againsttest/fixturesand mutateChunkGraphduringoptimize. - Adds assertions around
ChunkGraph.replaceModule,connectChunkAndModule, anddisconnectChunkAndModule. - Touches
ChunkGroupby invokinggetChildren()during the same optimization hook.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| chunkGraph.replaceModule(oldModule, newModule); | ||
|
|
||
| const hasNew = chunkGraph.isModuleInChunk(newModule, chunk); | ||
| const hasOld = chunkGraph.isModuleInChunk(oldModule, chunk); | ||
|
|
||
| if (!hasNew || hasOld) { | ||
| throw new Error("Module replacement failed in ChunkGraph"); | ||
| } | ||
|
|
||
| chunkGraph.connectChunkAndModule(chunk, oldModule); | ||
| chunkGraph.disconnectChunkAndModule(chunk, oldModule); | ||
|
|
||
| const [group] = compilation.chunkGroups; | ||
| if (group) { | ||
| group.getChildren(); |
There was a problem hiding this comment.
replaceModule(oldModule, newModule) replaces a real compilation module with a plain object that is not part of compilation.modules. After this hook, later phases (e.g. chunk hashing via chunk.updateHash → chunkGraph.getModuleHash) will try to access hash info for newModule and will throw because no hashes were ever set for it. This makes the test (and potentially the build) fail/flaky. Use an actual Module instance that participates in the compilation (or perform the replace + assertions + full revert within the optimize hook so the compilation continues with a valid graph), and consider calling compilation.checkConstraints() after the mutation to validate integrity.
| chunkGraph.replaceModule(oldModule, newModule); | |
| const hasNew = chunkGraph.isModuleInChunk(newModule, chunk); | |
| const hasOld = chunkGraph.isModuleInChunk(oldModule, chunk); | |
| if (!hasNew || hasOld) { | |
| throw new Error("Module replacement failed in ChunkGraph"); | |
| } | |
| chunkGraph.connectChunkAndModule(chunk, oldModule); | |
| chunkGraph.disconnectChunkAndModule(chunk, oldModule); | |
| const [group] = compilation.chunkGroups; | |
| if (group) { | |
| group.getChildren(); | |
| try { | |
| chunkGraph.replaceModule(oldModule, newModule); | |
| const hasNew = chunkGraph.isModuleInChunk(newModule, chunk); | |
| const hasOld = chunkGraph.isModuleInChunk(oldModule, chunk); | |
| if (!hasNew || hasOld) { | |
| throw new Error("Module replacement failed in ChunkGraph"); | |
| } | |
| const [group] = compilation.chunkGroups; | |
| if (group) { | |
| group.getChildren(); | |
| } | |
| } finally { | |
| chunkGraph.replaceModule(newModule, oldModule); | |
| chunkGraph.connectChunkAndModule(chunk, oldModule); | |
| chunkGraph.disconnectChunkAndModule(chunk, oldModule); | |
| compilation.checkConstraints(); |
| const modules = [...chunkGraph.getChunkModulesIterable(chunk)]; | ||
|
|
||
| if (modules.length >= 1) { | ||
| const oldModule = modules[0]; | ||
| const newModule = { | ||
| identifier: () => "mock-module-identifier", | ||
| getSourceTypes: () => ["javascript"], | ||
| size: () => 0, | ||
| identifierIndex: 0 | ||
| }; | ||
|
|
||
| chunkGraph.replaceModule(oldModule, newModule); | ||
|
|
||
| const hasNew = chunkGraph.isModuleInChunk(newModule, chunk); | ||
| const hasOld = chunkGraph.isModuleInChunk(oldModule, chunk); | ||
|
|
||
| if (!hasNew || hasOld) { | ||
| throw new Error("Module replacement failed in ChunkGraph"); | ||
| } | ||
|
|
||
| chunkGraph.connectChunkAndModule(chunk, oldModule); | ||
| chunkGraph.disconnectChunkAndModule(chunk, oldModule); | ||
|
|
||
| const [group] = compilation.chunkGroups; | ||
| if (group) { | ||
| group.getChildren(); | ||
| } |
There was a problem hiding this comment.
The if (modules.length >= 1) guard allows this test to become a no-op (and still pass) if no modules are present or if chunk resolves unexpectedly. Since this is an integration integrity test, it should fail loudly when preconditions aren’t met (e.g., assert a non-empty module list / expected module count) so the test actually validates behavior.
| const modules = [...chunkGraph.getChunkModulesIterable(chunk)]; | |
| if (modules.length >= 1) { | |
| const oldModule = modules[0]; | |
| const newModule = { | |
| identifier: () => "mock-module-identifier", | |
| getSourceTypes: () => ["javascript"], | |
| size: () => 0, | |
| identifierIndex: 0 | |
| }; | |
| chunkGraph.replaceModule(oldModule, newModule); | |
| const hasNew = chunkGraph.isModuleInChunk(newModule, chunk); | |
| const hasOld = chunkGraph.isModuleInChunk(oldModule, chunk); | |
| if (!hasNew || hasOld) { | |
| throw new Error("Module replacement failed in ChunkGraph"); | |
| } | |
| chunkGraph.connectChunkAndModule(chunk, oldModule); | |
| chunkGraph.disconnectChunkAndModule(chunk, oldModule); | |
| const [group] = compilation.chunkGroups; | |
| if (group) { | |
| group.getChildren(); | |
| } | |
| if (!chunk) { | |
| throw new Error("Expected compilation to contain at least one chunk"); | |
| } | |
| const modules = [...chunkGraph.getChunkModulesIterable(chunk)]; | |
| if (modules.length < 1) { | |
| throw new Error("Expected chunk to contain at least one module"); | |
| } | |
| const oldModule = modules[0]; | |
| const newModule = { | |
| identifier: () => "mock-module-identifier", | |
| getSourceTypes: () => ["javascript"], | |
| size: () => 0, | |
| identifierIndex: 0 | |
| }; | |
| chunkGraph.replaceModule(oldModule, newModule); | |
| const hasNew = chunkGraph.isModuleInChunk(newModule, chunk); | |
| const hasOld = chunkGraph.isModuleInChunk(oldModule, chunk); | |
| if (!hasNew || hasOld) { | |
| throw new Error("Module replacement failed in ChunkGraph"); | |
| } | |
| chunkGraph.connectChunkAndModule(chunk, oldModule); | |
| chunkGraph.disconnectChunkAndModule(chunk, oldModule); | |
| const [group] = compilation.chunkGroups; | |
| if (group) { | |
| group.getChildren(); |
|
|
||
| const [group] = compilation.chunkGroups; | ||
| if (group) { | ||
| group.getChildren(); |
There was a problem hiding this comment.
Calling group.getChildren() without any assertion doesn’t validate ChunkGroup integrity, and it won’t catch parent/child linkage issues (the method just returns a value). If the goal is integrity checking, use an invariant check (e.g. group.checkConstraints()) or assert expected parent/child relationships so failures are actionable.
| group.getChildren(); | |
| group.checkConstraints(); |
|
@alexander-akait I have addressed the co-pilot review .can u please take a view Thanks! |
| }); | ||
| }); | ||
| }, 60000); | ||
| }); |
There was a problem hiding this comment.
it is possible to rewrite this test to configCases? You can write a small custom plugin which make the same, I don't like unit tests for such cases because it is hard to maintain and hard to catch the real errors/problems
|
@alexander-akait thanks for the suggestion i have completely removed the unit test and migrated the logic to configCases/chunk-graph/graph-integrity.also it increase the line coverage from 26% to 38% Please take a view Thanks |
Merging this PR will degrade performance by 44.89%
Performance Changes
Comparing |
|
@aryanraj45 looks good let's fix lint and we can merge |
| size: () => 0 | ||
| }; | ||
|
|
||
| chunkGraph.replaceModule(m, mock); |
There was a problem hiding this comment.
you can use // @ts-expect-error for tests comment to avoid types problems
There was a problem hiding this comment.
@alexander-akait fixed the lint and added the comment please take a view thanks!
There was a problem hiding this comment.
Please always add for tests, we have an eslint rule that requires to write comment why it is expected
There was a problem hiding this comment.
done please take a look thanks!
|
@alexander-akait all the required CI passed! |
|
This PR is packaged and the instant preview is available (5310112). Install it locally:
npm i -D webpack@https://pkg.pr.new/webpack@5310112
yarn add -D webpack@https://pkg.pr.new/webpack@5310112
pnpm add -D webpack@https://pkg.pr.new/webpack@5310112 |
Summary
Added integration tests for core graph integrity methods in ChunkGraph.js and lib/ChunkGroup.js. Methods like replaceModule, connectChunkAndModule, and disconnectChunkAndModule were previously hit by high-level tests but had gaps in verifying specific manual manipulation safety during build hooks.
The new suite in test/GraphIntegrity.test.js ensures that graph state remains valid when modules are re-mapped or disconnected within the
optimizephase. Standalone coverage for lib/ChunkGraph.js jumped by ~26%.What kind of change does this PR introduce?
test
Did you add tests for your changes?
Yes, integrated test/GraphIntegrity.test.js.
Does this PR introduce a breaking change?
No.
If relevant, what needs to be documented once your changes are merged or what have you already documented?
N/A.
Use of AI
None