Skip to content

Commit c1927e6

Browse files
authored
Shorten the slowest CI legs: website build, format check, rewatch polling (#9302)
1 parent 1efa212 commit c1927e6

6 files changed

Lines changed: 111 additions & 133 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,10 @@ jobs:
188188
if: matrix.test_commit_records
189189
run: yarn test-upsert-comment
190190

191+
# The format check gives the same result everywhere; it runs on Linux
192+
# only, where it is fastest.
191193
- name: Run tests
192-
run: node scripts/test.js -all
194+
run: node scripts/test.js ${{ runner.os == 'Linux' && '-all' || '-ounit -mocha -build -docstrings' }}
193195

194196
- name: Check for diffs in tests folder
195197
run: git diff --ignore-cr-at-eol --exit-code tests
@@ -393,50 +395,80 @@ jobs:
393395

394396
# Checks that the website builds with the generated API docs. These
395397
# steps stay in this job rather than in one that needs build-compiler,
396-
# which would wait for the slowest platform before starting.
397-
- name: Checkout rescript-lang.org
398+
# which would wait for the slowest platform before starting. The build
399+
# depends only on the docs and the website commit, so an empty cache
400+
# entry keyed on both records each pair that built, and a pull request
401+
# whose docs match an earlier build skips it.
402+
- name: Website build key
403+
id: website-build-key
398404
if: ${{ matrix.generate_api_docs }}
405+
env:
406+
WEBSITE_BRANCH: ${{ steps.api-docs-plan.outputs.website-branch }}
407+
run: |
408+
sha=$(git ls-remote https://github.com/rescript-lang/rescript-lang.org.git "refs/heads/$WEBSITE_BRANCH" | cut -f1)
409+
if [ -z "$sha" ]; then
410+
echo "::error::The website has no branch $WEBSITE_BRANCH"
411+
exit 1
412+
fi
413+
echo "sha=$sha" >> "$GITHUB_OUTPUT"
414+
echo "value=website-build-v1-$sha-${{ hashFiles('scripts/res/apiDocs/**') }}" >> "$GITHUB_OUTPUT"
415+
shell: bash
416+
417+
- name: Look up website build
418+
id: website-build
419+
if: ${{ matrix.generate_api_docs }}
420+
uses: actions/cache/restore@v6
421+
with:
422+
path: tmp/website-build
423+
key: ${{ steps.website-build-key.outputs.value }}
424+
lookup-only: true
425+
426+
- name: Checkout rescript-lang.org
427+
if: ${{ matrix.generate_api_docs && steps.website-build.outputs.cache-hit != 'true' }}
399428
uses: actions/checkout@v7
400429
with:
401430
repository: rescript-lang/rescript-lang.org
402-
ref: ${{ steps.api-docs-plan.outputs.website-branch }}
431+
ref: ${{ steps.website-build-key.outputs.sha }}
403432
path: rescript-lang.org
404433

405-
- name: Check for API docs changes
406-
id: api-docs-diff
407-
if: ${{ matrix.generate_api_docs }}
408-
run: |
409-
cp -R scripts/res/apiDocs/. rescript-lang.org/data/api/
410-
git -C rescript-lang.org status
411-
if [[ -z "$(git -C rescript-lang.org status --porcelain)" ]]; then
412-
echo "changed=false" >> "$GITHUB_OUTPUT"
413-
else
414-
echo "changed=true" >> "$GITHUB_OUTPUT"
415-
fi
416-
shell: bash
417-
418434
# The website is checked out inside this repository, so its Yarn also
419435
# reads this repository's .yarnrc.yml and rejects the settings it does
420436
# not know yet. The steps that need this config are done.
421437
- name: Hide the compiler's Yarn config from the website
422-
if: ${{ matrix.generate_api_docs && steps.api-docs-diff.outputs.changed == 'true' }}
438+
if: ${{ matrix.generate_api_docs && steps.website-build.outputs.cache-hit != 'true' }}
423439
run: mv .yarnrc.yml "$RUNNER_TEMP/rescript.yarnrc.yml"
424440
shell: bash
425441

426442
- name: Use Node.js for website
427-
if: ${{ matrix.generate_api_docs && steps.api-docs-diff.outputs.changed == 'true' }}
443+
if: ${{ matrix.generate_api_docs && steps.website-build.outputs.cache-hit != 'true' }}
428444
uses: actions/setup-node@v7
429445
with:
430446
cache: yarn
431447
cache-dependency-path: rescript-lang.org/yarn.lock
432448
node-version-file: rescript-lang.org/.node-version
433449

434450
- name: Build website with the API docs
435-
if: ${{ matrix.generate_api_docs && steps.api-docs-diff.outputs.changed == 'true' }}
451+
if: ${{ matrix.generate_api_docs && steps.website-build.outputs.cache-hit != 'true' }}
436452
run: |
453+
cp -R ../scripts/res/apiDocs/. data/api/
437454
yarn
438455
yarn build
439456
working-directory: rescript-lang.org
457+
shell: bash
458+
459+
- name: Record website build
460+
if: ${{ matrix.generate_api_docs && steps.website-build.outputs.cache-hit != 'true' }}
461+
run: mkdir -p tmp/website-build && echo "$KEY" > tmp/website-build/key
462+
env:
463+
KEY: ${{ steps.website-build-key.outputs.value }}
464+
shell: bash
465+
466+
- name: Save website build record
467+
if: ${{ matrix.generate_api_docs && steps.website-build.outputs.cache-hit != 'true' }}
468+
uses: actions/cache/save@v6
469+
with:
470+
path: tmp/website-build
471+
key: ${{ steps.website-build-key.outputs.value }}
440472

441473
# Compares the words the compiler allocates on fixed inputs, and the
442474
# instructions it executes on some of them, with the base branch

‎rewatch/tests/utils.sh‎

Lines changed: 29 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,21 @@ replace() {
6060
fi
6161
}
6262

63-
wait_for_pid_gone() {
64-
local pid="$1"; local timeout
65-
timeout=${2:-20}
66-
while process_is_running "$pid" && [ "$timeout" -gt 0 ]; do
67-
sleep 1
68-
timeout=$((timeout - 1))
63+
# Runs a command every 0.1 seconds until it succeeds or the timeout in
64+
# seconds (the first argument) has passed. A one-second interval made every
65+
# wait in the suite take up to a second longer than needed.
66+
poll() {
67+
local deadline=$((SECONDS + $1)); shift
68+
until "$@"; do
69+
[ "$SECONDS" -lt "$deadline" ] || return 1
70+
sleep 0.1
6971
done
70-
! process_is_running "$pid"
72+
}
73+
74+
process_is_gone() { ! process_is_running "$1"; }
75+
76+
wait_for_pid_gone() {
77+
poll "${2:-20}" process_is_gone "$1"
7178
}
7279

7380
exit_watcher() {
@@ -116,62 +123,25 @@ clear_locks() {
116123
rm -f lib/rescript/watch.lock lib/rescript/build.lock
117124
}
118125

119-
wait_for_file() {
120-
local file="$1"; local timeout
121-
timeout=${2:-60}
122-
while [ "$timeout" -gt 0 ]; do
123-
[ -f "$file" ] && return 0
124-
sleep 1
125-
timeout=$((timeout - 1))
126-
done
127-
return 1
128-
}
126+
file_exists() { [ -f "$1" ]; }
127+
file_is_gone() { [ ! -f "$1" ]; }
128+
file_has_pattern() { grep -q "$2" "$1" 2>/dev/null; }
129129

130-
wait_for_pattern() {
131-
local file="$1"; local pattern="$2"; local timeout
132-
timeout=${3:-60}
133-
while [ "$timeout" -gt 0 ]; do
134-
grep -q "$pattern" "$file" 2>/dev/null && return 0
135-
sleep 1
136-
timeout=$((timeout - 1))
137-
done
138-
return 1
130+
file_lacks_pattern() {
131+
[ -f "$1" ] && ! grep -q "$2" "$1" 2>/dev/null
139132
}
140133

141-
wait_for_pattern_count() {
142-
local file="$1"; local pattern="$2"; local expected="$3"; local timeout
143-
timeout=${4:-60}
144-
while [ "$timeout" -gt 0 ]; do
145-
local current_count
146-
current_count=$(grep -c "$pattern" "$file" 2>/dev/null || true)
147-
current_count=${current_count:-0}
148-
[ "$current_count" -ge "$expected" ] && return 0
149-
sleep 1
150-
timeout=$((timeout - 1))
151-
done
152-
return 1
134+
file_has_pattern_count() {
135+
local current_count
136+
current_count=$(grep -c "$2" "$1" 2>/dev/null || true)
137+
[ "${current_count:-0}" -ge "$3" ]
153138
}
154139

155-
wait_for_pattern_gone() {
156-
local file="$1"; local pattern="$2"; local timeout
157-
timeout=${3:-60}
158-
while [ "$timeout" -gt 0 ]; do
159-
if [ -f "$file" ] && ! grep -q "$pattern" "$file" 2>/dev/null; then
160-
return 0
161-
fi
162-
sleep 1
163-
timeout=$((timeout - 1))
164-
done
165-
return 1
166-
}
140+
wait_for_file() { poll "${2:-60}" file_exists "$1"; }
141+
wait_for_file_gone() { poll "${2:-60}" file_is_gone "$1"; }
142+
wait_for_pattern() { poll "${3:-60}" file_has_pattern "$1" "$2"; }
143+
wait_for_pattern_gone() { poll "${3:-60}" file_lacks_pattern "$1" "$2"; }
167144

168-
wait_for_file_gone() {
169-
local file="$1"; local timeout
170-
timeout=${2:-60}
171-
while [ "$timeout" -gt 0 ]; do
172-
[ ! -f "$file" ] && return 0
173-
sleep 1
174-
timeout=$((timeout - 1))
175-
done
176-
return 1
145+
wait_for_pattern_count() {
146+
poll "${4:-60}" file_has_pattern_count "$1" "$2" "$3"
177147
}

‎rewatch/tests/watch/02-watch-warnings-persist-atomic-save.sh‎

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,12 @@ else
1515
exit 1
1616
fi
1717

18+
completed_log_changed() {
19+
[ -f "$1" ] && ! cmp -s "$1" "$2" && grep -q "#Done(" "$1" 2>/dev/null
20+
}
21+
1822
wait_for_changed_completed_log() {
19-
local file="$1"; local baseline="$2"; local timeout
20-
timeout=${3:-60}
21-
while [ "$timeout" -gt 0 ]; do
22-
if [ -f "$file" ] && ! cmp -s "$file" "$baseline" && grep -q "#Done(" "$file" 2>/dev/null; then
23-
return 0
24-
fi
25-
sleep 1
26-
timeout=$((timeout - 1))
27-
done
28-
return 1
23+
poll "${3:-60}" completed_log_changed "$1" "$2"
2924
}
3025

3126
COMPILER_LOG="./packages/watch-warnings/lib/rescript/.compiler.log"

‎rewatch/tests/watch/04-watch-config-change.sh‎

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,12 @@ wait_for_next_build() {
4242
"$completed_builds" 30
4343
}
4444

45+
tracked_outputs_exist() {
46+
[ -z "$(git ls-files --deleted -- '*.mjs')" ]
47+
}
48+
4549
wait_for_tracked_outputs() {
46-
local timeout
47-
timeout=60
48-
while [ "$timeout" -gt 0 ]; do
49-
if [ -z "$(git ls-files --deleted -- '*.mjs')" ]; then
50-
return 0
51-
fi
52-
sleep 1
53-
timeout=$((timeout - 1))
54-
done
55-
return 1
50+
poll 60 tracked_outputs_exist
5651
}
5752

5853
# Change the suffix in rescript.json (same approach as suffix test)
@@ -118,12 +113,10 @@ else
118113
fi
119114

120115
# The rebuild after the restore also removes the outputs with the new suffix.
121-
timeout=30
122-
while [ "$timeout" -gt 0 ] && [ -n "$(find . -name '*.res.mjs' -print -quit)" ]; do
123-
sleep 1
124-
timeout=$((timeout - 1))
125-
done
126-
if [ -n "$(find . -name '*.res.mjs' -print -quit)" ]; then
116+
suffixed_outputs_gone() {
117+
[ -z "$(find . -name '*.res.mjs' -print -quit)" ]
118+
}
119+
if ! poll 30 suffixed_outputs_gone; then
127120
error "Outputs with the restored configuration's previous suffix were not removed"
128121
find . -name '*.res.mjs'
129122
git restore --worktree -- ./src/Test.res

‎rewatch/tests/watch/07-changed-interface-after-failed-implementation.sh‎

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,13 @@ REWATCH_COMPILER_DOMAINS=1 "$REWATCH_EXECUTABLE" watch > rewatch.log 2>&1 &
4444
watch_pid=$!
4545
compiler_log=lib/rescript/.compiler.log
4646

47+
compiler_log_changed() {
48+
[ -f "$compiler_log" ] && ! cmp -s "$compiler_log" "$1" &&
49+
grep -q '#Done(' "$compiler_log"
50+
}
51+
4752
wait_for_changed_compiler_log() {
48-
local baseline="$1"; local timeout
49-
timeout=60
50-
while [ "$timeout" -gt 0 ]; do
51-
if [ -f "$compiler_log" ] && ! cmp -s "$compiler_log" "$baseline" &&
52-
grep -q '#Done(' "$compiler_log"; then
53-
return 0
54-
fi
55-
sleep 1
56-
timeout=$((timeout - 1))
57-
done
58-
return 1
53+
poll 60 compiler_log_changed "$1"
5954
}
6055

6156
if ! wait_for_pattern_count rewatch.log "Finished .*compilation" 1 60; then

‎rewatch/tests/watch/08-watch-overlapping-source-removed.sh‎

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,13 @@ retained_output() {
4242
rewatch_bg watch > rewatch.log 2>&1 &
4343
success "Watcher Started"
4444

45+
retained_output_has() {
46+
output=$(retained_output)
47+
[ -n "$output" ] && grep -q "$1" "$output"
48+
}
49+
4550
wait_for_retained_value() {
46-
local value=$1
47-
local timeout=30
48-
while [ "$timeout" -gt 0 ]; do
49-
output=$(retained_output)
50-
if [ -n "$output" ] && grep -q "$value" "$output"; then
51-
return 0
52-
fi
53-
sleep 1
54-
timeout=$((timeout - 1))
55-
done
56-
return 1
51+
poll 30 retained_output_has "$1"
5752
}
5853

5954
if ! wait_for_retained_value "= 1"; then
@@ -70,12 +65,10 @@ success "Initial build completed"
7065
# watched on its own
7166
mkdir "$RETAINED/nested"
7267
echo 'let value = 1' > "$RETAINED/nested/Nested.res"
73-
timeout=30
74-
until [ -n "$(find "$RETAINED/nested" -name 'Nested.*js')" ] || [ "$timeout" -le 0 ]; do
75-
sleep 1
76-
timeout=$((timeout - 1))
77-
done
78-
if [ -n "$(find "$RETAINED/nested" -name 'Nested.*js')" ]; then
68+
nested_output_exists() {
69+
[ -n "$(find "$RETAINED/nested" -name 'Nested.*js')" ]
70+
}
71+
if poll 30 nested_output_exists; then
7972
success "A folder created inside the retained folder is watched"
8073
else
8174
error "A folder created inside the retained folder is not watched"

0 commit comments

Comments
 (0)