ci: split ci-mirror into <60s gate steps
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
file-length / file length <= 250 lines (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (mypy, mypy, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (pylint, pylint, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (rest, , mypy,pylint) (push) Waiting to run
Tests / test (push) Waiting to run
release-apk / release (APK) (push) Waiting to run
repo-contract / repo contract (push) Waiting to run

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzxdWXTUM9dpnCS2wtFkx4
This commit is contained in:
Krzysztof Rudnicki 2026-11-01 21:25:54 +01:00
parent 1b5e201136
commit 3960fb8ca0
3 changed files with 136 additions and 11 deletions

View File

@ -132,9 +132,82 @@ repos:
- repo: local
hooks:
- id: ci-mirror
name: CI mirror (clean-venv pytest + pre-commit --all-files)
entry: bash scripts/ci_mirror.sh
# Split into <60 s steps (run in this order); no-arg ci_mirror.sh is the CI form.
- id: ci-mirror-venv
name: CI mirror 1 (clean venv)
entry: bash scripts/ci_mirror.sh venv
language: system
pass_filenames: false
always_run: true
require_serial: true
stages: [manual]
- id: ci-mirror-precommit
name: CI mirror 2 (pre-commit --all-files)
entry: bash scripts/ci_mirror.sh precommit
language: system
pass_filenames: false
always_run: true
require_serial: true
stages: [manual]
- id: ci-mirror-pytest
name: CI mirror 3 (pytest, xvfb, 100% cov)
entry: bash scripts/ci_mirror.sh pytest
language: system
pass_filenames: false
always_run: true
require_serial: true
stages: [manual]
- id: ci-mirror-flutter-analyze
name: CI mirror 4 (flutter analyze)
entry: bash scripts/ci_mirror.sh flutter-analyze
language: system
pass_filenames: false
always_run: true
require_serial: true
stages: [manual]
- id: ci-mirror-flutter-test-1
name: CI mirror 5 (flutter test 1/6)
entry: bash scripts/ci_mirror.sh flutter-test --shard 0/6
language: system
pass_filenames: false
always_run: true
require_serial: true
stages: [manual]
- id: ci-mirror-flutter-test-2
name: CI mirror 6 (flutter test 2/6)
entry: bash scripts/ci_mirror.sh flutter-test --shard 1/6
language: system
pass_filenames: false
always_run: true
require_serial: true
stages: [manual]
- id: ci-mirror-flutter-test-3
name: CI mirror 7 (flutter test 3/6)
entry: bash scripts/ci_mirror.sh flutter-test --shard 2/6
language: system
pass_filenames: false
always_run: true
require_serial: true
stages: [manual]
- id: ci-mirror-flutter-test-4
name: CI mirror 8 (flutter test 4/6)
entry: bash scripts/ci_mirror.sh flutter-test --shard 3/6
language: system
pass_filenames: false
always_run: true
require_serial: true
stages: [manual]
- id: ci-mirror-flutter-test-5
name: CI mirror 9 (flutter test 5/6)
entry: bash scripts/ci_mirror.sh flutter-test --shard 4/6
language: system
pass_filenames: false
always_run: true
require_serial: true
stages: [manual]
- id: ci-mirror-flutter-test-6
name: CI mirror 10 (flutter test 6/6)
entry: bash scripts/ci_mirror.sh flutter-test --shard 5/6
language: system
pass_filenames: false
always_run: true

View File

@ -2,7 +2,7 @@ name: diet_guard_app
description: "Companion phone app for diet_guard: log meals on the go."
publish_to: 'none'
version: 1.0.125+1
version: 1.0.126+1
environment:
sdk: ^3.12.2

View File

@ -119,27 +119,79 @@ flutter_clean_env() {
# hooks) then the full test suite. Deliberately fails rather than skips when
# flutter is missing -- a gate that quietly passes on a machine without the
# toolchain is the same as no gate.
run_flutter_gates() {
require_app() {
if [[ ! -f "$APP_DIR/pubspec.yaml" ]]; then
log "no app/pubspec.yaml — skipping the Flutter gates"
return
return 1
fi
if ! command -v flutter >/dev/null 2>&1; then
fail "flutter is not on PATH but app/ exists (cannot verify the app)"
fi
}
run_flutter_analyze() {
require_app || return 0
log "flutter analyze (app/)"
(cd "$APP_DIR" && flutter_clean_env analyze) || fail "flutter analyze"
}
run_flutter_test() {
require_app || return 0
log "flutter test (app/)"
(cd "$APP_DIR" && flutter_clean_env test) || fail "flutter test"
}
run_flutter_gates() {
run_flutter_analyze
run_flutter_test
}
# Bucket I of N of the app's test files (every N-th, sorted), so the N buckets
# partition the suite exactly. Each bucket stays under the 60 s action cap.
run_flutter_test_shard() {
[[ ${1:-} =~ ^([0-9]+)/([1-9][0-9]*)$ ]] || fail "flutter-test --shard wants I/N, got '${1:-}'"
local index=${BASH_REMATCH[1]} total=${BASH_REMATCH[2]} hooks i=0 file
local -a files=()
hooks="$(grep -c '^ - id: ci-mirror-flutter-test-[0-9]' "$ROOT/.pre-commit-config.yaml" || true)"
((total == hooks && index < total)) \
|| fail "shard $index/$total does not match the $hooks ci-mirror-flutter-test-N hooks"
require_app || return 0
while IFS= read -r file; do
if ((i % total == index)); then
files+=("${file#"$APP_DIR"/}")
fi
i=$((i + 1))
done < <(find "$APP_DIR/test" -name '*_test.dart' | LC_ALL=C sort)
((${#files[@]} > 0)) || fail "flutter test shard $index/$total has no test files"
log "flutter test shard $index/$total (${#files[@]} files)"
(cd "$APP_DIR" && flutter_clean_env test "${files[@]}") || fail "flutter test shard $index/$total"
}
# No phase argument (or pytest options) runs everything, exactly as CI does.
main() {
require_file
ensure_venv
run_precommit_all_files
run_pytest_clean_venv "$@"
run_flutter_gates
log "all CI gates passed locally — safe to push"
case "${1:-all}" in
venv) ensure_venv ;;
precommit) run_precommit_all_files ;;
pytest)
shift
ensure_venv
run_pytest_clean_venv "$@"
;;
flutter-analyze) run_flutter_analyze ;;
flutter-test)
[[ "${2:-}" == "--shard" ]] || fail "usage: ci_mirror.sh flutter-test --shard I/N"
run_flutter_test_shard "${3:-}"
;;
*)
[[ "${1:-}" == "all" ]] && shift
ensure_venv
run_precommit_all_files
run_pytest_clean_venv "$@"
run_flutter_gates
;;
esac
log "gates passed locally — safe to push"
}
main "$@"