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
57 changes: 50 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,72 @@ on:
branches: [ main, dev ]

jobs:
test:
name: Run Tests
# The suite is split into independent sections (scripts/ci/test_sections.sh)
# that run as parallel jobs. A test in a directory no section lists still runs
# in the `rest` section.
test-sections:
name: Tests (${{ matrix.section }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
section:
- core-data
- core-extensions
- core-theme
- core-ui
- features-workspace
- features-nosql
- features-app
- rest
steps:
- uses: actions/checkout@v4

- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.41.6'
channel: 'stable'
cache: true

- name: Install Linux dependencies (plugins)
run: |
sudo apt-get update
sudo apt-get install -y libsecret-1-dev

- name: Get dependencies
run: flutter pub get

- name: Run tests
run: flutter test --exclude-tags=golden

- name: Run tests (${{ matrix.section }})
run: |
set -euo pipefail
mapfile -t paths < <(./scripts/ci/test_sections.sh paths "${{ matrix.section }}")
if [ "${#paths[@]}" -eq 0 ]; then
echo "No tests in section ${{ matrix.section }}"
exit 0
fi
flutter test --exclude-tags=golden "${paths[@]}"

# Single required check for the whole suite: passes only when every section did.
test:
name: Run Tests
if: always()
needs: [test-sections, test-coverage]
runs-on: ubuntu-latest
steps:
- name: All test sections passed
run: |
echo "sections: ${{ needs.test-sections.result }}"
echo "coverage: ${{ needs.test-coverage.result }}"
[ "${{ needs.test-sections.result }}" = "success" ] && [ "${{ needs.test-coverage.result }}" = "success" ]

# Every test file must belong to exactly one section.
test-coverage:
name: Test sections cover all tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ./scripts/ci/test_sections.sh check

analyze:
name: Code Analysis
Expand Down
13 changes: 13 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,19 @@ flutter analyze
flutter test
```

CI runs the suite as parallel sections (`core-data`, `core-extensions`,
`core-theme`, `core-ui`, `features-workspace`, `features-nosql`,
`features-app`, `rest`) defined in `scripts/ci/test_sections.sh`. To run just
the one you touched:

```bash
flutter test $(scripts/ci/test_sections.sh paths features-workspace)
scripts/ci/test_sections.sh check # every test file is in exactly one section
```

A test in a directory no section lists runs in `rest`, so new directories need
no CI change; add the directory to a section if it belongs with one.

For release or large UI PRs, optionally smoke-test a build:

```bash
Expand Down
89 changes: 89 additions & 0 deletions scripts/ci/test_sections.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/usr/bin/env bash
# Splits the Flutter test suite into independent sections that CI runs in
# parallel jobs (see .github/workflows/ci.yml).
#
# scripts/ci/test_sections.sh list # section names, one per line
# scripts/ci/test_sections.sh paths <name> # test paths of one section
# scripts/ci/test_sections.sh check # every test file is in exactly one section
#
# A section is a set of test directories. The `rest` section is everything no
# other section claims, so a test added in a new directory is still run.
set -euo pipefail

cd "$(dirname "${BASH_SOURCE[0]}")/../.."

declare -A SECTIONS=(
[core-data]="test/core/database test/core/storage test/core/security test/core/actions test/core/app test/core/json test/core/csv test/core/util"
[core-extensions]="test/core/extensions test/core/market test/core/sdui test/core/updater test/features/extensions test/features/updater"
[core-theme]="test/core/theme test/shared"
[core-ui]="test/core/motion test/core/layout test/core/ui test/core/editor test/core/widgets test/core/platform test/core/demo"
[features-workspace]="test/features/workspace test/features/sql_workspaces test/features/sqlite test/features/mysql test/features/postgresql"
[features-nosql]="test/features/mongodb test/features/redis test/features/nosql"
[features-app]="test/features/main_screen test/features/connections test/features/settings test/features/command_palette test/features/onboarding test/features/macos test/features/help"
)
ORDER=(core-data core-extensions core-theme core-ui features-workspace features-nosql features-app rest)

all_tests() { find test -name '*_test.dart' | LC_ALL=C sort; }

claimed_dirs() {
local s
for s in "${!SECTIONS[@]}"; do echo "${SECTIONS[$s]}"; done | tr ' ' '\n'
}

section_files() {
local name="$1"
if [[ "$name" == "rest" ]]; then
local f d claimed
mapfile -t claimed < <(claimed_dirs)
while IFS= read -r f; do
local hit=0
for d in "${claimed[@]}"; do
[[ "$f" == "$d/"* ]] && { hit=1; break; }
done
(( hit )) || echo "$f"
done < <(all_tests)
return
fi
[[ -n "${SECTIONS[$name]:-}" ]] || { echo "unknown section: $name" >&2; exit 2; }
local d
for d in ${SECTIONS[$name]}; do
[[ -d "$d" ]] && find "$d" -name '*_test.dart'
done | LC_ALL=C sort
}

case "${1:-}" in
list)
printf '%s\n' "${ORDER[@]}"
;;
paths)
[[ -n "${2:-}" ]] || { echo "usage: $0 paths <section>" >&2; exit 2; }
# Directories keep the command line short; `rest` needs explicit files.
if [[ "$2" == "rest" ]]; then
section_files rest
else
for d in ${SECTIONS[$2]:-}; do [[ -d "$d" ]] && echo "$d"; done
fi
;;
check)
total=$(all_tests | wc -l)
sum=0
declare -A seen=()
for s in "${ORDER[@]}"; do
n=0
while IFS= read -r f; do
[[ -z "$f" ]] && continue
if [[ -n "${seen[$f]:-}" ]]; then
echo "in two sections: $f ($s and ${seen[$f]})" >&2; exit 1
fi
seen[$f]="$s"; n=$((n + 1))
done < <(section_files "$s")
printf '%-20s %3d test files\n' "$s" "$n"
sum=$((sum + n))
done
echo "total ${sum} of ${total}"
[[ "$sum" -eq "$total" ]] || { echo "some test files are in no section" >&2; exit 1; }
;;
*)
echo "usage: $0 {list|paths <section>|check}" >&2; exit 2
;;
esac
Loading