Skip to content

Commit 674c220

Browse files
committed
add skipping passed examples
1 parent efc67db commit 674c220

File tree

5 files changed

+155
-8
lines changed

5 files changed

+155
-8
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# GitHub Actions Scripts
2+
3+
## Example Caching System
4+
5+
This directory contains scripts that implement a smart caching mechanism for running examples in CI:
6+
7+
### How it works
8+
9+
1. **`list_examples.sh`** - Discovers all runnable Python examples and generates a matrix that includes skip information
10+
2. **`example_cache.sh`** - Consolidated script with subcommands:
11+
- `check <example_file>` - Checks if an example should be skipped based on previous successful run and file modifications
12+
- `record <example_file>` - Records successful example runs with the current commit hash
13+
3. **`run_single_example.sh`** - Runs individual examples and records success when they complete
14+
15+
### Cache behavior
16+
17+
- Examples that passed on the current branch and haven't been modified since will be skipped
18+
- Cache is stored per branch using GitHub Actions cache
19+
- Cache keys include the branch name and commit hash
20+
- Fallback to main branch cache if current branch cache doesn't exist
21+
22+
### Benefits
23+
24+
- Dramatically reduces CI time when most examples are already passing
25+
- Only runs examples that are new, modified, or previously failed
26+
- Maintains full test coverage while optimizing for common scenarios
27+
28+
### Cache storage
29+
30+
Cache files are stored in `.example-cache/` with the format:
31+
- `{branch-name}-{example-path-normalized}.success` - Contains the commit hash of the last successful run

.github/scripts/example_cache.sh

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
5+
if [ $# -lt 2 ]; then
6+
echo "Usage: $0 <command> <example_file>"
7+
echo "Commands:"
8+
echo " record <example_file> - Record successful example run"
9+
echo " check <example_file> - Check if example should be skipped"
10+
exit 1
11+
fi
12+
13+
COMMAND="$1"
14+
EXAMPLE_FILE="$2"
15+
CACHE_DIR=".example-cache"
16+
BRANCH_REF="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME:-main}}"
17+
18+
# Normalize the branch name for cache key (replace / with -)
19+
CACHE_KEY=$(echo "$BRANCH_REF" | sed 's/\//-/g')
20+
SUCCESS_FILE="$CACHE_DIR/${CACHE_KEY}-$(echo "$EXAMPLE_FILE" | sed 's/\//-/g').success"
21+
22+
mkdir -p "$CACHE_DIR"
23+
24+
case "$COMMAND" in
25+
"record")
26+
# Record the current commit hash as successful
27+
echo "$GITHUB_SHA" > "$SUCCESS_FILE"
28+
echo "Recorded success for $EXAMPLE_FILE at commit $GITHUB_SHA"
29+
;;
30+
31+
"check")
32+
# Check if we have a previous success record
33+
if [ ! -f "$SUCCESS_FILE" ]; then
34+
echo "false" # No previous success, must run
35+
exit 0
36+
fi
37+
38+
# Get the commit hash from the success file
39+
LAST_SUCCESS_COMMIT=$(cat "$SUCCESS_FILE")
40+
41+
# Check if the example file has been modified since the last success
42+
if git diff --quiet "$LAST_SUCCESS_COMMIT" HEAD -- "$EXAMPLE_FILE"; then
43+
echo "true" # File unchanged since last success, can skip
44+
else
45+
echo "false" # File modified, must run
46+
fi
47+
;;
48+
49+
*)
50+
echo "Unknown command: $COMMAND"
51+
echo "Available commands: record, check"
52+
exit 1
53+
;;
54+
esac

.github/scripts/list_examples.sh

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,19 @@ while IFS= read -r -d '' file; do
1111
fi
1212
done < <(find examples -name '*.py' -type f -print0)
1313

14-
# Convert to JSON format for GitHub Actions matrix
15-
printf '{"example":['
14+
# Convert to JSON format for GitHub Actions matrix with skip information
15+
printf '{"include":['
1616
for i in "${!examples[@]}"; do
1717
if [ $i -gt 0 ]; then
1818
printf ','
1919
fi
20-
printf '"%s"' "${examples[$i]}"
20+
21+
# Check if this example should be skipped
22+
should_skip="false"
23+
if [ -f ".github/scripts/example_cache.sh" ]; then
24+
should_skip=$(bash .github/scripts/example_cache.sh check "${examples[$i]}" || echo "false")
25+
fi
26+
27+
printf '{"example":"%s","skip":%s}' "${examples[$i]}" "$should_skip"
2128
done
2229
printf ']}'

.github/scripts/run_single_example.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,8 @@ elif [[ $exit_code -ne 0 ]]; then
8383
exit $exit_code
8484
else
8585
echo "Script completed successfully."
86+
# Record success in cache
87+
if [ -f ".github/scripts/example_cache.sh" ]; then
88+
bash .github/scripts/example_cache.sh record "$EXAMPLE_FILE"
89+
fi
8690
fi

