Skip to content

Commit

Permalink
🏗 Turbo-charge CircleCI parallelization for faster overall builds (#3…
Browse files Browse the repository at this point in the history
…9861)

* Parallelize `dist` builds on CircleCI
* Consolidate all the different `dist` jobs in CircleCI under a reusuable file
* Split browser tests by test type
  • Loading branch information
danielrozenberg authored Feb 28, 2024
1 parent 2797217 commit 8007632
Show file tree
Hide file tree
Showing 15 changed files with 328 additions and 348 deletions.
141 changes: 83 additions & 58 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,24 @@ experiment_job: &experiment_job
environment:
FLAVOR: experiment<< parameters.exp >>

dist_job: &dist_job
parameters:
module:
description: 'Whether to build Module or Nomodule'
type: enum
enum: ['Module', 'Nomodule']
purpose:
description: 'What is the downstream purpose of this build'
type: enum
enum: ['Test', 'Bundle Size']

test_types_job: &test_types_job
parameters:
test_type:
description: 'Which test type to run'
type: enum
enum: ['Unit', 'Integration', 'End-to-End']

executors:
base-docker-small:
docker:
Expand Down Expand Up @@ -236,41 +254,26 @@ jobs:
name: '⭐⭐⭐ Unminified Build ⭐⭐⭐'
command: node build-system/pr-check/unminified-build.js
- teardown_vm
nomodule_build_test:
executor:
name: node-docker-xlarge
steps:
- setup_vm
- run:
name: '⭐⭐⭐ Nomodule Build ⭐⭐⭐'
command: node build-system/pr-check/nomodule-build.js
- teardown_vm
module_build_test:
dist:
executor:
name: node-docker-xlarge
<<: *dist_job
parallelism: 3
steps:
- setup_vm
- run:
name: '⭐⭐⭐ Module Build ⭐⭐⭐'
command: node build-system/pr-check/module-build.js
name: '⭐⭐⭐ << parameters.module >> Build (<< parameters.purpose >>) ⭐⭐⭐'
command: node build-system/pr-check/dist.js --type "<< parameters.module >> Build (<< parameters.purpose >>)"
- teardown_vm
nomodule_build_bundle_size:
dist_3p:
executor:
name: node-docker-xlarge
<<: *dist_job
steps:
- setup_vm
- run:
name: '⭐⭐⭐ Nomodule Build ⭐⭐⭐'
command: node build-system/pr-check/bundle-size-nomodule-build.js
- teardown_vm
module_build_bundle_size:
executor:
name: node-docker-xlarge
steps:
- setup_vm
- run:
name: '⭐⭐⭐ Module Build ⭐⭐⭐'
command: node build-system/pr-check/bundle-size-module-build.js
name: '⭐⭐⭐ << parameters.module >> 3p Build (<< parameters.purpose >>) ⭐⭐⭐'
command: node build-system/pr-check/dist.js --type "<< parameters.module >> 3p Build (<< parameters.purpose >>)"
- teardown_vm
bundle_size:
executor:
Expand Down Expand Up @@ -395,34 +398,37 @@ jobs:
browser_tests_safari:
executor:
name: macos-medium
<<: *test_types_job
steps:
- setup_vm
- enable_safari_automation
- run:
name: '⭐⭐⭐ Browser Tests (Safari) ⭐⭐⭐'
command: node build-system/pr-check/browser-tests.js --browser=safari
name: '⭐⭐⭐ << parameters.test_type >> Tests (Safari) ⭐⭐⭐'
command: node build-system/pr-check/browser-tests.js --browser=safari --type=<< parameters.test_type >>
- store_test_output
- teardown_vm
browser_tests_firefox:
executor:
name: node-docker-medium
<<: *test_types_job
steps:
- setup_vm
- install_firefox
- run:
name: '⭐⭐⭐ Browser Tests (Firefox) ⭐⭐⭐'
command: node build-system/pr-check/browser-tests.js --browser=firefox
name: '⭐⭐⭐ << parameters.test_type >> Tests (Firefox) ⭐⭐⭐'
command: node build-system/pr-check/browser-tests.js --browser=firefox --type=<< parameters.test_type >>
- store_test_output
- teardown_vm
browser_tests_edge:
executor:
name: node-docker-medium
<<: *test_types_job
steps:
- setup_vm
- install_edge
- run:
name: '⭐⭐⭐ Browser Tests (Edge) ⭐⭐⭐'
command: node build-system/pr-check/browser-tests.js --browser=edge
name: '⭐⭐⭐ << parameters.test_type >> Tests (Edge) ⭐⭐⭐'
command: node build-system/pr-check/browser-tests.js --browser=edge --type=<< parameters.test_type >>
- store_test_output
- teardown_vm
experiment_build:
Expand Down Expand Up @@ -525,32 +531,32 @@ workflows:
<<: *push_and_pr_builds
requires:
- 'Initialize Repository'
- nomodule_build_test:
name: 'Nomodule Build (Test)'
<<: *push_and_pr_builds
requires:
- 'Initialize Repository'
- module_build_test:
name: 'Module Build (Test)'
<<: *push_and_pr_builds
requires:
- 'Initialize Repository'
- nomodule_build_bundle_size:
name: 'Nomodule Build (Bundle Size)'
- dist:
matrix:
parameters:
module: ['Module', 'Nomodule']
purpose: ['Test', 'Bundle Size']
name: '⛓️ << matrix.module >> Build (<< matrix.purpose >>)'
<<: *push_and_pr_builds
requires:
- 'Initialize Repository'
- module_build_bundle_size:
name: 'Module Build (Bundle Size)'
- dist_3p:
matrix:
parameters:
module: ['Module', 'Nomodule']
purpose: ['Test', 'Bundle Size']
name: '<< matrix.module >> 3p Build (<< matrix.purpose >>)'
<<: *push_and_pr_builds
requires:
- 'Initialize Repository'
- bundle_size:
name: 'Bundle Size'
<<: *push_and_pr_builds
requires:
- 'Nomodule Build (Bundle Size)'
- 'Module Build (Bundle Size)'
- '⛓️ Nomodule Build (Bundle Size)'
- 'Nomodule 3p Build (Bundle Size)'
- '⛓️ Module Build (Bundle Size)'
- 'Module 3p Build (Bundle Size)'
- validator_tests:
name: 'Validator Tests'
<<: *push_and_pr_builds
Expand All @@ -560,8 +566,10 @@ workflows:
name: 'Visual Diff Tests'
<<: *push_and_pr_builds
requires:
- 'Module Build (Test)'
- 'Nomodule Build (Test)'
- '⛓️ Module Build (Test)'
- 'Module 3p Build (Test)'
- '⛓️ Nomodule Build (Test)'
- 'Nomodule 3p Build (Test)'
- local_unit_tests:
name: 'Local Unit Tests'
<<: *push_and_pr_builds
Expand All @@ -584,37 +592,54 @@ workflows:
config: ['prod', 'canary']
<<: *push_and_pr_builds
requires:
- 'Nomodule Build (Test)'
- '⛓️ Nomodule Build (Test)'
- 'Nomodule 3p Build (Test)'
- module_tests:
name: 'Module Tests (<< matrix.config >>)'
matrix:
parameters:
config: ['prod', 'canary']
<<: *push_and_pr_builds
requires:
- 'Nomodule Build (Test)'
- 'Module Build (Test)'
- '⛓️ Module Build (Test)'
- 'Module 3p Build (Test)'
- '⛓️ Nomodule Build (Test)'
- 'Nomodule 3p Build (Test)'
- end_to_end_tests:
name: '⛓️ End-to-End Tests'
<<: *push_and_pr_builds
requires:
- 'Nomodule Build (Test)'
- '⛓️ Nomodule Build (Test)'
- 'Nomodule 3p Build (Test)'
- browser_tests_safari:
name: 'Browser Tests (Safari)'
name: '<< matrix.test_type >> Tests (Safari)'
matrix:
parameters:
test_type: ['Unit', 'Integration', 'End-to-End']
<<: *push_and_pr_builds
requires:
- 'Initialize for Mac OS'
- 'Nomodule Build (Test)'
- '⛓️ Nomodule Build (Test)'
- 'Nomodule 3p Build (Test)'
- browser_tests_firefox:
name: 'Browser Tests (Firefox)'
name: '<< matrix.test_type >> Tests (Firefox)'
matrix:
parameters:
test_type: ['Unit', 'Integration', 'End-to-End']
<<: *push_and_pr_builds
requires:
- 'Nomodule Build (Test)'
- '⛓️ Nomodule Build (Test)'
- 'Nomodule 3p Build (Test)'
- browser_tests_edge:
name: 'Browser Tests (Edge)'
name: '<< matrix.test_type >> Tests (Edge)'
matrix:
parameters:
# Note: we can't run e2e tests on Edge.
test_type: ['Unit', 'Integration']
<<: *push_and_pr_builds
requires:
- 'Nomodule Build (Test)'
- '⛓️ Nomodule Build (Test)'
- 'Nomodule 3p Build (Test)'
- experiment_build:
name: 'Experiment << matrix.exp >> Build'
matrix:
Expand Down
34 changes: 29 additions & 5 deletions build-system/common/ci.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,12 +184,34 @@ function circleciPrMergeCommit() {
}

