Skip to content

Commit af2e699

Browse files
Add test.debug to integration tests (#14133)
While working on #14078, there were a couple of debugging techniques that we were using quite frequently: - Being able to `cd` into the test setup - Seeing the stdio and stdout data in real-time (this currently requires us to mark a test as failing) - Checking the exact commands that are being run Since we naturally worked around this quite often, I decided to make this a first-level API with the introduction of a new `test.debug` flag. When set, it will: - Create the test setup in the project dir within a new `.debug` folder and won't delete it after the run. Having it in an explicit folder allows us to easily delete it manually when we need to. - Logs all run commands to the console (`>` for a sync call, `>&` for a spawned process) - Logs stdio and stderr to the console in real time. - Run the test as `.only` <img width="2267" alt="Screenshot 2024-08-06 at 13 19 49" src="https://github.com/user-attachments/assets/1b204ac2-feee-489e-9cd8-edf73c0f2abd"> --------- Co-authored-by: Robin Malfait <[email protected]>
1 parent 541d84a commit af2e699

File tree

2 files changed

+88
-55
lines changed

2 files changed

+88
-55
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ playwright-report/
77
blob-report/
88
playwright/.cache/
99
target/
10+
.debug

integrations/utils.ts

Lines changed: 87 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ interface TestContext {
4141
}
4242
}
4343
type TestCallback = (context: TestContext) => Promise<void> | void
44+
interface TestFlags {
45+
only?: boolean
46+
debug?: boolean
47+
}
4448

4549
type SpawnActor = { predicate: (message: string) => boolean; resolve: () => void }
4650

