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