Skip to content

Commit 24ccec0

Browse files
authored
Add test for output determinism (#86737)
These PRs are needed to get the test to pass - [x] #86736 - [x] #86727 - [x] #86769 - [x] #86865
1 parent accce89 commit 24ccec0

File tree

13 files changed

+165
-20
lines changed

13 files changed

+165
-20
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
'use client'
2+
3+
export function Client() {
4+
return 'Client Reference'
5+
}
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
import { Client } from './client'
2+
13
export default function Page() {
2-
return 'app-page (node)'
4+
return (
5+
<>
6+
app-page (node)
7+
<Client />
8+
</>
9+
)
310
}
Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
11
export function GET() {
22
return new Response('app-route (node)')
33
}
4-
5-
export const dynamic = 'force-dynamic'
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default function Page() {
2+
return 'app-page (edge)'
3+
}
4+
5+
export const runtime = 'edge'
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Page() {
2+
return 'app-page (node)'
3+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export function GET() {
2+
return new Response('app-route (edge)')
3+
}
4+
5+
export const runtime = 'edge'
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export function GET() {
2+
return new Response('app-route (node)')
3+
}
4+
5+
export const dynamic = 'force-dynamic'
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default function Layout({ children }) {
2+
return children
3+
}
4+
5+
export const dynamic = 'force-dynamic'

test/production/deterministic-build/app/layout.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,3 @@ export default function Layout({ children }) {
55
</html>
66
)
77
}
8-
9-
export const dynamic = 'force-dynamic'
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { FileRef, NextInstance, nextTestSetup } from 'e2e-utils'
2+
import path from 'path'
3+
import { promisify } from 'util'
4+
5+
import globOrig from 'glob'
6+
import { diff } from 'jest-diff'
7+
const glob = promisify(globOrig)
8+
9+
// These are cosmetic files which aren't deployed.
10+
const IGNORE = /trace|trace-build/
11+
12+
async function readFiles(next: NextInstance) {
13+
const files = (
14+
(await glob('**/*', {
15+
cwd: path.join(next.testDir, next.distDir),
16+
nodir: true,
17+
})) as string[]
18+
)
19+
.filter((f) => !IGNORE.test(f))
20+
.sort()
21+
22+
return Promise.all(
23+
files.map(async (filePath) => {
24+
const content = next.readFileSync(path.join(next.distDir, filePath))
25+
return [filePath, content] as const
26+
})
27+
)
28+
}
29+
30+
// TODO we need to fix these case
31+
// - static/chunks client chunks are content hashed and have the deployment id inlined
32+
const IGNORE_NAME = /^static\/chunks\//
33+
const IGNORE_CONTENT = new RegExp(
34+
[
35+
// These contain content-hashed browser or edge chunk urls (including the deployment id query param)
36+
'.*\\.html',
37+
'.*\\.rsc',
38+
'page_client-reference-manifest\\.js',
39+
// These contain the content-hashed browser chunk names (but they might not actually be deployed in the serverless function)
40+
'_buildManifest\\.js',
41+
'build-manifest\\.json',
42+
'client-build-manifest\\.json',
43+
'fallback-build-manifest\\.json',
44+
'middleware-build-manifest\\.js',
45+
]
46+
.map((v) => '(?:\\/|^)' + v + '$')
47+
.join('|')
48+
)
49+
50+
// Webpack itself isn't deterministic
51+
;(process.env.IS_TURBOPACK_TEST ? describe : describe.skip)(
52+
'deterministic build - changing deployment id',
53+
() => {
54+
const { next } = nextTestSetup({
55+
files: {
56+
app: new FileRef(path.join(__dirname, 'app')),
57+
pages: new FileRef(path.join(__dirname, 'pages')),
58+
// TODO constant generateBuildId isn't entirely representative of the real world
59+
'next.config.js': `module.exports = {
60+
generateBuildId: async () => 'default-build-id',
61+
// Enable these when debugging to get readable diffs
62+
// experimental: {
63+
// turbopackMinify: false,
64+
// turbopackModuleIds: 'named',
65+
// turbopackScopeHoisting: false,
66+
// },
67+
}`,
68+
},
69+
env: {
70+
NOW_BUILDER: '1',
71+
},
72+
skipStart: true,
73+
})
74+
75+
it('should produce identical build outputs even when changing deployment id', async () => {
76+
// First build
77+
next.env['NEXT_DEPLOYMENT_ID'] = 'foo-dpl-id'
78+
await next.build()
79+
let run1 = await readFiles(next)
80+
81+
// Second build
82+
next.env['NEXT_DEPLOYMENT_ID'] = 'bar-dpl-id'
83+
await next.build()
84+
let run2 = await readFiles(next)
85+
86+
run1 = run1.filter(([f, _]) => !IGNORE_NAME.test(f))
87+
run2 = run2.filter(([f, _]) => !IGNORE_NAME.test(f))
88+
89+
// First, compare file names
90+
let run1FileNames = run1.map(([f, _]) => f)
91+
let run2FileNames = run2.map(([f, _]) => f)
92+
expect(run1FileNames).toEqual(run2FileNames)
93+
94+
// Then, compare the file contents
95+
run1 = run1.filter(([f, _]) => !IGNORE_CONTENT.test(f))
96+
run2 = run2.filter(([f, _]) => !IGNORE_CONTENT.test(f))
97+
98+
let run1Map = new Map(run1)
99+
let run2Map = new Map(run2)
100+
101+
let errors = []
102+
for (const [fileName, content1] of run1Map) {
103+
const content2 = run2Map.get(fileName)
104+
if (content1 !== content2) {
105+
errors.push(
106+
`File content mismatch for ${fileName}\n\n` +
107+
diff(content1, content2)
108+
)
109+
}
110+
}
111+
if (errors.length > 0) {
112+
throw new Error(errors.join('\n\n'))
113+
}
114+
})
115+
}
116+
)

0 commit comments

Comments
 (0)