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
39 changes: 39 additions & 0 deletions .github/scripts/test_shard.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
# Run one slice of the test suite: test_shard.sh <index> <total>.
#
# Every *_test.dart under test/ belongs to exactly one slice (its position in
# the sorted list, modulo <total>), so the slices together are the whole suite.
# Inside a slice the files still run one at a time (--concurrency=1): the suite
# uses sqflite_common_ffi against real database files and parallel workers on
# ONE machine race on them. Separate CI jobs are separate machines.
#
# ponytail: slices are balanced by file COUNT, not by duration. If one slice
# becomes the slow one, split by recorded timings instead.
set -euo pipefail

index="$1"
total="$2"

# Listed into a variable first, so a failed `find` or `sort` fails the slice
# (set -e, pipefail). Feeding the loop straight from the pipeline would let a
# half-read test tree pass with some of its files never run.
all="$(find test -name '*_test.dart' | LC_ALL=C sort)"

files=()
i=0
while IFS= read -r f; do
if [ $((i % total)) -eq "$index" ]; then files+=("$f"); fi
i=$((i + 1))
done <<< "$all"

if [ "${#files[@]}" -eq 0 ]; then
echo "slice $index of $total holds no test files" >&2
exit 1
fi

echo "slice $index of $total: ${#files[@]} of $i test files"
if [ "${TEST_SHARD_LIST_ONLY:-}" = 1 ]; then
printf '%s\n' "${files[@]}"
exit 0
fi
flutter test --concurrency=1 --reporter=expanded "${files[@]}"
61 changes: 50 additions & 11 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ permissions:
contents: read

jobs:
test:
# Analyze, the lock guard and the zone-dependent tests: everything that is
# not the main suite.
checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -68,21 +70,58 @@ jobs:
- name: Analyze
run: flutter analyze

# --concurrency=1: the suite uses sqflite_common_ffi against real database
# files, and parallel workers race on them.
#
# The golden capture (whoop_hist.jsonl) is a real band recording kept
# beside the repo rather than committed to it, so the two derivation-replay
# tests SKIP here and run locally.
- name: Test
run: flutter test --concurrency=1 --reporter=expanded

# The WHOOP freeze goldens are per timezone (day labels are local), and
# the run above is UTC, so it only enforces the *_utc files and the other
# the main suite runs in UTC, so it only enforces the *_utc files and the other
# two skip. Re-run that one file under each committed offset. The Mi
# scale clock test reads the process zone and is a no-op under UTC, so it
# rides along.
- name: Zone-dependent tests (+05:30, -04:00)
run: |
TZ=Asia/Kolkata flutter test --concurrency=1 test/whoop_freeze_golden_test.dart test/adapters/miscale_test.dart
TZ=America/New_York flutter test --concurrency=1 test/whoop_freeze_golden_test.dart test/adapters/miscale_test.dart

# The main suite, split across machines. It ran as ONE job for about 75
# minutes, and a newer push to main cancels the run in progress
# (`cancel-in-progress` above), so runs on main were cancelled about as often
# as they finished. Each slice still runs its files one at a time; see
# .github/scripts/test_shard.sh.
#
# The golden capture (whoop_hist.jsonl) is a real band recording kept beside
# the repo rather than committed to it, so the two derivation-replay tests
# SKIP here and run locally.
suite:
runs-on: ubuntu-latest
strategy:
# One failing slice must not hide failures in the others.
fail-fast: false
matrix:
shard: [0, 1, 2, 3, 4, 5]
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false

# Same pin as `checks`; see the note there.
- uses: subosito/flutter-action@v2
with:
flutter-version: 3.41.6
channel: stable
cache: true

- name: Install dependencies
run: flutter pub get

- name: Test (slice ${{ matrix.shard }} of 6)
run: bash .github/scripts/test_shard.sh ${{ matrix.shard }} 6

# One check with the old job's name, so anything that waits for `test`
# (branch protection, a badge) keeps working. Fails unless every job above
# passed.
test:
if: always()
needs: [checks, suite]
runs-on: ubuntu-latest
steps:
- name: All checks and slices passed
run: |
[ "${{ needs.checks.result }}" = success ] && [ "${{ needs.suite.result }}" = success ]
Loading