From ae6ce86fc90921cc6bb03184a6666a02226ad31e Mon Sep 17 00:00:00 2001 From: Ryan Zimmerman Date: Thu, 20 Aug 2026 15:41:48 -0400 Subject: [PATCH 1/3] Use spawn to fix zombie processes from tests --- test/watch.js | 90 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 66 insertions(+), 24 deletions(-) diff --git a/test/watch.js b/test/watch.js index 9895a19..c667d1b 100644 --- a/test/watch.js +++ b/test/watch.js @@ -2,7 +2,7 @@ import test from 'ava' import fs from 'node:fs/promises' import path from 'path' -import { exec, spawn } from 'child_process' +import { spawn } from 'child_process' import chokidar from 'chokidar' import ENV from './helpers/env.js' @@ -49,9 +49,17 @@ testCb('--watch works', (t) => { // Start postcss-cli: watcher.on('ready', () => { - // Using exec() and quoting "*.css" to test watch's glob handling: - cp = exec( - `node ${path.resolve('index.js')} "*.css" -o output.css --no-map -w`, + cp = spawn( + 'node', + [ + path.resolve('index.js'), + // '*.css' arrives as a single literal arg to test watch's glob handling + '*.css', + '-o', + 'output.css', + '--no-map', + '-w', + ], { cwd: dir }, ) cp.on('error', t.end) @@ -114,10 +122,18 @@ testCb('--watch dependencies', (t) => { // Start postcss-cli: watcher.on('ready', () => { - cp = exec( - `node ${path.resolve( - 'index.js', - )} import.css -o output.css -u postcss-import -w --no-map`, + cp = spawn( + 'node', + [ + path.resolve('index.js'), + 'import.css', + '-o', + 'output.css', + '-u', + 'postcss-import', + '-w', + '--no-map', + ], { cwd: dir }, ) @@ -166,10 +182,18 @@ testCb('--watch dependencies', (t) => { }) let killed = false - const cp = exec( - `node ${path.resolve( - 'index.js', - )} a.css -o output.css -u postcss-import -w --no-map`, + const cp = spawn( + 'node', + [ + path.resolve('index.js'), + 'a.css', + '-o', + 'output.css', + '-u', + 'postcss-import', + '-w', + '--no-map', + ], { cwd: dir }, ) cp.on('error', t.end) @@ -278,10 +302,16 @@ testCb('--watch watches dependencies', (t) => { // Start postcss-cli: watcher.on('ready', () => { - cp = exec( - `node ${path.resolve( - 'index.js', - )} "s.css" -o output.css --no-map -w`, + cp = spawn( + 'node', + [ + path.resolve('index.js'), + 's.css', + '-o', + 'output.css', + '--no-map', + '-w', + ], { cwd: dir }, ) cp.on('error', t.end) @@ -375,10 +405,16 @@ testCb('--watch watches directory dependencies', (t) => { // Start postcss-cli: watcher.on('ready', () => { - cp = exec( - `node ${path.resolve( - 'index.js', - )} "s.css" -o output.css --no-map -w`, + cp = spawn( + 'node', + [ + path.resolve('index.js'), + 's.css', + '-o', + 'output.css', + '--no-map', + '-w', + ], { cwd: dir }, ) cp.on('error', t.end) @@ -480,10 +516,16 @@ testCb( // Start postcss-cli: watcher.on('ready', () => { - cp = exec( - `node ${path.resolve( - 'index.js', - )} "s.css" -o output.css --no-map -w`, + cp = spawn( + 'node', + [ + path.resolve('index.js'), + 's.css', + '-o', + 'output.css', + '--no-map', + '-w', + ], { cwd: dir }, ) cp.on('error', t.end) From e69997cb16dc5472032c1a83fb61a8d7dd379516 Mon Sep 17 00:00:00 2001 From: Ryan Zimmerman Date: Thu, 20 Aug 2026 15:52:35 -0400 Subject: [PATCH 2/3] Don't use shell when not needed --- test/watch.js | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/test/watch.js b/test/watch.js index c667d1b..d3e75c9 100644 --- a/test/watch.js +++ b/test/watch.js @@ -227,13 +227,23 @@ testCb('--watch dependencies', (t) => { testCb('--watch does exit on closing stdin (Ctrl-D/EOF)', (t) => { t.plan(1) - const cp = spawn(`./index.js test/fixtures/a.css -o ${tmp()} -w --no-map`, { - shell: true, - env: { - ...process.env, - FORCE_IS_TTY: true, + const cp = spawn( + 'node', + [ + path.resolve('index.js'), + 'test/fixtures/a.css', + '-o', + tmp(), + '-w', + '--no-map', + ], + { + env: { + ...process.env, + FORCE_IS_TTY: true, + }, }, - }) + ) cp.on('error', t.end) cp.on('exit', (code) => { From 68d4df41e19e86c7209ac752aa5e045dba4a7a20 Mon Sep 17 00:00:00 2001 From: Ryan Zimmerman Date: Thu, 20 Aug 2026 16:25:32 -0400 Subject: [PATCH 3/3] Run all watch tests on CI --- test/watch.js | 111 ++++++++++++++++++++++++-------------------------- 1 file changed, 53 insertions(+), 58 deletions(-) diff --git a/test/watch.js b/test/watch.js index d3e75c9..915ac46 100644 --- a/test/watch.js +++ b/test/watch.js @@ -160,69 +160,64 @@ testCb('--watch dependencies', (t) => { .catch(t.end) }) -// Doesn't work on CI for some reason -;(process.env.CI ? test.cb.skip : test.cb)( - "--watch doesn't exit on CssSyntaxError", - (t) => { - t.plan(0) - - ENV('', ['a.css']) - .then((dir) => { - // Init watcher: - const watcher = chokidar.watch('.', { - cwd: dir, - ignoreInitial: true, - awaitWriteFinish: true, - }) - watcher.on('add', (p) => { - if (p === 'output.css') { - // Change to invalid CSS - fs.writeFile(path.join(dir, 'a.css'), '.a { color: red').catch(done) - } - }) +testCb("--watch doesn't exit on CssSyntaxError", (t) => { + t.plan(0) - let killed = false - const cp = spawn( - 'node', - [ - path.resolve('index.js'), - 'a.css', - '-o', - 'output.css', - '-u', - 'postcss-import', - '-w', - '--no-map', - ], - { cwd: dir }, - ) - cp.on('error', t.end) - cp.stderr.on('data', (chunk) => { - // When error message is printed, kill the process after a timeout - if (~chunk.indexOf('Unclosed block')) { - setTimeout(() => { - killed = true - cp.kill() - }, 1000) - } - }) - cp.on('exit', (code) => { - if (!killed) - return t.end(`Should not exit (exited with code ${code})`) - done() - }) + ENV('', ['a.css']) + .then((dir) => { + // Init watcher: + const watcher = chokidar.watch('.', { + cwd: dir, + ignoreInitial: true, + awaitWriteFinish: true, + }) + watcher.on('add', (p) => { + if (p === 'output.css') { + // Change to invalid CSS + fs.writeFile(path.join(dir, 'a.css'), '.a { color: red').catch(done) + } + }) - function done(err) { - try { + let killed = false + const cp = spawn( + 'node', + [ + path.resolve('index.js'), + 'a.css', + '-o', + 'output.css', + '-u', + 'postcss-import', + '-w', + '--no-map', + ], + { cwd: dir }, + ) + cp.on('error', t.end) + cp.stderr.on('data', (chunk) => { + // When error message is printed, kill the process after a timeout + if (~chunk.indexOf('Unclosed block')) { + setTimeout(() => { + killed = true cp.kill() - } catch {} - - t.end(err) + }, 1000) } }) - .catch(t.end) - }, -) + cp.on('exit', (code) => { + if (!killed) return t.end(`Should not exit (exited with code ${code})`) + done() + }) + + function done(err) { + try { + cp.kill() + } catch {} + + t.end(err) + } + }) + .catch(t.end) +}) testCb('--watch does exit on closing stdin (Ctrl-D/EOF)', (t) => { t.plan(1)