diff --git a/.github/workflows/smoke-tests.yaml b/.github/workflows/smoke-tests.yaml index 181c8f7..ecfce17 100644 --- a/.github/workflows/smoke-tests.yaml +++ b/.github/workflows/smoke-tests.yaml @@ -183,3 +183,126 @@ jobs: split-total: 5 split-index: 1 - run: echo "The output test suite is ${{ steps.split-tests.outputs.test-suite }}" + + smoke-test-working-directory: + runs-on: ubuntu-latest + name: Smoke Test Working Directory + steps: + - uses: actions/checkout@v7 + - uses: actions/checkout@v7 + with: + repository: eliotsykes/rspec-rails-examples + path: examples/ + - uses: ./ + id: relative + name: Relative working-directory + with: + working-directory: examples + glob: spec/**/*_spec.rb + split-total: 1 + split-index: 0 + - uses: ./ + id: absolute + name: Absolute working-directory + with: + working-directory: ${{ github.workspace }}/examples + glob: spec/**/*_spec.rb + split-total: 1 + split-index: 0 + - name: Assert output is relative to working-directory and covers the glob + env: + RELATIVE: ${{ steps.relative.outputs.test-suite }} + ABSOLUTE: ${{ steps.absolute.outputs.test-suite }} + run: | + set -euo pipefail + + echo "$RELATIVE" | tr ' ' '\n' | sed '/^$/d' | sort -u > /tmp/relative.txt + echo "$ABSOLUTE" | tr ' ' '\n' | sed '/^$/d' | sort -u > /tmp/absolute.txt + (cd examples && find spec -name '*_spec.rb' | sort -u) > /tmp/expected.txt + + if [ ! -s /tmp/expected.txt ]; then + echo "::error::Fixture has no spec files, the test is not testing anything" + exit 1 + fi + + if ! diff -u /tmp/expected.txt /tmp/relative.txt; then + echo "::error::Relative working-directory output does not match the files under examples/spec" + exit 1 + fi + + if ! diff -u /tmp/relative.txt /tmp/absolute.txt; then + echo "::error::Absolute and relative working-directory outputs differ" + exit 1 + fi + + if grep -q '^examples/' /tmp/relative.txt; then + echo "::error::Output paths should be relative to working-directory, not the workspace" + exit 1 + fi + + echo "OK: $(wc -l < /tmp/expected.txt) files, paths relative to working-directory" + + smoke-test-expected-failures: + runs-on: ubuntu-latest + name: Smoke Test Expected Failures + steps: + - uses: actions/checkout@v7 + - uses: actions/checkout@v7 + with: + repository: eliotsykes/rspec-rails-examples + path: examples/ + - uses: ./ + id: missing-working-directory + name: Nonexistent working-directory + continue-on-error: true + with: + working-directory: examples/does-not-exist + glob: spec/**/*_spec.rb + split-total: 2 + split-index: 0 + - uses: ./ + id: index-out-of-range + name: split-index equal to split-total + continue-on-error: true + with: + glob: examples/spec/**/*_spec.rb + split-total: 2 + split-index: 2 + - uses: ./ + id: zero-total + name: split-total of zero + continue-on-error: true + with: + glob: examples/spec/**/*_spec.rb + split-total: 0 + split-index: 0 + - uses: ./ + id: control + name: Control, valid inputs must still pass + continue-on-error: true + with: + glob: examples/spec/**/*_spec.rb + split-total: 2 + split-index: 1 + - name: Assert each invalid input failed and the control passed + env: + MISSING_WORKING_DIRECTORY: ${{ steps.missing-working-directory.outcome }} + INDEX_OUT_OF_RANGE: ${{ steps.index-out-of-range.outcome }} + ZERO_TOTAL: ${{ steps.zero-total.outcome }} + CONTROL: ${{ steps.control.outcome }} + run: | + set -euo pipefail + status=0 + expect() { + if [ "$2" != "$3" ]; then + echo "::error::$1: expected outcome '$3' but got '$2'" + status=1 + else + echo "OK: $1 -> $2" + fi + } + expect "nonexistent working-directory" "$MISSING_WORKING_DIRECTORY" failure + expect "split-index equal to split-total" "$INDEX_OUT_OF_RANGE" failure + expect "split-total of zero" "$ZERO_TOTAL" failure + expect "valid inputs (control)" "$CONTROL" success + exit "$status" diff --git a/README.md b/README.md index 4f1b379..19cae61 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ Timings must be per test **file**. If your runner writes one report per worker, | `exclude-glob` | Glob pattern to exclude test files | `string` | | `""` | | `junit-path` | Path to a JUnit XML report to use for test timings | `string` | | `""` | | `line-count` | Use line count to estimate test duration | `bool` | | `false` | +| `working-directory` | Directory to run in, relative to `$GITHUB_WORKSPACE` (or absolute). `glob`, `exclude-glob` and `junit-path` are resolved relative to it, and `test-suite` paths are relative to it. Useful for monorepos | `string` | | `""` (the workspace) | ### Outputs diff --git a/action.yml b/action.yml index 8513071..d9192f5 100644 --- a/action.yml +++ b/action.yml @@ -22,6 +22,10 @@ inputs: line-count: description: Use line count to estimate test times (boolean) required: false + working-directory: + description: Directory to run in, relative to GITHUB_WORKSPACE (or absolute). The glob, exclude-glob and junit-path inputs are resolved relative to it, and the output paths are relative to it. Defaults to GITHUB_WORKSPACE. + required: false + default: "" outputs: test-suite: description: "A subset of tests, based on the the split index and split type" @@ -46,6 +50,9 @@ runs: SPLIT_TESTS_SHA256: 6988628a956f065c9eced4bb77bcc0b5c111717c1d4748b701c03afabb8349dd run: | set -euo pipefail + # Install into GITHUB_WORKSPACE so the binary can be found by absolute + # path regardless of the working-directory input. + cd "$GITHUB_WORKSPACE" curl -fL "https://github.com/leonid-shevtsov/split_tests/releases/download/${SPLIT_TESTS_VERSION}/split_tests.linux.gz" -o split_tests.gz if ! echo "${SPLIT_TESTS_SHA256} split_tests.gz" | sha256sum -c -; then echo "::error::split_tests ${SPLIT_TESTS_VERSION} checksum mismatch. Actual sha256: $(sha256sum split_tests.gz | cut -d' ' -f1)" >&2 @@ -63,9 +70,12 @@ runs: EXCLUDE_GLOB_INPUT: ${{ inputs.exclude-glob }} JUNIT_PATH: ${{ inputs.junit-path }} LINE_COUNT: ${{ inputs.line-count }} + WORKING_DIRECTORY: ${{ inputs.working-directory }} run: | set -euo pipefail + SPLIT_TESTS_BIN="$GITHUB_WORKSPACE/split_tests" + # setup split type if [ -n "$JUNIT_PATH" ]; then echo "junit-path set. Splitting based on JUnit timings" @@ -83,7 +93,17 @@ runs: EXCLUDE_GLOB=(-exclude-glob="$EXCLUDE_GLOB_INPUT") fi - TESTS=$(./split_tests "${SPLIT_BY[@]}" -split-index="$SPLIT_INDEX" -split-total="$SPLIT_TOTAL" -glob="$GLOB" "${EXCLUDE_GLOB[@]}") + # Resolve relative paths from the workspace, then run from there. + cd "$GITHUB_WORKSPACE" + if [ -n "$WORKING_DIRECTORY" ]; then + if [ ! -d "$WORKING_DIRECTORY" ]; then + echo "::error::working-directory '$WORKING_DIRECTORY' does not exist" + exit 1 + fi + cd "$WORKING_DIRECTORY" + fi + + TESTS=$("$SPLIT_TESTS_BIN" "${SPLIT_BY[@]}" -split-index="$SPLIT_INDEX" -split-total="$SPLIT_TOTAL" -glob="$GLOB" "${EXCLUDE_GLOB[@]}") echo "$TESTS"