Skip to content

Commit

Permalink
Chore: fix tests
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mysticatea committed Nov 3, 2017
1 parent dfb9dcb commit a830920
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 27 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,11 @@
"codecov": "^2.3.0",
"eslint": "^4.5.0",
"eslint-config-mysticatea": "^12.0.0",
"fs-extra": "^4.0.2",
"jsdoc": "^3.5.4",
"mocha": "^3.5.0",
"nyc": "^11.1.0",
"p-queue": "^2.2.0",
"power-assert": "^1.4.4",
"rimraf": "^2.6.1",
"yarn": "^1.2.1"
Expand Down
2 changes: 1 addition & 1 deletion test-workspace/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"test-task:nest-append:run-s": "node ../bin/run-s/index.js test-task:append",
"test-task:nest-append:run-p": "node ../bin/run-p/index.js test-task:append",
"test-task:delayed": "node tasks/output-with-delay.js",
"test-task:yarn": "node ../bin/npm-run-all/index.js test-task:append:{a,b}"
"test-task:yarn": "node ../bin/npm-run-all/index.js test-task:append:{a,b} --npm-path yarn"
},
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion test-workspace/tasks/append2.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ appendResult(process.argv[2])
setTimeout(() => {
appendResult(process.argv[2])
process.exit(0)
}, 2000)
}, 3000)