/**
* Returns an identifier that is unique to each CircleCI job. This is different
* from the workflow ID, which is common across all jobs in a workflow.
* Returns an identifier that is unique to each CircleCI build, discerning for
* parallelized builds
*
* Note that this is different from the workflow ID, which is common across all,
* and also different from the build number, which is common across parallelized
* builds.
* @return {string}
*/
function circleciBuildNumber() {
return isCircleci ? env('CIRCLE_BUILD_NUM') : '';
function circleciUniqueBuildNumber() {
return isCircleci
? `${env('CIRCLE_BUILD_NUM')}.${env('CIRCLE_NODE_INDEX')}`
: '';
}

/**
* Returns true for parallelized CircleCI builds.
* @return {boolean}
*/
function circleciIsParallelized() {
return isCircleci && env('CIRCLE_NODE_TOTAL') != '1';
}

/**
* Returns the parallelized build's shard number.
* @return {number}
*/
function circleciNodeIndex() {
return isCircleci ? Number(env('CIRCLE_NODE_INDEX')) : 0;
}

/**
Expand Down Expand Up @@ -222,7 +244,9 @@ module.exports = {
ciPullRequestBranch,
ciPullRequestSha,
ciPushBranch,
circleciBuildNumber,
circleciUniqueBuildNumber,
circleciIsParallelized,
circleciNodeIndex,
circleciPrMergeCommit,
ciRepoSlug,
isCiBuild,
Expand Down
58 changes: 22 additions & 36 deletions build-system/pr-check/browser-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,34 @@
* @fileoverview Script that runs tests on Safari / Firefox / Edge during CI.
*/

const {
skipDependentJobs,
timedExecOrDie,
timedExecOrThrow,
} = require('./utils');
const {browser} = require('minimist')(process.argv.slice(2));
const {skipDependentJobs, timedExecOrThrow} = require('./utils');
const {runCiJob} = require('./ci-job');
const {Targets, buildTargetsInclude} = require('./build-targets');

const argv = require('minimist')(process.argv.slice(2));
const {type} = argv;
const browser = argv.browser.toLowerCase();

const jobName = 'browser-tests.js';

const COMMANDS = {
'Unit': `amp unit --${browser}`,
'Integration': `amp integration --nobuild --minified --${browser}`,
'End-to-End': `amp e2e --nobuild --minified --browsers=${browser}`,
};

const INDIVIDUAL_TARGET = {
'Unit': Targets.UNIT_TEST,
'Integration': Targets.INTEGRATION_TEST,
'End-to-End': Targets.E2E_TEST,
};

/**
* Steps to run during push builds.
*/
function pushBuildWorkflow() {
try {
timedExecOrThrow(`amp unit --${browser}`, 'Unit tests failed!');
timedExecOrThrow(
`amp integration --nobuild --minified --${browser}`,
'Integration tests failed!'
);
timedExecOrThrow(
`amp e2e --nobuild --minified --browsers=${browser}`,
'End-to-end tests failed!'
);
timedExecOrThrow(COMMANDS[type], `${type} tests failed!`);
} catch (e) {
if (e.status) {
process.exitCode = e.status;
Expand All @@ -40,32 +43,15 @@ function pushBuildWorkflow() {
* Steps to run during PR builds.
*/
function prBuildWorkflow() {
if (
!buildTargetsInclude(
Targets.RUNTIME,
Targets.UNIT_TEST,
Targets.E2E_TEST,
Targets.INTEGRATION_TEST
)
) {
if (!buildTargetsInclude(Targets.RUNTIME, INDIVIDUAL_TARGET[type])) {
skipDependentJobs(
jobName,
'this PR does not affect the runtime, unit tests, integration tests, or end-to-end tests'
`this PR does not affect the runtime or ${type} tests`
);
return;
}
if (buildTargetsInclude(Targets.RUNTIME, Targets.UNIT_TEST)) {
timedExecOrDie(`amp unit --${browser}`);
}
if (buildTargetsInclude(Targets.RUNTIME, Targets.INTEGRATION_TEST)) {
timedExecOrDie(`amp integration --nobuild --minified --${browser}`);
}
if (
buildTargetsInclude(Targets.RUNTIME, Targets.E2E_TEST) &&
['safari', 'firefox'].includes(browser) // E2E tests can't be run on Edge.
) {
timedExecOrDie(`amp e2e --nobuild --minified --browsers=${browser}`);
}

pushBuildWorkflow();
}

runCiJob(jobName, pushBuildWorkflow, prBuildWorkflow);
Loading

0 comments on commit 8007632

Please sign in to comment.