]> git.lizzy.rs Git - rust.git/blob - src/tools/rustdoc-gui/tester.js
Auto merge of #96742 - m-ou-se:bsd-no-ancillary, r=joshtriplett
[rust.git] / src / tools / rustdoc-gui / tester.js
1 // This package needs to be install:
2 //
3 // ```
4 // npm install browser-ui-test
5 // ```
6
7 const fs = require("fs");
8 const path = require("path");
9 const os = require('os');
10 const {Options, runTest} = require('browser-ui-test');
11
12 function showHelp() {
13     console.log("rustdoc-js options:");
14     console.log("  --doc-folder [PATH]        : location of the generated doc folder");
15     console.log("  --file [PATH]              : file to run (can be repeated)");
16     console.log("  --debug                    : show extra information about script run");
17     console.log("  --show-text                : render font in pages");
18     console.log("  --no-headless              : disable headless mode");
19     console.log("  --help                     : show this message then quit");
20     console.log("  --tests-folder [PATH]      : location of the .GOML tests folder");
21     console.log("  --jobs [NUMBER]            : number of threads to run tests on");
22     console.log("  --executable-path [PATH]   : path of the browser's executable to be used");
23 }
24
25 function isNumeric(s) {
26     return /^\d+$/.test(s);
27 }
28
29 function parseOptions(args) {
30     var opts = {
31         "doc_folder": "",
32         "tests_folder": "",
33         "files": [],
34         "debug": false,
35         "show_text": false,
36         "no_headless": false,
37         "jobs": -1,
38         "executable_path": null,
39         "no_sandbox": false,
40     };
41     var correspondances = {
42         "--doc-folder": "doc_folder",
43         "--tests-folder": "tests_folder",
44         "--debug": "debug",
45         "--show-text": "show_text",
46         "--no-headless": "no_headless",
47         "--executable-path": "executable_path",
48         "--no-sandbox": "no_sandbox",
49     };
50
51     for (var i = 0; i < args.length; ++i) {
52         if (args[i] === "--doc-folder"
53             || args[i] === "--tests-folder"
54             || args[i] === "--file"
55             || args[i] === "--jobs"
56             || args[i] === "--executable-path") {
57             i += 1;
58             if (i >= args.length) {
59                 console.log("Missing argument after `" + args[i - 1] + "` option.");
60                 return null;
61             }
62             if (args[i - 1] === "--jobs") {
63                 if (!isNumeric(args[i])) {
64                     console.log(
65                         "`--jobs` option expects a positive number, found `" + args[i] + "`");
66                     return null;
67                 }
68                 opts["jobs"] = parseInt(args[i]);
69             } else if (args[i - 1] !== "--file") {
70                 opts[correspondances[args[i - 1]]] = args[i];
71             } else {
72                 opts["files"].push(args[i]);
73             }
74         } else if (args[i] === "--help") {
75             showHelp();
76             process.exit(0);
77         } else if (args[i] === "--no-sandbox") {
78             console.log("`--no-sandbox` is being used. Be very careful!");
79             opts[correspondances[args[i]]] = true;
80         } else if (correspondances[args[i]]) {
81             opts[correspondances[args[i]]] = true;
82         } else {
83             console.log("Unknown option `" + args[i] + "`.");
84             console.log("Use `--help` to see the list of options");
85             return null;
86         }
87     }
88     if (opts["tests_folder"].length < 1) {
89         console.log("Missing `--tests-folder` option.");
90     } else if (opts["doc_folder"].length < 1) {
91         console.log("Missing `--doc-folder` option.");
92     } else {
93         return opts;
94     }
95     return null;
96 }
97
98 /// Print single char status information without \n
99 function char_printer(n_tests) {
100     const max_per_line = 10;
101     let current = 0;
102     return {
103         successful: function() {
104             current += 1;
105             if (current % max_per_line === 0) {
106                 process.stdout.write(`. (${current}/${n_tests})${os.EOL}`);
107             } else {
108                 process.stdout.write(".");
109             }
110         },
111         erroneous: function() {
112             current += 1;
113             if (current % max_per_line === 0) {
114                 process.stderr.write(`F (${current}/${n_tests})${os.EOL}`);
115             } else {
116                 process.stderr.write("F");
117             }
118         },
119         finish: function() {
120             if (current % max_per_line === 0) {
121                 // Don't output if we are already at a matching line end
122                 console.log("");
123             } else {
124                 const spaces = " ".repeat(max_per_line - (current % max_per_line));
125                 process.stdout.write(`${spaces} (${current}/${n_tests})${os.EOL}${os.EOL}`);
126             }
127         },
128     };
129 }
130
131 /// Sort array by .file_name property
132 function by_filename(a, b) {
133     return a.file_name - b.file_name;
134 }
135
136 async function main(argv) {
137     let opts = parseOptions(argv.slice(2));
138     if (opts === null) {
139         process.exit(1);
140     }
141
142     // Print successful tests too
143     let debug = false;
144     // Run tests in sequentially
145     let headless = true;
146     const options = new Options();
147     try {
148         // This is more convenient that setting fields one by one.
149         let args = [
150             "--variable", "DOC_PATH", opts["doc_folder"], "--enable-fail-on-js-error",
151         ];
152         if (opts["debug"]) {
153             debug = true;
154             args.push("--debug");
155         }
156         if (opts["show_text"]) {
157             args.push("--show-text");
158         }
159         if (opts["no_sandbox"]) {
160             args.push("--no-sandbox");
161         }
162         if (opts["no_headless"]) {
163             args.push("--no-headless");
164             headless = false;
165         }
166         if (opts["executable_path"] !== null) {
167             args.push("--executable-path");
168             args.push(opts["executable_path"]);
169         }
170         options.parseArguments(args);
171     } catch (error) {
172         console.error(`invalid argument: ${error}`);
173         process.exit(1);
174     }
175
176     let failed = false;
177     let files;
178     if (opts["files"].length === 0) {
179         files = fs.readdirSync(opts["tests_folder"]);
180     } else {
181         files = opts["files"];
182     }
183     files = files.filter(file => path.extname(file) == ".goml");
184     if (files.length === 0) {
185         console.error("rustdoc-gui: No test selected");
186         process.exit(2);
187     }
188     files.sort();
189
190     if (!headless) {
191         opts["jobs"] = 1;
192         console.log("`--no-headless` option is active, disabling concurrency for running tests.");
193     }
194
195     console.log(`Running ${files.length} rustdoc-gui (${opts["jobs"]} concurrently) ...`);
196
197     if (opts["jobs"] < 1) {
198         process.setMaxListeners(files.length + 1);
199     } else if (headless) {
200         process.setMaxListeners(opts["jobs"] + 1);
201     }
202
203     const tests_queue = [];
204     let results = {
205         successful: [],
206         failed: [],
207         errored: [],
208     };
209     const status_bar = char_printer(files.length);
210     for (let i = 0; i < files.length; ++i) {
211         const file_name = files[i];
212         const testPath = path.join(opts["tests_folder"], file_name);
213         const callback = runTest(testPath, options)
214             .then(out => {
215                 const [output, nb_failures] = out;
216                 results[nb_failures === 0 ? "successful" : "failed"].push({
217                     file_name: testPath,
218                     output: output,
219                 });
220                 if (nb_failures > 0) {
221                     status_bar.erroneous();
222                     failed = true;
223                 } else {
224                     status_bar.successful();
225                 }
226             })
227             .catch(err => {
228                 results.errored.push({
229                     file_name: testPath + file_name,
230                     output: err,
231                 });
232                 status_bar.erroneous();
233                 failed = true;
234             })
235             .finally(() => {
236                 // We now remove the promise from the tests_queue.
237                 tests_queue.splice(tests_queue.indexOf(callback), 1);
238             });
239         tests_queue.push(callback);
240         if (opts["jobs"] > 0 && tests_queue.length >= opts["jobs"]) {
241             await Promise.race(tests_queue);
242         }
243     }
244     if (tests_queue.length > 0) {
245         await Promise.all(tests_queue);
246     }
247     status_bar.finish();
248
249     if (debug) {
250         results.successful.sort(by_filename);
251         results.successful.forEach(r => {
252             console.log(r.output);
253         });
254     }
255
256     if (results.failed.length > 0) {
257         console.log("");
258         results.failed.sort(by_filename);
259         results.failed.forEach(r => {
260             console.log(r.file_name, r.output);
261         });
262     }
263     if (results.errored.length > 0) {
264         console.log(os.EOL);
265         // print run errors on the bottom so developers see them better
266         results.errored.sort(by_filename);
267         results.errored.forEach(r => {
268             console.error(r.file_name, r.output);
269         });
270     }
271
272     if (failed) {
273         process.exit(1);
274     }
275 }
276
277 main(process.argv);