Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions dockerfiles/Dockerfile-gui-tests
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,7 @@ COPY ../package.json /build/package.json
# We also specify the version in case we need to update it to go around cache limitations.
RUN npm install --unsafe-perm=true --loglevel verbose --force

# Used in gui-tests/tester.js
ENV NODE_MODULE_PATH="/build/node_modules"

CMD ["node", "/build/out/gui-tests/tester.js"]
3 changes: 2 additions & 1 deletion dockerfiles/run-gui-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ cargo run -- start-web-server &
SERVER_PID=$!

# status="docker run . -v `pwd`:/build/out:ro gui_tests"
docker compose run --rm gui_tests
docker compose build gui_tests
docker compose run --rm --remove-orphans gui_tests
status=$?
kill $SERVER_PID
exit $status
260 changes: 23 additions & 237 deletions gui-tests/tester.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,247 +4,33 @@
// npm install browser-ui-test
// ```

const fs = require("fs");
const path = require("path");
const os = require("os");
const {Options, runTest} = require("browser-ui-test");

function showHelp() {
console.log("docs-rs-gui-js options:");
console.log(" --file [PATH] : file to run (can be repeated)");
console.log(" --debug : show extra information about script run");
console.log(" --show-text : render font in pages");
console.log(" --no-headless : disable headless mode");
console.log(" --help : show this message then quit");
console.log(" --jobs [NUMBER] : number of threads to run tests on");
}

function isNumeric(s) {
return /^\d+$/.test(s);
}

function parseOptions(args) {
const opts = {
"files": [],
"debug": false,
"show_text": false,
"no_headless": false,
"jobs": -1,
};
const correspondences = {
"--debug": "debug",
"--show-text": "show_text",
"--no-headless": "no_headless",
};

for (let i = 0; i < args.length; ++i) {
if (args[i] === "--file"
|| args[i] === "--jobs") {
i += 1;
if (i >= args.length) {
console.log("Missing argument after `" + args[i - 1] + "` option.");
return null;
}
if (args[i - 1] === "--jobs") {
if (!isNumeric(args[i])) {
console.log(
"`--jobs` option expects a positive number, found `" + args[i] + "`");
return null;
}
opts["jobs"] = parseInt(args[i]);
} else if (args[i - 1] !== "--file") {
opts[correspondences[args[i - 1]]] = args[i];
} else {
opts["files"].push(args[i]);
}
} else if (args[i] === "--help") {
showHelp();
process.exit(0);
} else if (correspondences[args[i]]) {
opts[correspondences[args[i]]] = true;
} else {
console.log("Unknown option `" + args[i] + "`.");
console.log("Use `--help` to see the list of options");
return null;
}
}
return opts;
}

/// Print single char status information without \n
function char_printer(n_tests) {
const max_per_line = 10;
let current = 0;
return {
successful: function() {
current += 1;
if (current % max_per_line === 0) {
process.stdout.write(`. (${current}/${n_tests})${os.EOL}`);
} else {
process.stdout.write(".");
}
},
erroneous: function() {
current += 1;
if (current % max_per_line === 0) {
process.stderr.write(`F (${current}/${n_tests})${os.EOL}`);
} else {
process.stderr.write("F");
}
},
finish: function() {
if (current % max_per_line === 0) {
// Don't output if we are already at a matching line end
console.log("");
} else {
const spaces = " ".repeat(max_per_line - (current % max_per_line));
process.stdout.write(`${spaces} (${current}/${n_tests})${os.EOL}${os.EOL}`);
}
},
};
}

/// Sort array by .file_name property
function by_filename(a, b) {
return a.file_name - b.file_name;
}
const spawn = require("child_process").spawn;

