Skip to content

Commit 43a6b16

Browse files
committed
Chore: fix tests
I challenged to make tests parallel for each file. But it makes CPU busy then timers in tests have gotten mess sometimes. Time up, I have to need more investigation on other time.
1 parent dfb9dcb commit 43a6b16

File tree

7 files changed

+191
-34
lines changed

7 files changed

+191
-34
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,11 @@
4747
"codecov": "^2.3.0",
4848
"eslint": "^4.5.0",
4949
"eslint-config-mysticatea": "^12.0.0",
50+
"fs-extra": "^4.0.2",
5051
"jsdoc": "^3.5.4",
5152
"mocha": "^3.5.0",
5253
"nyc": "^11.1.0",
54+
"p-queue": "^2.2.0",
5355
"power-assert": "^1.4.4",
5456
"rimraf": "^2.6.1",
5557
"yarn": "^1.2.1"

test-workspace/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"test-task:nest-append:run-s": "node ../bin/run-s/index.js test-task:append",
3939
"test-task:nest-append:run-p": "node ../bin/run-p/index.js test-task:append",
4040
"test-task:delayed": "node tasks/output-with-delay.js",
41-
"test-task:yarn": "node ../bin/npm-run-all/index.js test-task:append:{a,b}"
41+
"test-task:yarn": "node ../bin/npm-run-all/index.js test-task:append:{a,b} --npm-path yarn"
4242
},
4343
"repository": {
4444
"type": "git",

test-workspace/tasks/append2.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ appendResult(process.argv[2])
66
setTimeout(() => {
77
appendResult(process.argv[2])
88
process.exit(0)
9-
}, 2000)
9+
}, 3000)
1010

