ci: re-run flaky docs examples e2e tests in `test_docs_examples[_ivy]` jobs (#32497)

The docs examples e2e tests have been quite flaky recently. This causes
the `test_docs_examples` and `test_docs_examples_ivy` CircleCI jobs to
often fail (and block PRs) without a real reason.

This commit adds support for re-running failed docs examples e2e tests
and configures the `test_docs_examples` and `test_docs_examples_ivy`
jobs to try running each test that fails a second time, before giving up
and marking it as failed.

Closes #31841
Closes #31842

PR Close #32497
This commit is contained in:
George Kalpakas 2019-09-05 18:55:40 +03:00 committed by Matias Niemelä
parent 62d92f8091
commit 497d6b1323
2 changed files with 36 additions and 17 deletions

View File

@ -418,7 +418,7 @@ jobs:
# Run examples tests. The "CIRCLE_NODE_INDEX" will be set if "parallelism" is enabled. # Run examples tests. The "CIRCLE_NODE_INDEX" will be set if "parallelism" is enabled.
# Since the parallelism is set to "3", there will be three parallel CircleCI containers # Since the parallelism is set to "3", there will be three parallel CircleCI containers
# with either "0", "1" or "2" as node index. This can be passed to the "--shard" argument. # with either "0", "1" or "2" as node index. This can be passed to the "--shard" argument.
- run: yarn --cwd aio example-e2e --setup --local --cliSpecsConcurrency=5 --shard=${CIRCLE_NODE_INDEX}/${CIRCLE_NODE_TOTAL} - run: yarn --cwd aio example-e2e --setup --local --cliSpecsConcurrency=5 --shard=${CIRCLE_NODE_INDEX}/${CIRCLE_NODE_TOTAL} --retry 2
test_docs_examples_ivy: test_docs_examples_ivy:
<<: *job_defaults <<: *job_defaults
@ -444,7 +444,7 @@ jobs:
# Run examples tests with ivy. The "CIRCLE_NODE_INDEX" will be set if "parallelism" is enabled. # Run examples tests with ivy. The "CIRCLE_NODE_INDEX" will be set if "parallelism" is enabled.
# Since the parallelism is set to "3", there will be three parallel CircleCI containers # Since the parallelism is set to "3", there will be three parallel CircleCI containers
# with either "0", "1" or "2" as node index. This can be passed to the "--shard" argument. # with either "0", "1" or "2" as node index. This can be passed to the "--shard" argument.
- run: yarn --cwd aio example-e2e --setup --local --ivy --cliSpecsConcurrency=5 --shard=${CIRCLE_NODE_INDEX}/${CIRCLE_NODE_TOTAL} - run: yarn --cwd aio example-e2e --setup --local --ivy --cliSpecsConcurrency=5 --shard=${CIRCLE_NODE_INDEX}/${CIRCLE_NODE_TOTAL} --retry 2
# This job should only be run on PR builds, where `CI_PULL_REQUEST` is not `false`. # This job should only be run on PR builds, where `CI_PULL_REQUEST` is not `false`.
aio_preview: aio_preview:

View File

@ -36,10 +36,10 @@ if (argv.ivy) {
* Run Protractor End-to-End Tests for Doc Samples * Run Protractor End-to-End Tests for Doc Samples
* *
* Flags * Flags
* --filter to filter/select _example app subdir names * --filter to filter/select _example app subdir names
* e.g. --filter=foo // all example apps with 'foo' in their folder names. * e.g. --filter=foo // all example apps with 'foo' in their folder names.
* *
* --setup run yarn install, copy boilerplate and update webdriver * --setup to run yarn install, copy boilerplate and update webdriver
* e.g. --setup * e.g. --setup
* *
* --local to use the locally built Angular packages, rather than versions from npm * --local to use the locally built Angular packages, rather than versions from npm
@ -55,6 +55,9 @@ if (argv.ivy) {
* *
* --cliSpecsConcurrency Amount of CLI example specs that should be executed concurrently. * --cliSpecsConcurrency Amount of CLI example specs that should be executed concurrently.
* By default runs specs sequentially. * By default runs specs sequentially.
*
* --retry to retry failed tests (useful for overcoming flakes)
* e.g. --retry 3 // To try each test up to 3 times.
*/ */
function runE2e() { function runE2e() {
if (argv.setup) { if (argv.setup) {
@ -70,7 +73,7 @@ function runE2e() {
return Promise.resolve() return Promise.resolve()
.then(() => findAndRunE2eTests(argv.filter, outputFile, argv.shard, .then(() => findAndRunE2eTests(argv.filter, outputFile, argv.shard,
argv.cliSpecsConcurrency || DEFAULT_CLI_SPECS_CONCURRENCY)) argv.cliSpecsConcurrency || DEFAULT_CLI_SPECS_CONCURRENCY, argv.retry || 1))
.then((status) => { .then((status) => {
reportStatus(status, outputFile); reportStatus(status, outputFile);
if (status.failed.length > 0) { if (status.failed.length > 0) {
@ -85,7 +88,7 @@ function runE2e() {
// Finds all of the *e2e-spec.tests under the examples folder along with the corresponding apps // Finds all of the *e2e-spec.tests under the examples folder along with the corresponding apps
// that they should run under. Then run each app/spec collection sequentially. // that they should run under. Then run each app/spec collection sequentially.
function findAndRunE2eTests(filter, outputFile, shard, cliSpecsConcurrency) { function findAndRunE2eTests(filter, outputFile, shard, cliSpecsConcurrency, maxAttempts) {
const shardParts = shard ? shard.split('/') : [0, 1]; const shardParts = shard ? shard.split('/') : [0, 1];
const shardModulo = parseInt(shardParts[0], 10); const shardModulo = parseInt(shardParts[0], 10);
const shardDivider = parseInt(shardParts[1], 10); const shardDivider = parseInt(shardParts[1], 10);
@ -97,9 +100,22 @@ function findAndRunE2eTests(filter, outputFile, shard, cliSpecsConcurrency) {
fs.writeFileSync(outputFile, header); fs.writeFileSync(outputFile, header);
const status = {passed: [], failed: []}; const status = {passed: [], failed: []};
const updateStatus = (specPath, passed) => { const updateStatus = (specDescription, passed) => {
const arr = passed ? status.passed : status.failed; const arr = passed ? status.passed : status.failed;
arr.push(specPath); arr.push(specDescription);
};
const runTest = async (specPath, testFn) => {
let attempts = 0;
let passed = false;
while (true) {
attempts++;
passed = await testFn();
if (passed || (attempts >= maxAttempts)) break;
}
updateStatus(`${specPath} (attempts: ${attempts})`, passed);
}; };
return getE2eSpecs(EXAMPLES_PATH, filter) return getE2eSpecs(EXAMPLES_PATH, filter)
@ -117,12 +133,13 @@ function findAndRunE2eTests(filter, outputFile, shard, cliSpecsConcurrency) {
return e2eSpecPaths.systemjs return e2eSpecPaths.systemjs
.reduce( .reduce(
(promise, specPath) => { async (prevPromise, specPath) => {
return promise.then(() => { await prevPromise;
const examplePath = path.dirname(specPath);
return runE2eTestsSystemJS(examplePath, outputFile) const examplePath = path.dirname(specPath);
.then(passed => updateStatus(examplePath, passed)); const testFn = () => runE2eTestsSystemJS(examplePath, outputFile);
});
await runTest(examplePath, testFn);
}, },
Promise.resolve()) Promise.resolve())
.then(async () => { .then(async () => {
@ -138,9 +155,11 @@ function findAndRunE2eTests(filter, outputFile, shard, cliSpecsConcurrency) {
const bufferOutput = cliSpecsConcurrency > 1; const bufferOutput = cliSpecsConcurrency > 1;
while (specQueue.length) { while (specQueue.length) {
const chunk = specQueue.splice(0, cliSpecsConcurrency); const chunk = specQueue.splice(0, cliSpecsConcurrency);
await Promise.all(chunk.map((testDir, index) => { await Promise.all(chunk.map(testDir => {
return runE2eTestsCLI(testDir, outputFile, bufferOutput, ports.pop()) const port = ports.pop();
.then(passed => updateStatus(testDir, passed)); const testFn = () => runE2eTestsCLI(testDir, outputFile, bufferOutput, port);
return runTest(testDir, testFn);
})); }));
} }
}); });