Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion .github/workflows/check-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,17 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
# Tests are split across hosts into groups by package (see
# compute-test-group.mjs). When changing the group count, keep
# TOTAL_GROUPS below in sync with the number of list entries here.
group: [1, 2, 3, 4]
fail-fast: false

runs-on: ${{ matrix.os }}

env:
TOTAL_GROUPS: 4

# Steps represent a sequence of tasks that will be executed as part of the job
steps:
- name: Sets env vars
Expand Down Expand Up @@ -90,14 +97,21 @@ jobs:
npx lerna@${LERNA_VERSION} ls --all --since ${SINCE_REF}
shell: bash

# Checks are not split into groups; run them once per OS on the first group.
- name: Run Checks
if: ${{ matrix.group == 1 }}
run: npm run check-ci -- --stream
shell: bash

- name: Run Tests
run: npm run test-ci -- --stream
run: |
scopes=$(node .github/workflows/compute-test-group.mjs "${{ matrix.group }}" "${TOTAL_GROUPS}")
echo "Testing group ${{ matrix.group }}/${TOTAL_GROUPS} with: ${scopes}"
npm run test-ci -- --stream ${scopes}
shell: bash

# Each group uploads coverage for the packages it tested; the parallel
# build is finalized once by the separate coverage-finalize job below.
- name: Report Coverage
if: ${{ runner.os == 'Linux' }}
run: |
Expand All @@ -108,7 +122,22 @@ jobs:
flag_name=$(sed -E 's/packages\/([^\/]*)\/coverage\/lcov.info/\1/g' <<<$report)
coveralls report --base-path . --job-flag=$flag_name $report --parallel --no-logo
done
env:
COVERALLS_GIT_BRANCH: ${{ github.head_ref || github.ref_name }}
COVERALLS_REPO_TOKEN: ${{ github.token }}
COVERALLS_GIT_COMMIT: ${{ github.event.pull_request.head.sha || github.sha }}

# Signals to Coveralls that all parallel coverage uploads from the grouped
# check-and-test jobs are complete.
coverage-finalize:
name: Finalize Coverage
needs: check-and-test
if: ${{ always() && needs.check-and-test.result != 'skipped' }}
runs-on: ubuntu-latest
steps:
- name: Finalize Coveralls parallel build
run: |
curl -L https://coveralls.io/coveralls-linux.tar.gz | tar -xz -C /usr/local/bin
coveralls done
env:
COVERALLS_GIT_BRANCH: ${{ github.head_ref || github.ref_name }}
Expand Down
66 changes: 66 additions & 0 deletions .github/workflows/compute-test-group.mjs

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to not use typescript here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Node.js 22 is currently the test node here so it would need a flag to run the ts, I could do the flag, or I could upgrade the node, I chose neither 🤣

Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#! /usr/bin/env node
// Prints the `lerna run` filter flags (`--scope <name> ...`) for the packages
// that belong to a given test group. Packages are distributed round-robin over
// the groups after sorting by name, so the assignment is stable across runs and
// every package with a `test-ci` script lands in exactly one group.
//
// Usage: node compute-test-group.mjs <groupIndex> <totalGroups>
// groupIndex is 1-based.

import { readFileSync, readdirSync, existsSync } from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';

const groupIndex = Number.parseInt(process.argv[2], 10);
const totalGroups = Number.parseInt(process.argv[3], 10);

if (
!Number.isInteger(groupIndex) ||
!Number.isInteger(totalGroups) ||
groupIndex < 1 ||
totalGroups < 1 ||
groupIndex > totalGroups
) {
throw new Error(
`Invalid group arguments: groupIndex=${process.argv[2]} totalGroups=${process.argv[3]} ` +
`(expected 1 <= groupIndex <= totalGroups)`,
);
}

// Resolve the repo root relative to this script (.github/workflows/).
const repoRoot = path.resolve(fileURLToPath(import.meta.url), '../../..');

// Keep this in sync with the `packages` globs in lerna.json.
const workspaceGlobs = ['packages', 'configs'];
const explicitDirs = ['scripts'];

const packageDirs = [
...workspaceGlobs.flatMap((glob) => {
const base = path.join(repoRoot, glob);
if (!existsSync(base)) return [];
return readdirSync(base, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.map((entry) => path.join(base, entry.name));
}),
...explicitDirs.map((dir) => path.join(repoRoot, dir)),
];

const testablePackages = packageDirs
.map((dir) => path.join(dir, 'package.json'))
.filter((manifestPath) => existsSync(manifestPath))
.map((manifestPath) => JSON.parse(readFileSync(manifestPath, 'utf8')))
.filter((manifest) => manifest.scripts && manifest.scripts['test-ci'])
.map((manifest) => manifest.name)
.sort();

const groupPackages = testablePackages.filter(
(_name, index) => index % totalGroups === groupIndex - 1,
);

if (groupPackages.length === 0) {
// Emit a filter that matches nothing so `lerna run` becomes a no-op rather
// than falling back to running every package.
process.stdout.write('--scope __no_packages_in_this_group__');
} else {
process.stdout.write(groupPackages.map((name) => `--scope ${name}`).join(' '));
}
Loading
Loading