Skip to content

Commit 44a2b03

Browse files
committed
fix root-ownership race conditions in meta-test
Currently all of our tests verify on teardown that there are no root-owned files in the cache. However, owing to some race conditions and slippery stream event deferral behavior that won't be fixed until v7, occasionally cacache's chown doesn't get processed until _after_ the promise resolves and the test ends. As a result, sometimes this check occurs before the chown has happened, resulting in flaky hard-to-reproduce failures. The somewhat-kludgey solution here is to move the ownership check from t.teardown to process.on('exit'). In npm v7, we should move it back to t.teardown, because we should never have a test that resolves in such a way as to leave the cache in an invalid state. PR-URL: #262 Credit: @isaacs Close: #262 Reviewed-by: @isaacs
1 parent dcff367 commit 44a2b03

File tree

2 files changed

+43
-21
lines changed

2 files changed

+43
-21
lines changed

test/common-tap.js

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -60,29 +60,31 @@ const find = require('which').sync('find')
6060
require('tap').teardown(() => {
6161
// work around windows folder locking
6262
process.chdir(returnCwd)
63-
try {
64-
if (isSudo) {
65-
// running tests as sudo. ensure we didn't leave any root-owned
66-
// files in the cache by mistake.
67-
const args = [ commonCache, '-uid', '0' ]
68-
const found = spawnSync(find, args)
69-
const output = found && found.stdout && found.stdout.toString()
70-
if (output.length) {
71-
const er = new Error('Root-owned files left in cache!')
72-
er.testName = main
73-
er.files = output.trim().split('\n')
74-
throw er
63+
process.on('exit', () => {
64+
try {
65+
if (isSudo) {
66+
// running tests as sudo. ensure we didn't leave any root-owned
67+
// files in the cache by mistake.
68+
const args = [ commonCache, '-uid', '0' ]
69+
const found = spawnSync(find, args)
70+
const output = found && found.stdout && found.stdout.toString()
71+
if (output.length) {
72+
const er = new Error('Root-owned files left in cache!')
73+
er.testName = main
74+
er.files = output.trim().split('\n')
75+
throw er
76+
}
77+
}
78+
if (!process.env.NO_TEST_CLEANUP) {
79+
rimraf.sync(exports.pkg)
80+
rimraf.sync(commonCache)
81+
}
82+
} catch (e) {
83+
if (process.platform !== 'win32') {
84+
throw e
7585
}
7686
}
77-
if (!process.env.NO_TEST_CLEANUP) {
78-
rimraf.sync(exports.pkg)
79-
rimraf.sync(commonCache)
80-
}
81-
} catch (e) {
82-
if (process.platform !== 'win32') {
83-
throw e
84-
}
85-
}
87+
})
8688
})
8789

8890
var port = exports.port = 15443 + testId
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const t = require('tap')
2+
if (!process.getuid || process.getuid() !== 0 || !process.env.SUDO_UID || !process.env.SUDO_GID) {
3+
t.pass('this test only runs in sudo mode')
4+
t.end()
5+
process.exit(0)
6+
}
7+
8+
const common = require('../common-tap.js')
9+
const fs = require('fs')
10+
const mkdirp = require('mkdirp')
11+
mkdirp.sync(common.cache + '/root/owned')
12+
fs.writeFileSync(common.cache + '/root/owned/file.txt', 'should be chowned')
13+
const chown = require('chownr')
14+
15+
// this will fire after t.teardown() but before process.on('exit')
16+
setTimeout(() => {
17+
chown.sync(common.cache, +process.env.SUDO_UID, +process.env.SUDO_GID)
18+
}, 100)
19+
20+
t.pass('this is fine')

0 commit comments

Comments
 (0)