Skip to content

Commit d5489ab

Browse files
authored
Preserve FastLanes retry hashes and failure evidence (#169)
* Preserve FastLanes failure evidence and hashes * Include FastLanes runner errors in evidence
1 parent 1d3b40d commit d5489ab

2 files changed

Lines changed: 82 additions & 18 deletions

File tree

‎.github/workflows/benchmark-fastlanes.yml‎

Lines changed: 77 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ jobs:
9797
printf 'source_commit=%s\n' "$FASTLANES_SOURCE_COMMIT"
9898
printf 'requested_numeric_rows=%s\n' '${{ inputs.numeric_rows }}'
9999
printf 'requested_timeout_seconds=%s\n' '${{ inputs.timeout_seconds }}'
100+
printf 'host_python='; python3 --version
100101
printf 'python='; nix develop --command python --version
101102
if test -x native/fastlanes/.venv/bin/python; then
102103
printf 'fastlanes_python='; native/fastlanes/.venv/bin/python --version
@@ -116,20 +117,27 @@ jobs:
116117
CASE_TIMEOUT_SECONDS: ${{ inputs.timeout_seconds }}
117118
run: |
118119
set -euo pipefail
120+
status=0
119121
case "$CASE_TIMEOUT_SECONDS" in
120122
''|*[!0-9]*)
121-
echo "timeout_seconds must be an integer" >&2
122-
exit 2
123+
printf '%s\n' "timeout_seconds must be an integer" \
124+
> "$RUN_DIR/measure-error.txt"
125+
status=2
126+
;;
127+
*)
128+
if test "$CASE_TIMEOUT_SECONDS" -lt 1 || \
129+
test "$CASE_TIMEOUT_SECONDS" -gt 120; then
130+
printf '%s\n' "timeout_seconds must be between 1 and 120" \
131+
> "$RUN_DIR/measure-error.txt"
132+
status=2
133+
fi
123134
;;
124135
esac
125-
if test "$CASE_TIMEOUT_SECONDS" -lt 1 || \
126-
test "$CASE_TIMEOUT_SECONDS" -gt 120; then
127-
echo "timeout_seconds must be between 1 and 120" >&2
128-
exit 2
129-
fi
130-
set +e
131-
PYTHONPATH="$GITHUB_WORKSPACE/src" \
132-
native/fastlanes/.venv/bin/python - "$RUN_DIR/results.json" <<'PY'
136+
if test "$status" -eq 0; then
137+
set +e
138+
PYTHONPATH="$GITHUB_WORKSPACE/src" \
139+
native/fastlanes/.venv/bin/python - "$RUN_DIR/results.json" \
140+
> "$RUN_DIR/measure.log" 2>&1 <<'PY'
133141
import json
134142
import os
135143
import sys
@@ -153,8 +161,9 @@ jobs:
153161
}
154162
output.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n")
155163
PY
156-
status=$?
157-
set -e
164+
status=$?
165+
set -e
166+
fi
158167
printf '%s\n' "$status" > "$RUN_DIR/measure-status.txt"
159168
exit "$status"
160169
@@ -165,16 +174,25 @@ jobs:
165174
if test -s "$RUN_DIR/results.json"; then
166175
exit 0
167176
fi
168-
nix develop --command python - "$RUN_DIR/results.json" \
177+
python3 - "$RUN_DIR/results.json" \
169178
'${{ steps.source.outcome }}' '${{ steps.build.outcome }}' \
170-
'${{ steps.measure.outcome }}' <<'PY'
179+
'${{ steps.measure.outcome }}' "$RUN_DIR/measure-error.txt" \
180+
"$RUN_DIR/measure.log" <<'PY'
171181
import json
172182
import sys
173183
from pathlib import Path
174184
175185
output = Path(sys.argv[1])
176-
source, build, measure = sys.argv[2:]
186+
source, build, measure = sys.argv[2:5]
187+
error_path = Path(sys.argv[5])
188+
log_path = Path(sys.argv[6])
177189
available = source == "success" and build == "success"
190+
if error_path.is_file():
191+
error = error_path.read_text().strip()
192+
elif log_path.is_file():
193+
error = log_path.read_text(errors="replace").strip()[-1000:]
194+
else:
195+
error = "none"
178196
output.write_text(
179197
json.dumps(
180198
{
@@ -185,7 +203,7 @@ jobs:
185203
"source_commit": "f0edc1020a538f1f8098640fce8347c9ac247a0d",
186204
"status": "UNSUPPORTED" if not available else "FAILED",
187205
"failure_reason": (
188-
f"source={source}; build={build}; measure={measure}"
206+
f"source={source}; build={build}; measure={measure}; error={error}"
189207
),
190208
},
191209
indent=2,
@@ -195,11 +213,52 @@ jobs:
195213
)
196214
PY
197215
216+
- name: Bind case inputs and artifacts to hashes
217+
if: always()
218+
run: |
219+
set -euo pipefail
220+
if test ! -s "$RUN_DIR/results.json" || \
221+
test ! -d "$RUN_DIR/claims/fastlanes-official"; then
222+
exit 0
223+
fi
224+
python3 - "$RUN_DIR/results.json" \
225+
"$RUN_DIR/claims/fastlanes-official" <<'PY'
226+
import hashlib
227+
import json
228+
import sys
229+
from pathlib import Path
230+
231+
results_path = Path(sys.argv[1])
232+
root = Path(sys.argv[2])
233+
234+
def digest(path: Path) -> str | None:
235+
if not path.is_file():
236+
return None
237+
value = hashlib.sha256()
238+
with path.open("rb") as handle:
239+
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
240+
value.update(chunk)
241+
return value.hexdigest()
242+
243+
case_hashes = {}
244+
for case_dir in sorted(path for path in root.iterdir() if path.is_dir()):
245+
case_hashes[case_dir.name] = {
246+
"input/data.csv": digest(case_dir / "input/data.csv"),
247+
"input/schema.json": digest(case_dir / "input/schema.json"),
248+
"data.fls": digest(case_dir / "data.fls"),
249+
"decoded.csv": digest(case_dir / "decoded.csv"),
250+
}
251+
payload = json.loads(results_path.read_text())
252+
payload["case_hash_algorithm"] = "sha256"
253+
payload["case_hashes"] = case_hashes
254+
results_path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n")
255+
PY
256+
198257
- name: Write concise evidence report
199258
if: always()
200259
run: |
201260
set -euo pipefail
202-
nix develop --command python - "$RUN_DIR/results.json" "$RUN_DIR/report.md" <<'PY'
261+
python3 - "$RUN_DIR/results.json" "$RUN_DIR/report.md" <<'PY'
203262
import json
204263
import sys
205264
from pathlib import Path
@@ -249,7 +308,7 @@ jobs:
249308
test '${{ steps.checkout.outcome }}' = success
250309
test '${{ steps.source.outcome }}' = success
251310
test '${{ steps.build.outcome }}' = success
252-
nix develop --command python - "$RUN_DIR/results.json" <<'PY'
311+
python3 - "$RUN_DIR/results.json" <<'PY'
253312
import json
254313
import sys
255314
from pathlib import Path

‎tests/test_fastlanes_workflow.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,12 @@ def test_fastlanes_retry_workflow_preserves_build_and_measurement_logs() -> None
2929

3030
assert '"$RUN_DIR/build.log"' in workflow
3131
assert '"$RUN_DIR/build-status.txt"' in workflow
32+
assert '"$RUN_DIR/measure.log"' in workflow
33+
assert '"$RUN_DIR/measure-error.txt"' in workflow
3234
assert '"$RUN_DIR/measure-status.txt"' in workflow
35+
assert '"$RUN_DIR/measure.log" <<\'PY\'' in workflow
3336
assert "steps.measure.outcome" in workflow
3437
assert '"status": "UNSUPPORTED" if not available else "FAILED"' in workflow
38+
assert "case_hash_algorithm" in workflow
39+
assert "input/data.csv" in workflow
3540
assert "if: always()" in workflow

0 commit comments

Comments
 (0)