.github/workflows/run-examples.yml

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,32 @@ jobs:
1818
outputs:
1919
matrix: ${{ steps.generate.outputs.matrix }}
2020
examples-count: ${{ steps.generate.outputs.count }}
21+
total-count: ${{ steps.generate.outputs.total-count }}
2122
steps:
2223
- uses: actions/checkout@v4
24+
with:
25+
fetch-depth: 0 # Need full history for git diff
26+
27+
- name: Restore example cache
28+
uses: actions/cache@v4
29+
with:
30+
path: .example-cache
31+
key: examples-cache-${{ github.head_ref || github.ref_name }}-${{ github.sha }}
32+
restore-keys: |
33+
examples-cache-${{ github.head_ref || github.ref_name }}-
34+
examples-cache-main-
2335
2436
- name: Generate matrix
2537
id: generate
2638
run: |
2739
matrix=$(bash .github/scripts/list_examples.sh)
2840
echo "matrix=$matrix" >> $GITHUB_OUTPUT
29-
count=$(echo "$matrix" | jq '.example | length')
30-
echo "count=$count" >> $GITHUB_OUTPUT
31-
echo "Found $count runnable examples"
41+
total_count=$(echo "$matrix" | jq '.include | length')
42+
skipped_count=$(echo "$matrix" | jq '[.include[] | select(.skip == true)] | length')
43+
run_count=$((total_count - skipped_count))
44+
echo "total-count=$total_count" >> $GITHUB_OUTPUT
45+
echo "count=$run_count" >> $GITHUB_OUTPUT
46+
echo "Found $total_count total examples ($run_count to run, $skipped_count cached)"
3247
echo "$matrix" | jq .
3348
3449
# Second job: Run examples in parallel
@@ -89,6 +104,8 @@ jobs:
89104

90105
steps:
91106
- uses: actions/checkout@v4
107+
with:
108+
fetch-depth: 0 # Need full history for git diff
92109

93110
- name: Install uv
94111
uses: astral-sh/setup-uv@v2
@@ -104,7 +121,27 @@ jobs:
104121
restore-keys: |
105122
uv-${{ runner.os }}-
106123
124+
- name: Restore example cache
125+
uses: actions/cache@v4
126+
with:
127+
path: .example-cache
128+
key: examples-cache-${{ github.head_ref || github.ref_name }}-${{ github.sha }}
129+
restore-keys: |
130+
examples-cache-${{ github.head_ref || github.ref_name }}-
131+
examples-cache-main-
132+
133+
- name: Check if example should be skipped
134+
id: check-skip
135+
run: |
136+
if [[ "${{ matrix.skip }}" == "true" ]]; then
137+
echo "skip=true" >> $GITHUB_OUTPUT
138+
echo "✅ Skipping ${{ matrix.example }} (cached from previous successful run)"
139+
else
140+
echo "skip=false" >> $GITHUB_OUTPUT
141+
fi
142+
107143
- name: Pre-install common dependencies
144+
if: steps.check-skip.outputs.skip == 'false'
108145
run: |
109146
# Create a temporary environment with common ragbits packages to cache them
110147
uv venv --python 3.10 .temp-env
@@ -121,6 +158,7 @@ jobs:
121158
rm -rf .temp-env
122159
123160
- name: Run example - ${{ matrix.example }}
161+
if: steps.check-skip.outputs.skip == 'false'
124162
env:
125163
PR_BRANCH: ${{ github.head_ref }}
126164
GOOGLE_CLOUD_PROJECT: ${{ secrets.GCP_PROJECT_ID }}
@@ -135,6 +173,13 @@ jobs:
135173
chmod +x .github/scripts/run_single_example.sh
136174
./.github/scripts/run_single_example.sh "${{ matrix.example }}"
137175
176+
- name: Save example cache
177+
if: always()
178+
uses: actions/cache/save@v4
179+
with:
180+
path: .example-cache
181+
key: examples-cache-${{ github.head_ref || github.ref_name }}-${{ github.sha }}
182+
138183
# Summary job: Report overall results
139184
examples-summary:
140185
name: Examples summary
@@ -143,10 +188,16 @@ jobs:
143188
if: always()
144189
steps:
145190
- name: Check results
191+
env:
192+
TOTAL_COUNT: ${{ needs.generate-matrix.outputs.total-count }}
193+
RUN_COUNT: ${{ needs.generate-matrix.outputs.examples-count }}
146194
run: |
147195
echo "Examples matrix generation: ${{ needs.generate-matrix.result }}"
148196
echo "Examples execution: ${{ needs.examples.result }}"
149-
echo "Total examples: ${{ needs.generate-matrix.outputs.examples-count }}"
197+
echo "Total examples: $TOTAL_COUNT"
198+
echo "Examples run: $RUN_COUNT"
199+
cached_count=$((TOTAL_COUNT - RUN_COUNT))
200+
echo "Examples cached: $cached_count"
150201
151202
if [[ "${{ needs.generate-matrix.result }}" != "success" ]]; then
152203
echo "❌ Failed to generate examples matrix"
@@ -158,4 +209,4 @@ jobs:
158209
exit 1
159210
fi
160211
161-
echo "✅ All ${{ needs.generate-matrix.outputs.examples-count }} examples completed successfully"
212+
echo "✅ All examples completed successfully ($RUN_COUNT run, $cached_count cached)"

0 commit comments

Comments
 (0)