@@ -9,6 +9,9 @@ const path = require("path");
99const os = require('os');
1010const {Options, runTest} = require('browser-ui-test');
1111
12+ // If a test fails or errors, we will retry it two more times in case it was a flaky failure.
13+ const NB_RETRY = 3;
14+
1215function showHelp() {
1316 console.log("rustdoc-js options:");
1417 console.log(" --doc-folder [PATH] : location of the generated doc folder");
@@ -129,11 +132,59 @@ function char_printer(n_tests) {
129132 };
130133}
131134
132- /// Sort array by .file_name property
135+ // Sort array by .file_name property
133136function by_filename(a, b) {
134137 return a.file_name - b.file_name;
135138}
136139
140+ async function runTests(opts, framework_options, files, results, status_bar, showTestFailures) {
141+ const tests_queue = [];
142+
143+ for (const testPath of files) {
144+ const callback = runTest(testPath, framework_options)
145+ .then(out => {
146+ const [output, nb_failures] = out;
147+ results[nb_failures === 0 ? "successful" : "failed"].push({
148+ file_name: testPath,
149+ output: output,
150+ });
151+ if (nb_failures === 0) {
152+ status_bar.successful();
153+ } else if (showTestFailures) {
154+ status_bar.erroneous();
155+ }
156+ })
157+ .catch(err => {
158+ results.errored.push({
159+ file_name: testPath,
160+ output: err,
161+ });
162+ if (showTestFailures) {
163+ status_bar.erroneous();
164+ }
165+ })
166+ .finally(() => {
167+ // We now remove the promise from the tests_queue.
168+ tests_queue.splice(tests_queue.indexOf(callback), 1);
169+ });
170+ tests_queue.push(callback);
171+ if (opts["jobs"] > 0 && tests_queue.length >= opts["jobs"]) {
172+ await Promise.race(tests_queue);
173+ }
174+ }
175+ if (tests_queue.length > 0) {
176+ await Promise.all(tests_queue);
177+ }
178+ }
179+
180+ function createEmptyResults() {
181+ return {
182+ successful: [],
183+ failed: [],
184+ errored: [],
185+ };
186+ }
187+
137188async function main(argv) {
138189 let opts = parseOptions(argv.slice(2));
139190 if (opts === null) {
@@ -144,7 +195,7 @@ async function main(argv) {
144195 let debug = false;
145196 // Run tests in sequentially
146197 let headless = true;
147- const options = new Options();
198+ const framework_options = new Options();
148199 try {
149200 // This is more convenient that setting fields one by one.
150201 let args = [
@@ -169,13 +220,12 @@ async function main(argv) {
169220 args.push("--executable-path");
170221 args.push(opts["executable_path"]);
171222 }
172- options .parseArguments(args);
223+ framework_options .parseArguments(args);
173224 } catch (error) {
174225 console.error(`invalid argument: ${error}`);
175226 process.exit(1);
176227 }
177228
178- let failed = false;
179229 let files;
180230 if (opts["files"].length === 0) {
181231 files = fs.readdirSync(opts["tests_folder"]);
@@ -187,6 +237,9 @@ async function main(argv) {
187237 console.error("rustdoc-gui: No test selected");
188238 process.exit(2);
189239 }
240+ files.forEach((file_name, index) => {
241+ files[index] = path.join(opts["tests_folder"], file_name);
242+ });
190243 files.sort();
191244
192245 if (!headless) {
@@ -215,52 +268,29 @@ async function main(argv) {
215268 };
216269 process.on('exit', exitHandling);
217270
218- const tests_queue = [];
219- let results = {
220- successful: [],
221- failed: [],
222- errored: [],
223- };
271+ const originalFilesLen = files.length;
272+ let results = createEmptyResults();
224273 const status_bar = char_printer(files.length);
225- for (let i = 0; i < files.length; ++i) {
226- const file_name = files[i];
227- const testPath = path.join(opts["tests_folder"], file_name);
228- const callback = runTest(testPath, options)
229- .then(out => {
230- const [output, nb_failures] = out;
231- results[nb_failures === 0 ? "successful" : "failed"].push({
232- file_name: testPath,
233- output: output,
234- });
235- if (nb_failures > 0) {
236- status_bar.erroneous();
237- failed = true;
238- } else {
239- status_bar.successful();
240- }
241- })
242- .catch(err => {
243- results.errored.push({
244- file_name: testPath + file_name,
245- output: err,
246- });
247- status_bar.erroneous();
248- failed = true;
249- })
250- .finally(() => {
251- // We now remove the promise from the tests_queue.
252- tests_queue.splice(tests_queue.indexOf(callback), 1);
253- });
254- tests_queue.push(callback);
255- if (opts["jobs"] > 0 && tests_queue.length >= opts["jobs"]) {
256- await Promise.race(tests_queue);
274+
275+ let new_results;
276+ for (let it = 0; it < NB_RETRY && files.length > 0; ++it) {
277+ new_results = createEmptyResults();
278+ await runTests(opts, framework_options, files, new_results, status_bar, it + 1 >= NB_RETRY);
279+ Array.prototype.push.apply(results.successful, new_results.successful);
280+ // We generate the new list of files with the previously failing tests.
281+ files = Array.prototype.concat(new_results.failed, new_results.errored);
282+ if (files.length > originalFilesLen / 2) {
283+ // If we have too many failing tests, it's very likely not flaky failures anymore so
284+ // no need to retry.
285+ break;
257286 }
258287 }
259- if (tests_queue.length > 0) {
260- await Promise.all(tests_queue);
261- }
288+
262289 status_bar.finish();
263290
291+ Array.prototype.push.apply(results.failed, new_results.failed);
292+ Array.prototype.push.apply(results.errored, new_results.errored);
293+
264294 // We don't need this listener anymore.
265295 process.removeListener("exit", exitHandling);
266296
@@ -287,7 +317,7 @@ async function main(argv) {
287317 });
288318 }
289319
290- if (failed) {
320+ if (results. failed.length > 0 || results.errored.length > 0 ) {
291321 process.exit(1);
292322 }
293323}
0 commit comments