async function main(argv) {
const opts = parseOptions(argv.slice(2));
if (opts === null) {
process.exit(1);
}

// Print successful tests too
let debug = false;
// Run tests in sequentially
let headless = true;
const options = new Options();
try {
// This is more convenient that setting fields one by one.
const args = [];
if (typeof process.env.SERVER_URL !== "undefined") {
args.push("--variable", "DOC_PATH", process.env.SERVER_URL);
} else {
args.push("--variable", "DOC_PATH", "http://127.0.0.1:3000");
let server = "http://127.0.0.1:3000";
if (typeof process.env.SERVER_URL !== "undefined") {
server = process.env.SERVER_URL;
}
let nodeModulePath = "./node_modules";
if (typeof process.env.NODE_MODULE_PATH !== "undefined") {
nodeModulePath = process.env.NODE_MODULE_PATH;
}
await spawn("node", [
path.join(nodeModulePath, "browser-ui-test/src/index.js"),
"--display-format",
"compact",
"--variable",
"DOC_PATH",
server,
"--test-folder",
__dirname,
...argv.slice(2),
], {stdio: "inherit", stderr: "inherit"}).on("exit", code => {
if (code !== 0) {
process.exit(1);
}
if (opts["debug"]) {
debug = true;
args.push("--debug");
}
if (opts["show_text"]) {
args.push("--show-text");
}
if (opts["no_headless"]) {
args.push("--no-headless");
headless = false;
}
options.parseArguments(args);
} catch (error) {
console.error(`invalid argument: ${error}`);
process.exit(1);
}

let failed = false;
let files;
if (opts["files"].length === 0) {
files = fs.readdirSync(__dirname);
} else {
files = opts["files"];
}
files = files.filter(file => path.extname(file) === ".goml");
if (files.length === 0) {
console.error("No test selected");
process.exit(2);
}
files.sort();

if (!headless) {
opts["jobs"] = 1;
console.log("`--no-headless` option is active, disabling concurrency for running tests.");
}
let jobs = opts["jobs"];

if (opts["jobs"] < 1) {
jobs = files.length;
process.setMaxListeners(files.length + 1);
} else if (headless) {
process.setMaxListeners(opts["jobs"] + 1);
}
console.log(`Running ${files.length} docs.rs GUI (${jobs} concurrently) ...`);

const tests_queue = [];
const results = {
successful: [],
failed: [],
errored: [],
};
const status_bar = char_printer(files.length);
for (let i = 0; i < files.length; ++i) {
const file_name = files[i];
const testPath = path.join(__dirname, file_name);
const callback = runTest(testPath, {"options": options})
.then(out => {
const [output, nb_failures] = out;
results[nb_failures === 0 ? "successful" : "failed"].push({
file_name: testPath,
output: output,
});
if (nb_failures > 0) {
status_bar.erroneous();
failed = true;
} else {
status_bar.successful();
}
})
.catch(err => {
results.errored.push({
file_name: testPath + file_name,
output: err,
});
status_bar.erroneous();
failed = true;
})
.finally(() => {
// We now remove the promise from the tests_queue.
tests_queue.splice(tests_queue.indexOf(callback), 1);
});
tests_queue.push(callback);
if (opts["jobs"] > 0 && tests_queue.length >= opts["jobs"]) {
await Promise.race(tests_queue);
}
}
if (tests_queue.length > 0) {
await Promise.all(tests_queue);
}
status_bar.finish();

if (debug) {
results.successful.sort(by_filename);
results.successful.forEach(r => {
console.log(r.output);
});
}

if (results.failed.length > 0) {
console.log("");
results.failed.sort(by_filename);
results.failed.forEach(r => {
console.log(r.file_name, r.output);
});
}
if (results.errored.length > 0) {
console.log(os.EOL);
// print run errors on the bottom so developers see them better
results.errored.sort(by_filename);
results.errored.forEach(r => {
console.error(r.file_name, r.output);
});
}

if (failed) {
process.exit(1);
}
});
}

main(process.argv);
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "deps",
"dependencies": {
"browser-ui-test": "^0.21.3"
"browser-ui-test": "^0.22.0"
}
}
Loading