// SIGINT/SIGTERM Handling.
process.on("SIGINT", () => {
Expand Down
173 changes: 173 additions & 0 deletions test/bin/run-tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/**
* @author Toru Nagashima <https://github.com/mysticatea>
* @copyright 2017 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"

/*
* Run tests in parallel.
* This can reduce the spent time of tests to 1/3, but this is badly affecting to the timers in tests.
* I need more investigation.
*/

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const spawn = require("child_process").spawn
const path = require("path")
const os = require("os")
const fs = require("fs-extra")
const PQueue = require("p-queue")

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

const ROOT_PATH = path.resolve(__dirname, "../")
const WORKSPACE_PATH = path.resolve(__dirname, "../../test-workspace")
const MOCHA_PATH = path.resolve(__dirname, "../../node_modules/mocha/bin/_mocha")

/**
* Convert a given duration in seconds to a string.
* @param {number} durationInSec A duration to convert.
* @returns {string} The string of the duration.
*/
function durationToText(durationInSec) {
return `${durationInSec / 60 | 0}m ${durationInSec % 60 | 0}s`
}

/**
* Run a given test file.
* @param {string} filePath The absolute path to a test file.
* @param {string} workspacePath The absolute path to the workspace directory.
* @returns {Promise<{duration:number,exitCode:number,failing:number,id:string,passing:number,text:string}>}
* - `duration` is the spent time in seconds.
* - `exitCode` is the exit code of the child process.
* - `failing` is the number of failed tests.
* - `id` is the name of this tests.
* - `passing` is the number of succeeded tests.
* - `text` is the result text of the child process.
*/
function runMocha(filePath, workspacePath) {
return new Promise((resolve, reject) => {
const startInSec = process.uptime()
const cp = spawn(
process.execPath,
[MOCHA_PATH, filePath, "--reporter", "dot", "--timeout", "120000"],
{ cwd: workspacePath, stdio: ["ignore", "pipe", "inherit"] }
)

let resultText = ""

cp.stdout.setEncoding("utf8")
cp.stdout.on("data", (rawChunk) => {
const chunk = rawChunk.trim().replace(/^[․.!]+/, (dots) => {
process.stdout.write(dots)
return ""
})
if (chunk) {
resultText += chunk
resultText += "\n\n"
}
})

cp.on("exit", (exitCode) => {
let passing = 0
let failing = 0
const text = resultText
.replace(/(\d+) passing\s*\(.+?\)/, (_, n) => {
passing += Number(n)
return ""
})
.replace(/(\d+) failing\s*/, (_, n) => {
failing += Number(n)
return ""
})
.replace(/^\s*\d+\)/gm, "")
.split("\n")
.filter(line => !line.includes("empower-core"))
.join("\n")
.trim()

resolve({
duration: process.uptime() - startInSec,
exitCode,
failing,
id: path.basename(filePath, ".js"),
passing,
text,
})
})
cp.on("error", reject)
})
}

/**
* Run a given test file.
* @param {string} filePath The absolute path to a test file.
* @returns {Promise<{duration:number,exitCode:number,failing:number,id:string,passing:number,text:string}>}
* - `duration` is the spent time in seconds.
* - `exitCode` is the exit code of the child process.
* - `failing` is the number of failed tests.
* - `id` is the name of this tests.
* - `passing` is the number of succeeded tests.
* - `text` is the result text of the child process.
*/
async function runMochaWithWorkspace(filePath) {
const basename = path.basename(filePath, ".js")
const workspacePath = path.resolve(__dirname, `../../test-workspace-${basename}`)

await fs.remove(workspacePath)
await fs.copy(WORKSPACE_PATH, workspacePath, { dereference: true, recursive: true })
try {
return await runMocha(filePath, workspacePath)
}
finally {
try {
await fs.remove(workspacePath)
}
catch (_error) {
// ignore to keep the original error.
}
}
}

//------------------------------------------------------------------------------
// Main
//------------------------------------------------------------------------------

(async () => {
const startInSec = process.uptime()
const queue = new PQueue({ concurrency: os.cpus().length + 1 })
const results = await Promise.all(
(await fs.readdir(ROOT_PATH))
.filter(fileName => path.extname(fileName) === ".js")
.map(fileName => path.join(ROOT_PATH, fileName))
.map(filePath => queue.add(() => runMochaWithWorkspace(filePath)))
)

process.stdout.write("\n\n")

for (const result of results) {
if (result.text) {
process.stdout.write(`\n${result.text}\n\n`)
}
if (result.exitCode) {
process.exitCode = 1
}
}

let passing = 0
let failing = 0
for (const result of results) {
passing += result.passing
failing += result.failing
process.stdout.write(`\n${result.id}: passing ${result.passing} failing ${result.failing} (${durationToText(result.duration)})`)
}
process.stdout.write(`\n\nTOTAL: passing ${passing} failing ${failing} (${durationToText(process.uptime() - startInSec)})\n\n`)
})().catch(error => {
process.stderr.write(`\n\n${error.stack}\n\n`)
process.exit(1) //eslint-disable-line no-process-exit
})
16 changes: 0 additions & 16 deletions test/lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ const FILE_NAME = "test.txt"
const NPM_RUN_ALL = path.resolve(__dirname, "../../bin/npm-run-all/index.js")
const RUN_P = path.resolve(__dirname, "../../bin/run-p/index.js")
const RUN_S = path.resolve(__dirname, "../../bin/run-s/index.js")
const YARN = path.resolve(__dirname, "../../node_modules/yarn/bin/yarn.js")

/**
* Spawns the given script with the given arguments.
Expand Down Expand Up @@ -164,18 +163,3 @@ module.exports.runPar = function runPar(args, stdout, stderr) {
module.exports.runSeq = function runSeq(args, stdout, stderr) {
return spawn(RUN_S, args, stdout, stderr)
}

/**
* Executes `yarn run` with the given arguments.
*
* @param {string[]} args - The arguments to execute.
* @param {Writable} [stdout] - The writable stream to receive stdout.
* @param {Writable} [stderr] - The writable stream to receive stderr.
* @returns {Promise<void>} The promise which becomes fulfilled if the child
* process finished.
*/
module.exports.runWithYarn = function runWithYarn(args, stdout, stderr) {
return spawn(YARN, ["run"].concat(args), stdout, stderr)
}

module.exports.YARN_PATH = YARN
16 changes: 7 additions & 9 deletions test/yarn.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,43 +24,41 @@ const removeResult = util.removeResult
* Execute a command.
* @param {string} command A command to execute.
* @param {string[]} args Arguments for the command.
* @returns {Promise<string>} The result of child process's stdout.
* @returns {Promise<void>} The result of child process's stdout.
*/
function exec(command, args) {
return new Promise((resolve, reject) => {
const stdout = new BufferStream()
const stderr = new BufferStream()
const cp = spawn(command, args, { stdio: ["inherit", "pipe", "pipe"] })
const cp = spawn(command, args, { stdio: ["ignore", "ignore", "pipe"] })

cp.stdout.pipe(stdout)
cp.stderr.pipe(stderr)
cp.on("exit", (exitCode) => {
if (exitCode) {
reject(new Error(`Exited with ${exitCode}: ${stderr.value}`))
return
}
resolve(stdout.value)
resolve()
})
cp.on("error", reject)
})
}

const nodeVersion = Number(process.versions.node.split(".")[0])

//------------------------------------------------------------------------------
// Test
//------------------------------------------------------------------------------

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

beforeEach(removeResult)

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

0 comments on commit a830920

Please sign in to comment.