Files
2026-08-17 13:56:58 +01:00

285 lines
9.6 KiB
YAML

# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
name: Test Retry Action
permissions:
contents: read
on:
pull_request:
paths:
- ".github/workflows/test-retry.yml"
- "retry/action.yml"
workflow_dispatch:
jobs:
test-bash:
runs-on: ${{ matrix.os }}
defaults:
run:
shell: bash
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v7
- name: Cleanup any leftover test files
run: rm -f /tmp/retry_attempt /tmp/retry_attempt_py /tmp/retry_child
- name: Test successful command
uses: ./retry
with:
run: |
echo "This should succeed first try"
true
- name: Test Python shell on Windows
if: matrix.os == 'windows-latest'
uses: ./retry
with:
shell: python
run: print("Python shell works")
- name: Test retry with eventual success
uses: ./retry
env:
TEST_VAR: "hello"
with:
retries: 2
retry_delay_seconds: 5
run: |
echo "Running attempt with TEST_VAR=$TEST_VAR"
# Validate env var is passed correctly
if [ "$TEST_VAR" != "hello" ]; then
echo "ERROR: TEST_VAR not passed correctly, got '$TEST_VAR'"
exit 1
fi
# Fail first two times, succeed on third attempt
if [ ! -f /tmp/retry_attempt ]; then
echo "1" > /tmp/retry_attempt
echo "First attempt - failing"
exit 1
fi
count=$(cat /tmp/retry_attempt)
if [ "$count" -lt 2 ]; then
echo "$((count + 1))" > /tmp/retry_attempt
echo "Attempt $((count + 1)) - failing"
exit 1
fi
rm -f /tmp/retry_attempt
echo "Success on attempt $((count + 1))!"
- name: Test hung command retry
uses: ./retry
with:
retries: 1
retry_delay_seconds: 0
timeout_minutes: 1
run: |
if [ ! -f /tmp/retry_attempt ]; then
touch /tmp/retry_attempt
if [ "$RUNNER_OS" = "Windows" ]; then
child_file=$(cygpath -w /tmp/retry_child)
powershell.exe -NoProfile -Command "[IO.File]::WriteAllText('$child_file', [string]\$PID); Start-Sleep 120" &
elif [ "$RUNNER_OS" = "Linux" ]; then
python3 -c 'import os, signal, time; os.fork() and os._exit(0); os.setsid(); os.fork() and os._exit(0); open("/tmp/retry_child", "w").write(str(os.getpid())); os.environ.clear(); signal.signal(signal.SIGTERM, signal.SIG_IGN); time.sleep(120)'
sleep 120 &
else
python3 -c 'import os, signal, time; os.fork() and os._exit(0); os.setsid(); os.fork() and os._exit(0); open("/tmp/retry_child", "w").write(str(os.getpid())); signal.signal(signal.SIGTERM, signal.SIG_IGN); time.sleep(120)'
sleep 120 &
fi
wait $!
fi
if [ "$RUNNER_OS" = "Windows" ]; then
powershell.exe -NoProfile -Command "if (Get-Process -Id $(cat /tmp/retry_child) -ErrorAction SilentlyContinue) { exit 0 } else { exit 1 }" && child_alive=true
elif kill -0 "$(cat /tmp/retry_child)" 2>/dev/null; then
child_alive=true
fi
if [ "$child_alive" = true ]; then
echo "::error::Timed-out child is still running"
exit 1
fi
rm -f /tmp/retry_attempt /tmp/retry_child
- name: Test multi-line failure
uses: ./retry
continue-on-error: true
env:
TEST_VAR: "hello"
with:
retries: 2
retry_delay_seconds: 5
run: |
echo "Running line 1 with TEST_VAR=$TEST_VAR"
echo "Running line 2 with TEST_VAR=$TEST_VAR"
false
- name: Record start (exponential)
id: start_exp
run: echo "t=$(date +%s)" >> "$GITHUB_OUTPUT"
- name: Test exponential backoff timing
uses: ./retry
continue-on-error: true
with:
retries: 3
retry_delay_seconds: 2
backoff: exponential
jitter: false
run: |
# Waits should be 2s, 4s, 8s. Total ~14s between 4 attempts.
echo "Attempt at $(date +%s)"
false
- name: Assert exponential timing (~14s expected)
run: |
elapsed=$(( $(date +%s) - ${{ steps.start_exp.outputs.t }} ))
# Expected waits: 2 + 4 + 8 = 14s (no jitter). Allow generous upper bound for runner overhead.
if [ "$elapsed" -lt 13 ] || [ "$elapsed" -gt 40 ]; then
echo "::error::Exponential timing off: ${elapsed}s, expected ~14s (13-40s range)"
exit 1
fi
echo "OK: ${elapsed}s elapsed (expected ~14s)"
- name: Record start (fixed)
id: start_fixed
run: echo "t=$(date +%s)" >> "$GITHUB_OUTPUT"
- name: Test fixed backoff
uses: ./retry
continue-on-error: true
with:
retries: 2
retry_delay_seconds: 3
backoff: fixed
jitter: false
run: |
echo "Attempt at $(date +%s)"
false
- name: Assert fixed timing (~6s expected)
run: |
elapsed=$(( $(date +%s) - ${{ steps.start_fixed.outputs.t }} ))
# Expected waits: 3 + 3 = 6s (no jitter). Allow runner overhead.
if [ "$elapsed" -lt 6 ] || [ "$elapsed" -gt 25 ]; then
echo "::error::Fixed timing off: ${elapsed}s, expected ~6s (6-25s range)"
exit 1
fi
echo "OK: ${elapsed}s elapsed (expected ~6s)"
- name: Record start (jitter floor)
id: start_jitter
run: echo "t=$(date +%s)" >> "$GITHUB_OUTPUT"
- name: Test jitter never collapses small delay to 0
uses: ./retry
continue-on-error: true
with:
retries: 4
retry_delay_seconds: 1
backoff: fixed
jitter: true
run: |
echo "Attempt at $(date +%s)"
false
- name: Assert jitter floor (>=4s for 4 retries of >=1s each)
run: |
elapsed=$(( $(date +%s) - ${{ steps.start_jitter.outputs.t }} ))
# Without the floor, jitter would round 1s to 0s and the 4 retries would run instantly.
if [ "$elapsed" -lt 4 ]; then
echo "::error::Jitter collapsed small delays: ${elapsed}s elapsed, expected >=4s"
exit 1
fi
echo "OK: ${elapsed}s elapsed (>=4s confirmed)"
- name: Test invalid backoff value rejected
uses: ./retry
continue-on-error: true
id: invalid_backoff
with:
retries: 0
backoff: linear
run: echo "should not run"
- name: Assert invalid backoff failed
run: |
if [ "${{ steps.invalid_backoff.outcome }}" != "failure" ]; then
echo "::error::Expected invalid backoff to fail, got ${{ steps.invalid_backoff.outcome }}"
exit 1
fi
- name: Test invalid numeric input rejected
uses: ./retry
continue-on-error: true
id: invalid_retries
with:
retries: "-1"
run: echo "should not run"
- name: Assert invalid numeric input failed
run: |
if [ "${{ steps.invalid_retries.outcome }}" != "failure" ]; then
echo "::error::Expected invalid retries to fail, got ${{ steps.invalid_retries.outcome }}"
exit 1
fi
test-python:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Cleanup any leftover test files
run: rm -f /tmp/retry_attempt /tmp/retry_attempt_py
- name: Test successful Python command
uses: ./retry
with:
shell: python
run: |
print("This should succeed first try")
- name: Test Python retry with eventual success
uses: ./retry
env:
TEST_VAR: "hello"
with:
shell: python
retries: 2
retry_delay_seconds: 5
run: |
import os
from pathlib import Path
print(f"Running attempt with TEST_VAR={os.environ.get('TEST_VAR')}")
# Validate env var is passed correctly
if os.environ.get('TEST_VAR') != 'hello':
raise Exception(f"TEST_VAR not passed correctly, got '{os.environ.get('TEST_VAR')}'")
# Fail first two times, succeed on third attempt
counter_file = Path("/tmp/retry_attempt_py")
if not counter_file.exists():
counter_file.write_text("1")
print("First attempt - failing")
raise Exception("Intentional failure")
count = int(counter_file.read_text())
if count < 2:
counter_file.write_text(str(count + 1))
print(f"Attempt {count + 1} - failing")
raise Exception("Intentional failure")
counter_file.unlink()
print(f"Success on attempt {count + 1}!")
- name: Test Python multi-line failure
uses: ./retry
continue-on-error: true
env:
TEST_VAR: "hello"
with:
shell: python
retries: 2
retry_delay_seconds: 5
run: |
import os
print(f"Running line 1 with TEST_VAR={os.environ.get('TEST_VAR')}")
print(f"Running line 2 with TEST_VAR={os.environ.get('TEST_VAR')}")
assert False, "Forced assertion error"