1111
// SIGINT/SIGTERM Handling.
1212
process.on("SIGINT", () => {

test/aggregate-output.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ describe("[aggregated-output] npm-run-all", () => {
5151

5252
it("Node API with parallel", async () => {
5353
await nodeApi(
54-
["test-task:delayed first 300", "test-task:delayed second 100", "test-task:delayed third 200"],
54+
["test-task:delayed first 5000", "test-task:delayed second 1000", "test-task:delayed third 3000"],
5555
{ stdout, parallel: true, silent: true, aggregateOutput: true }
5656
)
5757
assert.equal(stdout.value, EXPECTED_PARALLELIZED_TEXT)
@@ -60,7 +60,7 @@ describe("[aggregated-output] npm-run-all", () => {
6060
it("Node API without parallel should fail", async () => {
6161
try {
6262
await nodeApi(
63-
["test-task:delayed first 300", "test-task:delayed second 100", "test-task:delayed third 200"],
63+
["test-task:delayed first 5000", "test-task:delayed second 1000", "test-task:delayed third 3000"],
6464
{ stdout, silent: true, aggregateOutput: true }
6565
)
6666
}
@@ -72,7 +72,7 @@ describe("[aggregated-output] npm-run-all", () => {
7272

7373
it("npm-run-all command with parallel", async () => {
7474
await runAll(
75-
["--parallel", "test-task:delayed first 300", "test-task:delayed second 100", "test-task:delayed third 200", "--silent", "--aggregate-output"],
75+
["--parallel", "test-task:delayed first 5000", "test-task:delayed second 1000", "test-task:delayed third 3000", "--silent", "--aggregate-output"],
7676
stdout
7777
)
7878
assert.equal(stdout.value, EXPECTED_PARALLELIZED_TEXT)
@@ -81,7 +81,7 @@ describe("[aggregated-output] npm-run-all", () => {
8181
it("npm-run-all command without parallel should fail", async () => {
8282
try {
8383
await runAll(
84-
["test-task:delayed first 300", "test-task:delayed second 100", "test-task:delayed third 200", "--silent", "--aggregate-output"],
84+
["test-task:delayed first 5000", "test-task:delayed second 1000", "test-task:delayed third 3000", "--silent", "--aggregate-output"],
8585
stdout
8686
)
8787
}
@@ -94,7 +94,7 @@ describe("[aggregated-output] npm-run-all", () => {
9494
it("run-s command should fail", async () => {
9595
try {
9696
await runSeq(
97-
["test-task:delayed first 300", "test-task:delayed second 100", "test-task:delayed third 200", "--silent", "--aggregate-output"],
97+
["test-task:delayed first 5000", "test-task:delayed second 1000", "test-task:delayed third 3000", "--silent", "--aggregate-output"],
9898
stdout
9999
)
100100
}
@@ -107,9 +107,9 @@ describe("[aggregated-output] npm-run-all", () => {
107107
it("run-p command", async () => {
108108
await runPar(
109109
[
110-
"test-task:delayed first 3000",
110+
"test-task:delayed first 5000",
111111
"test-task:delayed second 1000",
112-
"test-task:delayed third 2000",
112+
"test-task:delayed third 3000",
113113
"--silent", "--aggregate-output",
114114
],
115115
stdout

test/bin/run-tests.js

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/**
2+
* @author Toru Nagashima <https://github.com/mysticatea>
3+
* @copyright 2017 Toru Nagashima. All rights reserved.
4+
* See LICENSE file in root directory for full license.
5+
*/
6+
"use strict"
7+
8+
/*
9+
* Run tests in parallel.
10+
* This can reduce the spent time of tests to 1/3, but this is badly affecting to the timers in tests.
11+
* I need more investigation.
12+
*/
13+
14+
//------------------------------------------------------------------------------
15+
// Requirements
16+
//------------------------------------------------------------------------------
17+
18+
const spawn = require("child_process").spawn
19+
const path = require("path")
20+
const os = require("os")
21+
const fs = require("fs-extra")
22+
const PQueue = require("p-queue")
23+
24+
//------------------------------------------------------------------------------
25+
// Helpers
26+
//------------------------------------------------------------------------------
27+
28+
const ROOT_PATH = path.resolve(__dirname, "../")
29+
const WORKSPACE_PATH = path.resolve(__dirname, "../../test-workspace")
30+
const MOCHA_PATH = path.resolve(__dirname, "../../node_modules/mocha/bin/_mocha")
31+
32+
/**
33+
* Convert a given duration in seconds to a string.
34+
* @param {number} durationInSec A duration to convert.
35+
* @returns {string} The string of the duration.
36+
*/
37+
function durationToText(durationInSec) {
38+
return `${durationInSec / 60 | 0}m ${durationInSec % 60 | 0}s`
39+
}
40+
41+
/**
42+
* Run a given test file.
43+
* @param {string} filePath The absolute path to a test file.
44+
* @param {string} workspacePath The absolute path to the workspace directory.
45+
* @returns {Promise<{duration:number,exitCode:number,failing:number,id:string,passing:number,text:string}>}
46+
* - `duration` is the spent time in seconds.
47+
* - `exitCode` is the exit code of the child process.
48+
* - `failing` is the number of failed tests.
49+
* - `id` is the name of this tests.
50+
* - `passing` is the number of succeeded tests.
51+
* - `text` is the result text of the child process.
52+
*/
53+
function runMocha(filePath, workspacePath) {
54+
return new Promise((resolve, reject) => {
55+
const startInSec = process.uptime()
56+
const cp = spawn(
57+
process.execPath,
58+
[MOCHA_PATH, filePath, "--reporter", "dot", "--timeout", "120000"],
59+
{ cwd: workspacePath, stdio: ["ignore", "pipe", "inherit"] }
60+
)
61+
62+
let resultText = ""
63+
64+
cp.stdout.setEncoding("utf8")
65+
cp.stdout.on("data", (rawChunk) => {
66+
const chunk = rawChunk.trim().replace(/^[.!]+/, (dots) => {
67+
process.stdout.write(dots)
68+
return ""
69+
})
70+
if (chunk) {
71+
resultText += chunk
72+
resultText += "\n\n"
73+
}
74+
})
75+
76+
cp.on("exit", (exitCode) => {
77+
let passing = 0
78+
let failing = 0
79+
const text = resultText
80+
.replace(/(\d+) passing\s*\(.+?\)/, (_, n) => {
81+
passing += Number(n)
82+
return ""
83+
})
84+
.replace(/(\d+) failing\s*/, (_, n) => {
85+
failing += Number(n)
86+
return ""
87+
})
88+
.replace(/^\s*\d+\)/gm, "")
89+
.split("\n")
90+
.filter(line => !line.includes("empower-core"))
91+
.join("\n")
92+
.trim()
93+
94+
resolve({
95+
duration: process.uptime() - startInSec,
96+
exitCode,
97+
failing,
98+
id: path.basename(filePath, ".js"),
99+
passing,
100+
text,
101+
})
102+
})
103+
cp.on("error", reject)
104+
})
105+
}
106+
107+
/**
108+
* Run a given test file.
109+
* @param {string} filePath The absolute path to a test file.
110+
* @returns {Promise<{duration:number,exitCode:number,failing:number,id:string,passing:number,text:string}>}
111+
* - `duration` is the spent time in seconds.
112+
* - `exitCode` is the exit code of the child process.
113+
* - `failing` is the number of failed tests.
114+
* - `id` is the name of this tests.
115+
* - `passing` is the number of succeeded tests.
116+
* - `text` is the result text of the child process.
117+
*/
118+
async function runMochaWithWorkspace(filePath) {
119+
const basename = path.basename(filePath, ".js")
120+
const workspacePath = path.resolve(__dirname, `../../test-workspace-${basename}`)
121+
122+
await fs.remove(workspacePath)
123+
await fs.copy(WORKSPACE_PATH, workspacePath, { dereference: true, recursive: true })
124+
try {
125+
return await runMocha(filePath, workspacePath)
126+
}
127+
finally {
128+
try {
129+
await fs.remove(workspacePath)
130+
}
131+
catch (_error) {
132+
// ignore to keep the original error.
133+
}
134+
}
135+
}
136+
137+
//------------------------------------------------------------------------------
138+
// Main
139+
//------------------------------------------------------------------------------
140+
141+
(async () => {
142+
const startInSec = process.uptime()
143+
const queue = new PQueue({ concurrency: os.cpus().length + 1 })
144+
const results = await Promise.all(
145+
(await fs.readdir(ROOT_PATH))
146+
.filter(fileName => path.extname(fileName) === ".js")
147+
.map(fileName => path.join(ROOT_PATH, fileName))
148+
.map(filePath => queue.add(() => runMochaWithWorkspace(filePath)))
149+
)
150+
151+
process.stdout.write("\n\n")
152+
153+
for (const result of results) {
154+
if (result.text) {
155+
process.stdout.write(`\n${result.text}\n\n`)
156+
}
157+
if (result.exitCode) {
158+
process.exitCode = 1
159+
}
160+
}
161+
162+
let passing = 0
163+
let failing = 0
164+
for (const result of results) {
165+
passing += result.passing
166+
failing += result.failing
167+
process.stdout.write(`\n${result.id}: passing ${result.passing} failing ${result.failing} (${durationToText(result.duration)})`)
168+
}
169+
process.stdout.write(`\n\nTOTAL: passing ${passing} failing ${failing} (${durationToText(process.uptime() - startInSec)})\n\n`)
170+
})().catch(error => {
171+
process.stderr.write(`\n\n${error.stack}\n\n`)
172+
process.exit(1) //eslint-disable-line no-process-exit
173+
})

test/lib/util.js

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ const FILE_NAME = "test.txt"
2222
const NPM_RUN_ALL = path.resolve(__dirname, "../../bin/npm-run-all/index.js")
2323
const RUN_P = path.resolve(__dirname, "../../bin/run-p/index.js")
2424
const RUN_S = path.resolve(__dirname, "../../bin/run-s/index.js")
25-
const YARN = path.resolve(__dirname, "../../node_modules/yarn/bin/yarn.js")
2625

2726
/**
2827
* Spawns the given script with the given arguments.
@@ -164,18 +163,3 @@ module.exports.runPar = function runPar(args, stdout, stderr) {
164163
module.exports.runSeq = function runSeq(args, stdout, stderr) {
165164
return spawn(RUN_S, args, stdout, stderr)
166165
}
167-
168-
/**
169-
* Executes `yarn run` with the given arguments.
170-
*
171-
* @param {string[]} args - The arguments to execute.
172-
* @param {Writable} [stdout] - The writable stream to receive stdout.
173-
* @param {Writable} [stderr] - The writable stream to receive stderr.
174-
* @returns {Promise<void>} The promise which becomes fulfilled if the child
175-
* process finished.
176-
*/
177-
module.exports.runWithYarn = function runWithYarn(args, stdout, stderr) {
178-
return spawn(YARN, ["run"].concat(args), stdout, stderr)
179-
}
180-
181-
module.exports.YARN_PATH = YARN

test/yarn.js

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,43 +24,41 @@ const removeResult = util.removeResult
2424
* Execute a command.
2525
* @param {string} command A command to execute.
2626
* @param {string[]} args Arguments for the command.
27-
* @returns {Promise<string>} The result of child process's stdout.
27+
* @returns {Promise<void>} The result of child process's stdout.
2828
*/
2929
function exec(command, args) {
3030
return new Promise((resolve, reject) => {
31-
const stdout = new BufferStream()
3231
const stderr = new BufferStream()
33-
const cp = spawn(command, args, { stdio: ["inherit", "pipe", "pipe"] })
32+
const cp = spawn(command, args, { stdio: ["ignore", "ignore", "pipe"] })
3433

35-
cp.stdout.pipe(stdout)
3634
cp.stderr.pipe(stderr)
3735
cp.on("exit", (exitCode) => {
3836
if (exitCode) {
3937
reject(new Error(`Exited with ${exitCode}: ${stderr.value}`))
4038
return
4139
}
42-
resolve(stdout.value)
40+
resolve()
4341
})
4442
cp.on("error", reject)
4543
})
4644
}
4745

46+
const nodeVersion = Number(process.versions.node.split(".")[0])
47+
4848
//------------------------------------------------------------------------------
4949
// Test
5050
//------------------------------------------------------------------------------
5151

52-
describe("[yarn]", () => {
52+
;(nodeVersion >= 6 ? describe : xdescribe)("[yarn]", () => {
5353
before(() => process.chdir("test-workspace"))
5454
after(() => process.chdir(".."))
5555

5656
beforeEach(removeResult)
5757

5858
describe("'yarn run' command", () => {
5959
it("should run 'npm-run-all' in scripts with yarn.", async () => {
60-
const stdout = await exec("yarn", ["run", "test-task:yarn"])
61-
const matches = stdout.match(/^\$ node .+$/gm)
60+
await exec("yarn", ["run", "test-task:yarn"])
6261
assert(result() === "aabb")
63-
assert(matches.length === 3)
6462
})
6563
})
6664
})

0 commit comments

Comments
 (0)