@@ -51,70 +55,41 @@ export function test(
5155
name: string,
5256
config: TestConfig,
5357
testCallback: TestCallback,
54-
{ only = false } = {},
58+
{ only = false, debug = false }: TestFlags = {},
5559
) {
5660
return (only ? defaultTest.only : defaultTest)(
5761
name,
5862
{ timeout: TEST_TIMEOUT },
5963
async (options) => {
60-
let root = await fs.mkdtemp(
61-
// On Windows CI, tmpdir returns a path containing a weird RUNNER~1 folder
62-
// that apparently causes the vite builds to not work.
63-
path.join(
64-
process.env.CI && platform() === 'win32' ? homedir() : tmpdir(),
65-
'tailwind-integrations',
66-
),
67-
)
68-
69-
async function write(filename: string, content: string): Promise<void> {
70-
let full = path.join(root, filename)
71-
72-
if (filename.endsWith('package.json')) {
73-
content = await overwriteVersionsInPackageJson(content)
74-
}
75-
76-
// Ensure that files written on Windows use \r\n line ending
77-
if (platform() === 'win32') {
78-
content = content.replace(/\n/g, '\r\n')
79-
}
80-
81-
let dir = path.dirname(full)
82-
await fs.mkdir(dir, { recursive: true })
83-
await fs.writeFile(full, content)
84-
}
85-
86-
for (let [filename, content] of Object.entries(config.fs)) {
87-
await write(filename, content)
64+
let rootDir = debug
65+
? path.join(REPO_ROOT, '.debug')
66+
: // On Windows CI, tmpdir returns a path containing a weird RUNNER~1
67+
// folder that apparently causes the vite builds to not work.
68+
process.env.CI && platform() === 'win32'
69+
? homedir()
70+
: tmpdir()
71+
await fs.mkdir(rootDir, { recursive: true })
72+
73+
let root = await fs.mkdtemp(path.join(rootDir, 'tailwind-integrations'))
74+
75+
if (debug) {
76+
console.log('Running test in debug mode. File system will be written to:')
77+
console.log(root)
78+
console.log()
8879
}
8980

90-
try {
91-
execSync('pnpm install', { cwd: root })
92-
} catch (error: any) {
93-
console.error(error.stdout.toString())
94-
console.error(error.stderr.toString())
95-
throw error
96-
}
97-
98-
let disposables: (() => Promise<void>)[] = []
99-
100-
async function dispose() {
101-
await Promise.all(disposables.map((dispose) => dispose()))
102-
try {
103-
await fs.rm(root, { recursive: true, maxRetries: 5, force: true })
104-
} catch (err) {
105-
if (!process.env.CI) {
106-
throw err
107-
}
108-
}
109-
}
110-
111-
options.onTestFinished(dispose)
112-
11381
let context = {
11482
root,
11583
async exec(command: string, childProcessOptions: ChildProcessOptions = {}) {
84+
let cwd = childProcessOptions.cwd ?? root
85+
if (debug && cwd !== root) {
86+
let relative = path.relative(root, cwd)
87+
if (relative[0] !== '.') relative = `./${relative}`
88+
console.log(`> cd ${relative}`)
89+
}
90+
if (debug) console.log(`> ${command}`)
11691
return execSync(command, {
117-
cwd: root,
92+
cwd,
11893
stdio: 'pipe',
11994
...childProcessOptions,
12095
}).toString()
@@ -127,8 +102,15 @@ export function test(
127102
rejectDisposal = reject
128103
})
129104

105+
let cwd = childProcessOptions.cwd ?? root
106+
if (debug && cwd !== root) {
107+
let relative = path.relative(root, cwd)
108+
if (relative[0] !== '.') relative = `./${relative}`
109+
console.log(`> cd ${relative}`)
110+
}
111+
if (debug) console.log(`>& ${command}`)
130112
let child = spawn(command, {
131-
cwd: root,
113+
cwd,
132114
shell: true,
133115
env: {
134116
...process.env,
@@ -177,12 +159,14 @@ export function test(
177159

178160
child.stdout.on('data', (result) => {
179161
let content = result.toString()
162+
if (debug) console.log(content)
180163
combined.push(['stdout', content])
181164
stdoutMessages.push(content)
182165
notifyNext(stdoutActors, stdoutMessages)
183166
})
184167
child.stderr.on('data', (result) => {
185168
let content = result.toString()
169+
if (debug) console.error(content)
186170
combined.push(['stderr', content])
187171
stderrMessages.push(content)
188172
notifyNext(stderrActors, stderrMessages)
@@ -195,6 +179,9 @@ export function test(
195179
})
196180

197181
options.onTestFailed(() => {
182+
// In debug mode, messages are logged to the console immediatly
183+
if (debug) return
184+
198185
for (let [type, message] of combined) {
199186
if (type === 'stdout') {
200187
console.log(message)
@@ -253,7 +240,22 @@ export function test(
253240
})
254241
},
255242
fs: {
256-
write,
243+
async write(filename: string, content: string): Promise<void> {
244+
let full = path.join(root, filename)
245+
246+
if (filename.endsWith('package.json')) {
247+
content = await overwriteVersionsInPackageJson(content)
248+
}
249+
250+
// Ensure that files written on Windows use \r\n line ending
251+
if (platform() === 'win32') {
252+
content = content.replace(/\n/g, '\r\n')
253+
}
254+
255+
let dir = path.dirname(full)
256+
await fs.mkdir(dir, { recursive: true })
257+
await fs.writeFile(full, content)
258+
},
257259
read(filePath: string) {
258260
return fs.readFile(path.resolve(root, filePath), 'utf8')
259261
},
@@ -277,13 +279,43 @@ export function test(
277279
},
278280
} satisfies TestContext
279281

282+
for (let [filename, content] of Object.entries(config.fs)) {
283+
await context.fs.write(filename, content)
284+
}
285+
286+
try {
287+
context.exec('pnpm install')
288+
} catch (error: any) {
289+
console.error(error)
290+
throw error
291+
}
292+
293+
let disposables: (() => Promise<void>)[] = []
294+
295+
async function dispose() {
296+
await Promise.all(disposables.map((dispose) => dispose()))
297+
try {
298+
if (debug) return
299+
await fs.rm(root, { recursive: true, maxRetries: 5, force: true })
300+
} catch (err) {
301+
if (!process.env.CI) {
302+
throw err
303+
}
304+
}
305+
}
306+
307+
options.onTestFinished(dispose)
308+
280309
await testCallback(context)
281310
},
282311
)
283312
}
284313
test.only = (name: string, config: TestConfig, testCallback: TestCallback) => {
285314
return test(name, config, testCallback, { only: true })
286315
}
316+
test.debug = (name: string, config: TestConfig, testCallback: TestCallback) => {
317+
return test(name, config, testCallback, { only: true, debug: true })
318+
}
287319

288320
// Maps package names to their tarball filenames. See scripts/pack-packages.ts
289321
// for more details.

0 commit comments

Comments
 (0)