From 147eac4e83001962190f723ca21a70f852c4466c Mon Sep 17 00:00:00 2001 From: isaacs Date: Tue, 11 Feb 2020 10:15:37 -0800 Subject: [PATCH] Full tests, handle errors properly in many cases --- chownr.js | 34 +++--- package.json | 5 +- test/basic.js | 122 ++++++++++------------ test/concurrent-sync.js | 192 ++++++++++++++-------------------- test/concurrent.js | 206 +++++++++++++++---------------------- test/non-handled-errors.js | 139 +++++++++++++++++++++++++ test/old-readdir-sync.js | 85 --------------- test/old-readdir.js | 89 ++-------------- test/sync.js | 115 ++++++++++----------- 9 files changed, 438 insertions(+), 549 deletions(-) create mode 100644 test/non-handled-errors.js delete mode 100644 test/old-readdir-sync.js diff --git a/chownr.js b/chownr.js index 1f84a33..0d40932 100644 --- a/chownr.js +++ b/chownr.js @@ -7,6 +7,7 @@ const LCHOWN = fs.lchown ? 'lchown' : 'chown' /* istanbul ignore next */ const LCHOWNSYNC = fs.lchownSync ? 'lchownSync' : 'chownSync' +/* istanbul ignore next */ const needEISDIRHandled = fs.lchown && !process.version.match(/v1[1-9]+\./) && !process.version.match(/v10\.[6-9]/) @@ -20,6 +21,7 @@ const lchownSync = (path, uid, gid) => { } } +/* istanbul ignore next */ const chownSync = (path, uid, gid) => { try { return fs.chownSync(path, uid, gid) @@ -66,8 +68,7 @@ if (/^v4\./.test(nodeVersion)) const chown = (cpath, uid, gid, cb) => { fs[LCHOWN](cpath, uid, gid, handleEISDIR(cpath, uid, gid, er => { // Skip ENOENT error - if (er && er.code === 'ENOENT') return cb() - cb(er) + cb(er && er.code !== 'ENOENT' ? er : null) })) } @@ -75,9 +76,8 @@ const chownrKid = (p, child, uid, gid, cb) => { if (typeof child === 'string') return fs.lstat(path.resolve(p, child), (er, stats) => { // Skip ENOENT error - if (er && er.code === 'ENOENT') return cb() if (er) - return cb(er) + return cb(er.code !== 'ENOENT' ? er : null) stats.name = child chownrKid(p, stats, uid, gid, cb) }) @@ -100,10 +100,12 @@ const chownr = (p, uid, gid, cb) => { readdir(p, { withFileTypes: true }, (er, children) => { // any error other than ENOTDIR or ENOTSUP means it's not readable, // or doesn't exist. give up. - if (er && er.code !== 'ENOTDIR' && er.code !== 'ENOTSUP') - return cb(er) - if (er && er.code === 'ENOENT') - return cb() + if (er) { + if (er.code === 'ENOENT') + return cb() + else if (er.code !== 'ENOTDIR' && er.code !== 'ENOTSUP') + return cb(er) + } if (er || !children.length) return chown(p, uid, gid, cb) @@ -129,8 +131,10 @@ const chownrKidSync = (p, child, uid, gid) => { stats.name = child child = stats } catch (er) { - if (er.code === 'ENOENT') return - throw er; + if (er.code === 'ENOENT') + return + else + throw er } } @@ -145,13 +149,15 @@ const chownrSync = (p, uid, gid) => { try { children = readdirSync(p, { withFileTypes: true }) } catch (er) { - if (er && er.code === 'ENOTDIR' && er.code !== 'ENOTSUP') + if (er.code === 'ENOENT') + return + else if (er.code === 'ENOTDIR' || er.code === 'ENOTSUP') return handleEISDirSync(p, uid, gid) - if (er && er.code === 'ENOENT') return - throw er + else + throw er } - if (children.length) + if (children && children.length) children.forEach(child => chownrKidSync(p, child, uid, gid)) return handleEISDirSync(p, uid, gid) diff --git a/package.json b/package.json index 5cf89c8..000c1cd 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,11 @@ "rimraf": "^2.7.1", "tap": "^14.10.6" }, + "tap": { + "check-coverage": true + }, "scripts": { - "test": "tap test/*.js --cov", + "test": "tap", "preversion": "npm test", "postversion": "npm publish", "postpublish": "git push origin --follow-tags" diff --git a/test/basic.js b/test/basic.js index bc905f8..3001b80 100644 --- a/test/basic.js +++ b/test/basic.js @@ -2,84 +2,72 @@ if (!process.getuid || !process.getgid) { throw new Error("Tests require getuid/getgid support") } -var curUid = +process.getuid() -, curGid = +process.getgid() -, chownr = require("../") -, test = require("tap").test -, mkdirp = require("mkdirp") -, rimraf = require("rimraf") -, fs = require("fs") +const curUid = +process.getuid() +const curGid = +process.getgid() +const chownr = require("../") +const t = require("tap") +const mkdirp = require("mkdirp") +const rimraf = require("rimraf") +const fs = require("fs") // sniff the 'id' command for other groups that i can legally assign to -var exec = require("child_process").exec -, groups -, dirs = [] +const {exec} = require("child_process") +let groups +let dirs = [] -exec("id", function (code, output) { - if (code) throw new Error("failed to run 'id' command") - groups = output.trim().split("=")[3].split(",").map(function (s) { - return parseInt(s, 10) - }).filter(function (g) { - return g !== curGid - }) - - // console.error([curUid, groups[0]], "uid, gid") - - rimraf("/tmp/chownr", function (er) { - if (er) throw er - var cnt = 5 - for (var i = 0; i < 5; i ++) { - mkdirp(getDir(), then) - } - function then (er) { - if (er) throw er - if (-- cnt === 0) { - runTest() - } - } +t.test('get the ids to use', { bail: true }, t => { + exec("id", function (code, output) { + if (code) throw new Error("failed to run 'id' command") + groups = output.trim().split("=")[3].split(",") + .map(s => parseInt(s, 10)) + .filter(g => g !== curGid) + t.end() }) }) -function getDir () { - var dir = "/tmp/chownr" - - dir += "/" + Math.floor(Math.random() * Math.pow(16,4)).toString(16) - dirs.push(dir) - dir += "/" + Math.floor(Math.random() * Math.pow(16,4)).toString(16) - dirs.push(dir) - dir += "/" + Math.floor(Math.random() * Math.pow(16,4)).toString(16) - dirs.push(dir) - return dir -} +t.test('run test', t => { + const dir = t.testdir({ + a: { b: { c: {}}}, + d: { e: { f: {}}}, + g: { h: { i: {}}}, + j: { k: { l: {}}}, + m: { n: { o: {}}}, + }) -function runTest () { - test("should complete successfully", function (t) { + t.test("should complete successfully", t => { // console.error("calling chownr", curUid, groups[0], typeof curUid, typeof groups[0]) - chownr("/tmp/chownr", curUid, groups[0], function (er) { - t.ifError(er) + chownr(dir, curUid, groups[0], er => { + if (er) + throw er t.end() }) }) - dirs.forEach(function (dir) { - test("verify "+dir, function (t) { - fs.stat(dir, function (er, st) { - if (er) { - t.ifError(er) - return t.end() - } - t.equal(st.uid, curUid, "uid should be " + curUid) - t.equal(st.gid, groups[0], "gid should be "+groups[0]) - t.end() - }) - }) - }) + const dirs = [ + '', + 'a', + 'a/b', + 'a/b/c', + 'd', + 'd/e', + 'd/e/f', + 'g', + 'g/h', + 'g/h/i', + 'j', + 'j/k', + 'j/k/l', + 'm', + 'm/n', + 'm/n/o', + ] - test("cleanup", function (t) { - rimraf("/tmp/chownr/", function (er) { - t.ifError(er) - t.end() + dirs.forEach(d => t.test(`verify ${d}`, t => { + t.match(fs.statSync(`${dir}/${d}`), { + uid: curUid, + gid: groups[0], }) - }) -} - + t.end() + })) + t.end() +}) diff --git a/test/concurrent-sync.js b/test/concurrent-sync.js index 16d63eb..af45e88 100644 --- a/test/concurrent-sync.js +++ b/test/concurrent-sync.js @@ -2,133 +2,93 @@ if (!process.getuid || !process.getgid) { throw new Error("Tests require getuid/getgid support") } -var curUid = +process.getuid() -, curGid = +process.getgid() -, test = require("tap").test -, mkdirp = require("mkdirp") -, rimraf = require("rimraf") -, fs = require("fs") -, path = require("path") +const curUid = +process.getuid() +const curGid = +process.getgid() +const chownr = require("../") +const t = require("tap") +const mkdirp = require("mkdirp") +const rimraf = require("rimraf") +const fs = require("fs") // sniff the 'id' command for other groups that i can legally assign to -var exec = require("child_process").exec -, groups -, dirs = [] -, files = [] - -// Monkey-patch fs.readdirSync to remove f1 before the callback happens -const readdirSync = fs.readdirSync - -var chownr = require("../") - -exec("id", function (code, output) { - if (code) throw new Error("failed to run 'id' command") - groups = output.trim().split("=")[3].split(",").map(function (s) { - return parseInt(s, 10) - }).filter(function (g) { - return g !== curGid - }) - - // console.error([curUid, groups[0]], "uid, gid") - - rimraf("/tmp/chownr", function (er) { - if (er) throw er - var cnt = 5 - for (var i = 0; i < 5; i ++) { - mkdirp(getDir(), then) - } - function then (er, made) { - if (er) throw er - var f1 = path.join(made, "f1"); - files.push(f1) - fs.writeFileSync(f1, "file-1"); - var f2 = path.join(made, "f2"); - files.push(f2) - fs.writeFileSync(f2, "file-2"); - if (-- cnt === 0) { - runTest() - } - } +const {exec} = require("child_process") +let groups + +t.test('get the ids to use', { bail: true }, t => { + exec("id", function (code, output) { + if (code) throw new Error("failed to run 'id' command") + groups = output.trim().split("=")[3].split(",") + .map(s => parseInt(s, 10)) + .filter(g => g !== curGid) + t.end() }) }) -function getDir () { - var dir = "/tmp/chownr" - - dir += "/" + Math.floor(Math.random() * Math.pow(16,4)).toString(16) - dirs.push(dir) - dir += "/" + Math.floor(Math.random() * Math.pow(16,4)).toString(16) - dirs.push(dir) - dir += "/" + Math.floor(Math.random() * Math.pow(16,4)).toString(16) - dirs.push(dir) - return dir -} +t.test('run test', t => { + const dir = t.testdir({ + f1: 'f1', + f2: 'f2', + d1: {}, + d2: {}, + a: { d1: {}, d2: {}, f1: 'f1', f2: 'f2', b: { d1: {}, d2: {}, f1: 'f1', f2: 'f2', c: { d1: {}, d2: {}, f1: 'f1', f2: 'f2' }}}, + d: { d1: {}, d2: {}, f1: 'f1', f2: 'f2', e: { d1: {}, d2: {}, f1: 'f1', f2: 'f2', f: { d1: {}, d2: {}, f1: 'f1', f2: 'f2' }}}, + g: { d1: {}, d2: {}, f1: 'f1', f2: 'f2', h: { d1: {}, d2: {}, f1: 'f1', f2: 'f2', i: { d1: {}, d2: {}, f1: 'f1', f2: 'f2' }}}, + j: { d1: {}, d2: {}, f1: 'f1', f2: 'f2', k: { d1: {}, d2: {}, f1: 'f1', f2: 'f2', l: { d1: {}, d2: {}, f1: 'f1', f2: 'f2' }}}, + m: { d1: {}, d2: {}, f1: 'f1', f2: 'f2', n: { d1: {}, d2: {}, f1: 'f1', f2: 'f2', o: { d1: {}, d2: {}, f1: 'f1', f2: 'f2' }}}, + }) -function runTest () { - test("patch fs.readdirSync", function (t) { - // Monkey-patch fs.readdirSync to remove f1 before returns - // This simulates the case where some files are deleted when chownr.sync - // is in progress - fs.readdirSync = function () { - const args = [].slice.call(arguments) - const dir = args[0] - const children = readdirSync.apply(fs, args); - try { - fs.unlinkSync(path.join(dir, 'f1')) - } catch (er) { - if (er.code !== 'ENOENT') throw er - } + t.test("should complete successfully", { bail: true }, t => { + const readdirSync = fs.readdirSync + fs.readdirSync = (...args) => { + const children = readdirSync(...args) + try { fs.unlinkSync(`${args[0]}/f2`) } catch (_) {} + try { fs.rmdirSync(`${args[0]}/d1`) } catch (_) {} + try { fs.writeFileSync(`${args[0]}/d1`, 'now a file!') } catch (_) {} + try { fs.rmdirSync(`${args[0]}/d2`) } catch (_) {} return children } + t.teardown(() => fs.readdirSync = readdirSync) + chownr.sync(dir, curUid, groups[0]) t.end() }) - test("should complete successfully", function (t) { - // console.error("calling chownr", curUid, groups[0], typeof curUid, typeof groups[0]) - chownr.sync("/tmp/chownr", curUid, groups[0]) - t.end() - }) - - test("restore fs.readdirSync", function (t) { - // Restore fs.readdirSync - fs.readdirSync = readdirSync - t.end() - }) - - dirs.forEach(function (dir) { - test("verify "+dir, function (t) { - fs.stat(dir, function (er, st) { - if (er) { - t.ifError(er) - return t.end() - } - t.equal(st.uid, curUid, "uid should be " + curUid) - t.equal(st.gid, groups[0], "gid should be "+groups[0]) - t.end() - }) + const dirs = [ + '', + 'a', + 'a/b', + 'a/b/c', + 'd', + 'd/e', + 'd/e/f', + 'g', + 'g/h', + 'g/h/i', + 'j', + 'j/k', + 'j/k/l', + 'm', + 'm/n', + 'm/n/o', + ] + + dirs.forEach(d => t.test(`verify ${d}`, t => { + t.match(fs.statSync(`${dir}/${d}`), { + uid: curUid, + gid: groups[0], }) - }) - - files.forEach(function (f) { - test("verify "+f, function (t) { - fs.stat(f, function (er, st) { - if (er) { - if (er.code !== 'ENOENT') - t.ifError(er) - return t.end() - } - t.equal(st.uid, curUid, "uid should be " + curUid) - t.equal(st.gid, groups[0], "gid should be "+groups[0]) - t.end() - }) + t.match(fs.statSync(`${dir}/${d}/f1`), { + uid: curUid, + gid: groups[0], }) - }) - - test("cleanup", function (t) { - rimraf("/tmp/chownr/", function (er) { - t.ifError(er) - t.end() + const st = fs.statSync(`${dir}/${d}/d1`) + t.equal(st.isFile(), true, 'd1 turned into a file') + t.match(st, { + uid: curUid, + gid: groups[0], }) - }) -} - + t.throws(() => fs.statSync(`${dir}/${d}/f2`)) + t.throws(() => fs.statSync(`${dir}/${d}/d2`)) + t.end() + })) + t.end() +}) diff --git a/test/concurrent.js b/test/concurrent.js index 935caa7..4dc746e 100644 --- a/test/concurrent.js +++ b/test/concurrent.js @@ -2,142 +2,102 @@ if (!process.getuid || !process.getgid) { throw new Error("Tests require getuid/getgid support") } -var curUid = +process.getuid() -, curGid = +process.getgid() -, test = require("tap").test -, mkdirp = require("mkdirp") -, rimraf = require("rimraf") -, fs = require("fs") -, path = require("path") +const curUid = +process.getuid() +const curGid = +process.getgid() +const chownr = require("../") +const t = require("tap") +const mkdirp = require("mkdirp") +const rimraf = require("rimraf") +const fs = require("fs") // sniff the 'id' command for other groups that i can legally assign to -var exec = require("child_process").exec -, groups -, dirs = [] -, files = [] - -// Monkey-patch fs.readdir to remove f1 before the callback happens -const readdir = fs.readdir - -var chownr = require("../") - -exec("id", function (code, output) { - if (code) throw new Error("failed to run 'id' command") - groups = output.trim().split("=")[3].split(",").map(function (s) { - return parseInt(s, 10) - }).filter(function (g) { - return g !== curGid - }) - - // console.error([curUid, groups[0]], "uid, gid") - - rimraf("/tmp/chownr", function (er) { - if (er) throw er - var cnt = 5 - for (var i = 0; i < 5; i ++) { - mkdirp(getDir(), then) - } - function then (er, made) { - if (er) throw er - var f1 = path.join(made, "f1"); - files.push(f1) - fs.writeFile(f1, "file-1", er => { - if (er) throw er - var f2 = path.join(made, "f2"); - files.push(f2) - fs.writeFile(f2, "file-2", er => { - if (er) throw er - if (-- cnt === 0) { - runTest() - } - }) - }) - } +const {exec} = require("child_process") +let groups +let dirs = [] + +t.test('get the ids to use', { bail: true }, t => { + exec("id", function (code, output) { + if (code) throw new Error("failed to run 'id' command") + groups = output.trim().split("=")[3].split(",") + .map(s => parseInt(s, 10)) + .filter(g => g !== curGid) + t.end() }) }) -function getDir () { - var dir = "/tmp/chownr" - - dir += "/" + Math.floor(Math.random() * Math.pow(16,4)).toString(16) - dirs.push(dir) - dir += "/" + Math.floor(Math.random() * Math.pow(16,4)).toString(16) - dirs.push(dir) - dir += "/" + Math.floor(Math.random() * Math.pow(16,4)).toString(16) - dirs.push(dir) - return dir -} +t.test('run test', t => { + const dir = t.testdir({ + f1: 'f1', + f2: 'f2', + d1: {}, + d2: {}, + a: { d1: {}, d2: {}, f1: 'f1', f2: 'f2', b: { d1: {}, d2: {}, f1: 'f1', f2: 'f2', c: { d1: {}, d2: {}, f1: 'f1', f2: 'f2' }}}, + d: { d1: {}, d2: {}, f1: 'f1', f2: 'f2', e: { d1: {}, d2: {}, f1: 'f1', f2: 'f2', f: { d1: {}, d2: {}, f1: 'f1', f2: 'f2' }}}, + g: { d1: {}, d2: {}, f1: 'f1', f2: 'f2', h: { d1: {}, d2: {}, f1: 'f1', f2: 'f2', i: { d1: {}, d2: {}, f1: 'f1', f2: 'f2' }}}, + j: { d1: {}, d2: {}, f1: 'f1', f2: 'f2', k: { d1: {}, d2: {}, f1: 'f1', f2: 'f2', l: { d1: {}, d2: {}, f1: 'f1', f2: 'f2' }}}, + m: { d1: {}, d2: {}, f1: 'f1', f2: 'f2', n: { d1: {}, d2: {}, f1: 'f1', f2: 'f2', o: { d1: {}, d2: {}, f1: 'f1', f2: 'f2' }}}, + }) -function runTest () { - test("patch fs.readdir", function (t) { - // Monkey-patch fs.readdir to remove f1 before the callback happens - // This simulates the case where some files are deleted when chownr - // is in progress asynchronously - fs.readdir = function () { - const args = [].slice.call(arguments) + t.test("should complete successfully", { bail: true }, t => { + const readdir = fs.readdir + fs.readdir = (...args) => { const cb = args.pop() - const dir = args[0] - args.push((er, children) => { - if (er) return cb(er) - fs.unlink(path.join(dir, 'f1'), er => { - if (er && er.code === 'ENOENT') return cb(null, children) - cb(er, children) - }); - }); - readdir.apply(fs, args); + readdir(...args, (er, children) => { + if (er) + return cb(er) + try { fs.unlinkSync(`${args[0]}/f2`) } catch (_) {} + try { fs.rmdirSync(`${args[0]}/d1`) } catch (_) {} + try { fs.writeFileSync(`${args[0]}/d1`, 'now a file!') } catch (_) {} + try { fs.rmdirSync(`${args[0]}/d2`) } catch (_) {} + cb(null, children) + }) } - t.end() - }) + t.teardown(() => fs.readdir = readdir) - test("should complete successfully", function (t) { - // console.error("calling chownr", curUid, groups[0], typeof curUid, typeof groups[0]) - chownr("/tmp/chownr", curUid, groups[0], function (er) { - t.ifError(er) + chownr(dir, curUid, groups[0], er => { + if (er) + throw er t.end() }) }) - - test("restore fs.readdir", function (t) { - // Restore fs.readdir - fs.readdir = readdir - t.end() - }) - - dirs.forEach(function (dir) { - test("verify "+dir, function (t) { - fs.stat(dir, function (er, st) { - if (er) { - t.ifError(er) - return t.end() - } - t.equal(st.uid, curUid, "uid should be " + curUid) - t.equal(st.gid, groups[0], "gid should be "+groups[0]) - t.end() - }) + const dirs = [ + '', + 'a', + 'a/b', + 'a/b/c', + 'd', + 'd/e', + 'd/e/f', + 'g', + 'g/h', + 'g/h/i', + 'j', + 'j/k', + 'j/k/l', + 'm', + 'm/n', + 'm/n/o', + ] + + dirs.forEach(d => t.test(`verify ${d}`, t => { + t.match(fs.statSync(`${dir}/${d}`), { + uid: curUid, + gid: groups[0], }) - }) - - files.forEach(function (f) { - test("verify "+f, function (t) { - fs.stat(f, function (er, st) { - if (er) { - if (er.code !== 'ENOENT') - t.ifError(er) - return t.end() - } - t.equal(st.uid, curUid, "uid should be " + curUid) - t.equal(st.gid, groups[0], "gid should be "+groups[0]) - t.end() - }) + t.match(fs.statSync(`${dir}/${d}/f1`), { + uid: curUid, + gid: groups[0], }) - }) - - test("cleanup", function (t) { - rimraf("/tmp/chownr/", function (er) { - t.ifError(er) - t.end() + const st = fs.statSync(`${dir}/${d}/d1`) + t.equal(st.isFile(), true, 'd1 turned into a file') + t.match(st, { + uid: curUid, + gid: groups[0], }) - }) -} - + t.throws(() => fs.statSync(`${dir}/${d}/f2`)) + t.throws(() => fs.statSync(`${dir}/${d}/d2`)) + t.end() + })) + t.end() +}) diff --git a/test/non-handled-errors.js b/test/non-handled-errors.js new file mode 100644 index 0000000..27e2fc3 --- /dev/null +++ b/test/non-handled-errors.js @@ -0,0 +1,139 @@ +if (!process.getuid || !process.getgid) { + throw new Error("Tests require getuid/getgid support") +} + +const t = require('tap') +const fs = require('fs') +const {lchownSync, lchown, readdir, readdirSync, lstat, lstatSync } = fs +const chownr = require('../') + +const curUid = +process.getuid() +const curGid = +process.getgid() + +// sniff the 'id' command for other groups that i can legally assign to +const {exec} = require("child_process") +let groups +let dirs = [] + +t.test('get the ids to use', { bail: true }, t => { + exec("id", function (code, output) { + if (code) throw new Error("failed to run 'id' command") + groups = output.trim().split("=")[3].split(",") + .map(s => parseInt(s, 10)) + .filter(g => g !== curGid) + t.end() + }) +}) + + +t.test('fail lchown', t => { + fs.lchownSync = (...args) => { + throw new Error('poop') + } + fs.lchown = (...args) => { + args.pop()(new Error('poop')) + } + t.teardown(() => { + fs.lchownSync = lchownSync + fs.lchown = lchown + }) + + t.test('async fail', t => { + const dir = t.testdir() + chownr(dir, curUid, groups[0], er => { + t.match(er, { message: 'poop' }) + t.end() + }) + }) + + t.test('sync fail', t => { + const dir = t.testdir() + t.throws(() => chownr.sync(dir, curUid, groups[0]), { message: 'poop' }) + t.end() + }) + + t.end() +}) + +t.test('fail readdir', t => { + fs.readdirSync = (...args) => { + throw new Error('poop') + } + fs.readdir = (...args) => { + args.pop()(new Error('poop')) + } + t.teardown(() => { + fs.readdirSync = readdirSync + fs.readdir = readdir + }) + + t.test('async fail', t => { + const dir = t.testdir() + chownr(dir, curUid, groups[0], er => { + t.match(er, { message: 'poop' }) + t.end() + }) + }) + + t.test('sync fail', t => { + const dir = t.testdir() + t.throws(() => chownr.sync(dir, curUid, groups[0]), { message: 'poop' }) + t.end() + }) + + t.end() +}) + +t.test('fail lstat', t => { + // this is only relevant when using the old readdir + fs.readdir = (path, options, cb) => readdir(path, cb || options) + fs.readdirSync = (path, options) => readdirSync(path) + + fs.lstatSync = (...args) => { + throw new Error('poop') + } + fs.lstat = (...args) => { + args.pop()(new Error('poop')) + } + t.teardown(() => { + fs.readdirSync = readdirSync + fs.readdir = readdir + fs.lstatSync = lstatSync + fs.lstat = lstat + }) + + t.test('async fail', t => { + const dir = t.testdir({ a: 'b', c: 'd' }) + chownr(dir, curUid, groups[0], er => { + t.match(er, { message: 'poop' }) + t.end() + }) + }) + + t.test('sync fail', t => { + const dir = t.testdir({ a: 'b', c: 'd' }) + t.throws(() => chownr.sync(dir, curUid, groups[0]), { message: 'poop' }) + t.end() + }) + + t.end() +}) + +t.test('bubble up async errors', t => { + fs.readdir = (...args) => { + fs.readdir = (...args) => { + fs.readdir = (...args) => args.pop()(new Error('poop')) + readdir(...args) + } + readdir(...args) + } + t.teardown(() => { + fs.readdir = readdir + }) + + const dir = t.testdir({a: { b: { c: { d: {}}}}}) + chownr(dir, curUid, groups[0], er => { + t.match(er, { message: 'poop' }) + t.end() + }) +}) diff --git a/test/old-readdir-sync.js b/test/old-readdir-sync.js deleted file mode 100644 index 5e883f0..0000000 --- a/test/old-readdir-sync.js +++ /dev/null @@ -1,85 +0,0 @@ -if (!process.getuid || !process.getgid) { - throw new Error("Tests require getuid/getgid support") -} - -const fs = require('fs') -const readdir = fs.readdir -fs.readdir = (path, options, cb) => readdir(path, cb || options) -const readdirSync = fs.readdirSync -fs.readdirSync = (path, options) => readdirSync(path) - -var curUid = +process.getuid() -, curGid = +process.getgid() -, chownr = require("../") -, test = require("tap").test -, mkdirp = require("mkdirp") -, rimraf = require("rimraf") - -// sniff the 'id' command for other groups that i can legally assign to -var exec = require("child_process").exec -, groups -, dirs = [] - -exec("id", function (code, output) { - if (code) throw new Error("failed to run 'id' command") - groups = output.trim().split("=")[3].split(",").map(function (s) { - return parseInt(s, 10) - }).filter(function (g) { - return g !== curGid - }) - - // console.error([curUid, groups[0]], "uid, gid") - - rimraf("/tmp/chownr", function (er) { - if (er) throw er - var cnt = 5 - for (var i = 0; i < 5; i ++) { - mkdirp(getDir(), then) - } - function then (er) { - if (er) throw er - if (-- cnt === 0) { - runTest() - } - } - }) -}) - -function getDir () { - var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16); - var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16); - var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16); - var dir = "/tmp/chownr/" + [x,y,z].join("/") - dirs.push(dir) - return dir -} - -function runTest () { - test("should complete successfully", function (t) { - // console.error("calling chownr", curUid, groups[0], typeof curUid, typeof groups[0]) - chownr.sync("/tmp/chownr", curUid, groups[0]) - t.end() - }) - - dirs.forEach(function (dir) { - test("verify "+dir, function (t) { - fs.stat(dir, function (er, st) { - if (er) { - t.ifError(er) - return t.end() - } - t.equal(st.uid, curUid, "uid should be " + curUid) - t.equal(st.gid, groups[0], "gid should be "+groups[0]) - t.end() - }) - }) - }) - - test("cleanup", function (t) { - rimraf("/tmp/chownr/", function (er) { - t.ifError(er) - t.end() - }) - }) -} - diff --git a/test/old-readdir.js b/test/old-readdir.js index a0abc00..78b44bc 100644 --- a/test/old-readdir.js +++ b/test/old-readdir.js @@ -8,83 +8,12 @@ fs.readdir = (path, options, cb) => readdir(path, cb || options) const readdirSync = fs.readdirSync fs.readdirSync = (path, options) => readdirSync(path) -var curUid = +process.getuid() -, curGid = +process.getgid() -, chownr = require("../") -, test = require("tap").test -, mkdirp = require("mkdirp") -, rimraf = require("rimraf") - -// sniff the 'id' command for other groups that i can legally assign to -var exec = require("child_process").exec -, groups -, dirs = [] - -exec("id", function (code, output) { - if (code) throw new Error("failed to run 'id' command") - groups = output.trim().split("=")[3].split(",").map(function (s) { - return parseInt(s, 10) - }).filter(function (g) { - return g !== curGid - }) - - // console.error([curUid, groups[0]], "uid, gid") - - rimraf("/tmp/chownr", function (er) { - if (er) throw er - var cnt = 5 - for (var i = 0; i < 5; i ++) { - mkdirp(getDir(), then) - } - function then (er) { - if (er) throw er - if (-- cnt === 0) { - runTest() - } - } - }) -}) - -function getDir () { - var dir = "/tmp/chownr" - - dir += "/" + Math.floor(Math.random() * Math.pow(16,4)).toString(16) - dirs.push(dir) - dir += "/" + Math.floor(Math.random() * Math.pow(16,4)).toString(16) - dirs.push(dir) - dir += "/" + Math.floor(Math.random() * Math.pow(16,4)).toString(16) - dirs.push(dir) - return dir -} - -function runTest () { - test("should complete successfully", function (t) { - // console.error("calling chownr", curUid, groups[0], typeof curUid, typeof groups[0]) - chownr("/tmp/chownr", curUid, groups[0], function (er) { - t.ifError(er) - t.end() - }) - }) - - dirs.forEach(function (dir) { - test("verify "+dir, function (t) { - fs.stat(dir, function (er, st) { - if (er) { - t.ifError(er) - return t.end() - } - t.equal(st.uid, curUid, "uid should be " + curUid) - t.equal(st.gid, groups[0], "gid should be "+groups[0]) - t.end() - }) - }) - }) - - test("cleanup", function (t) { - rimraf("/tmp/chownr/", function (er) { - t.ifError(er) - t.end() - }) - }) -} - +const t = require('tap') +t.comment('basic.js') +require('./basic.js') +t.comment('sync.js') +require('./sync.js') +t.comment('concurrent.js') +require('./concurrent.js') +t.comment('concurrent-sync.js') +require('./concurrent-sync.js') diff --git a/test/sync.js b/test/sync.js index 62d02c2..f05b0d7 100644 --- a/test/sync.js +++ b/test/sync.js @@ -2,79 +2,68 @@ if (!process.getuid || !process.getgid) { throw new Error("Tests require getuid/getgid support") } -var curUid = +process.getuid() -, curGid = +process.getgid() -, chownr = require("../") -, test = require("tap").test -, mkdirp = require("mkdirp") -, rimraf = require("rimraf") -, fs = require("fs") +const curUid = +process.getuid() +const curGid = +process.getgid() +const chownr = require("../") +const t = require("tap") +const mkdirp = require("mkdirp") +const rimraf = require("rimraf") +const fs = require("fs") // sniff the 'id' command for other groups that i can legally assign to -var exec = require("child_process").exec -, groups -, dirs = [] +const {exec} = require("child_process") +let groups -exec("id", function (code, output) { - if (code) throw new Error("failed to run 'id' command") - groups = output.trim().split("=")[3].split(",").map(function (s) { - return parseInt(s, 10) - }).filter(function (g) { - return g !== curGid - }) - - // console.error([curUid, groups[0]], "uid, gid") - - rimraf("/tmp/chownr", function (er) { - if (er) throw er - var cnt = 5 - for (var i = 0; i < 5; i ++) { - mkdirp(getDir(), then) - } - function then (er) { - if (er) throw er - if (-- cnt === 0) { - runTest() - } - } +t.test('get the ids to use', { bail: true }, t => { + exec("id", function (code, output) { + if (code) throw new Error("failed to run 'id' command") + groups = output.trim().split("=")[3].split(",") + .map(s => parseInt(s, 10)) + .filter(g => g !== curGid) + t.end() }) }) -function getDir () { - var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16); - var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16); - var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16); - var dir = "/tmp/chownr/" + [x,y,z].join("/") - dirs.push(dir) - return dir -} +t.test('run test', t => { + const dir = t.testdir({ + a: { b: { c: {}}}, + d: { e: { f: {}}}, + g: { h: { i: {}}}, + j: { k: { l: {}}}, + m: { n: { o: {}}}, + }) -function runTest () { - test("should complete successfully", function (t) { + t.test("should complete successfully", t => { // console.error("calling chownr", curUid, groups[0], typeof curUid, typeof groups[0]) - chownr.sync("/tmp/chownr", curUid, groups[0]) + chownr.sync(dir, curUid, groups[0]) t.end() }) - dirs.forEach(function (dir) { - test("verify "+dir, function (t) { - fs.stat(dir, function (er, st) { - if (er) { - t.ifError(er) - return t.end() - } - t.equal(st.uid, curUid, "uid should be " + curUid) - t.equal(st.gid, groups[0], "gid should be "+groups[0]) - t.end() - }) - }) - }) + const dirs = [ + '', + 'a', + 'a/b', + 'a/b/c', + 'd', + 'd/e', + 'd/e/f', + 'g', + 'g/h', + 'g/h/i', + 'j', + 'j/k', + 'j/k/l', + 'm', + 'm/n', + 'm/n/o', + ] - test("cleanup", function (t) { - rimraf("/tmp/chownr/", function (er) { - t.ifError(er) - t.end() + dirs.forEach(d => t.test(`verify ${d}`, t => { + t.match(fs.statSync(`${dir}/${d}`), { + uid: curUid, + gid: groups[0], }) - }) -} - + t.end() + })) + t.end() +})