Skip to content

Commit

Permalink
action: go conditional tests (#709)
Browse files Browse the repository at this point in the history
* poc: go test caching action

* chore: unit tests

* feat: seperate steps for action

* feat: simpler concurrency model

* feat: test coverage

* chore: cleanup

* more testing, cleanup

* fix: better default concurrency parameters

* rename: go-conditional-tests

* fix: final cleanup, and testing

* chore: add changeset

* fix: run tests with default 10m timeout
  • Loading branch information
erikburt authored Dec 6, 2024
1 parent de57536 commit 9a266f6
Show file tree
Hide file tree
Showing 29 changed files with 140,836 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/tasty-frogs-provide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"go-conditional-tests": minor
---

initial creation of go-conditional-tests
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ yarn.lock
# Don't LLM format prompt files
/actions/llm-pr-writer/pr-writer-prompt.md
/actions/llm-action-error-reporter/log-analyze-prompt.md

# GQL generated type files
**/src/generated/graphql.ts
13 changes: 13 additions & 0 deletions apps/go-conditional-tests/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*", "src/generated/*.ts", "node_modules"],
"overrides": [
{
"files": ["./package.json"],
"parser": "jsonc-eslint-parser",
"rules": {
"@nrwl/nx/nx-plugin-checks": "error"
}
}
]
}
1 change: 1 addition & 0 deletions apps/go-conditional-tests/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# go-conditional-tests
182 changes: 182 additions & 0 deletions apps/go-conditional-tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
# go-conditional-tests

An action that maintains an index of golang unit test binaries, and can
conditionally execute those binaries when changed.

```mermaid
sequenceDiagram
participant Workflow
participant Action
participant Pipeline
participant Github
box go-conditional-tests
participant Action
participant Pipeline
end
Workflow->>Github: Checkout repository
Workflow->>Workflow: Setup...
Workflow->>Action: 'Build'
activate Action
Action-->>Pipeline: Start Build Process
Pipeline->>Pipeline: Filter/List Packages
Pipeline->>Pipeline: Build Package Test Binaries
Pipeline-->>Action: Done Build
Action-->>Github: Upload Build Logs
Action->>Workflow: Done Build
deactivate Action
Workflow->>Action: 'Run'
activate Action
Action-->>Pipeline: Start Run Process
Pipeline->>Github: Get Test Hash Index
Github->>Pipeline: Returned
Pipeline->>Pipeline: Hash Test Binaries
Pipeline->>Pipeline: Compare Hashes
Pipeline->>Pipeline: Run Changed Tests
Pipeline-->>Action: Done Execution
Action-->>Github: Upload Run Logs
Action-->>Github: Upload Coverage (if enabled)
Action->>Workflow: Done Execution
deactivate Action
Workflow->>Action: 'Update'
activate Action
Action-->>Pipeline: Start Update Process
Pipeline->>Pipeline: Check Update Criteria
opt Criteria Met
Pipeline->>Github: Update Hash Index
Github->>Pipeline: Updated
end
Pipeline-->>Action: Done Update
Action->>Workflow: Done Execution
deactivate Action
```

## Usage

Example workflow job:

```
run-unit-tests:
name: Unit Tests
needs: filter
runs-on: ubuntu-latest
permissions:
id-token: write
contents: write
steps:
- name: Checkout the repo
uses: actions/checkout@v4.2.1
- name: Setup
...
- name: Build Tests
uses: smartcontractkit/.github/apps/go-conditional-tests@<version>
timeout-minutes: 10
with:
pipeline-step: "build"
test-suite: "unit"
module-directory: "./module"
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Run Tests
uses: smartcontractkit/.github/apps/go-conditional-tests@<version>
timeout-minutes: 15
env:
CL_DATABASE_URL: ${{ env.DB_URL }}
with:
pipeline-step: "run"
test-suite: "unit"
module-directory: "./module"
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Update Test Index
uses: smartcontractkit/.github/apps/go-conditional-tests@<version>
timeout-minutes: 2
with:
pipeline-step: "update"
test-suite: "unit"
github-token: ${{ secrets.GITHUB_TOKEN }}
```

### Setup

This action requires an orphaned branch to store the test hash indexes in.

1. `git switch --orphan test-hashes`
2. `printf '*\n!.gitignore\n!README.md\n!*.json' > .gitignore`
3. `git add .gitignore`
4. `git commit -m "test-hashes: initial commit"`
5. `git push --set-upstream origin test-hashes`

## Action

### Inputs

#### Behavioral Inputs

- `pipeline-step`, `build / run / update / e2e`
- Describes which step of the pipeline to perform. This allows you to separate
the action into multiple steps of a job.
- `build` - finds all the packages and builds the test binary for each
- `run` - given the output from `build`, will hash the binaries, compare those
to the hash index, then run those that have changed.
- `update` - given the output from `run`, it will update the hash index with
the new indexes, if on the repo's main branch.
- `e2e` - performs all of the above as a single step.

### General Inputs

- `test-suite`
- The name of the test suite, used to scope artifacts and the test indexes
- `module-directory`, path (`./`)
- The path to the root module for the tests. Similar to setting
`working-directory`.
- `hashes-branch`, string (`test-hashes`)
- The (ideally orphaned) git branch to store the test hash index json files
on. Used by `run` and `update`.
- `collect-coverage`, true / **false**
- Enables the `build`, and `run` flags for collecting coverage. Then uploads
the coverage files. This will also enable `run-all-tests` and should skip
the update step. This is because the update step should not use hashes from
binaries built with the coverage parameters.

#### `build` inputs

- `build-concurrency`, number (`8`)
- The amount of concurrent builds when building the test binaries. Recommended
to be the number of available CPU cores.
- `build-flags`: string (`""`)
- CLI build flags to pass to the `go test -c ...` command when building the
test binaries

#### `run` inputs

- `run-all-tests`: true / **false**
- Runs every test binary built, ignoring the normal behaviour of conditional
execution based on the different hashes.
- `run-concurrency`, number (`8`)
- The amount of concurrent running tests.

### Other Inputs

- `github-token`
- Used by `run` and `update` steps to authenticate to github to fetch/update
the test hash index.

### TODO

- Support for config files so not everything has to be passed directly to the
action?
- Ignore certain directories?
- Scrub logs?
- Update the hash index of only successful tests?
61 changes: 61 additions & 0 deletions apps/go-conditional-tests/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: go-conditional-tests
description: "Filter, build, run affected Go tests"
inputs:
pipeline-step:
description: |
The step of the pipeline to run. Can be one of:
- build: Build then hash all test binaries
- run: Compare test hashes and run affected tests ('build' must have been run first)
- update: Update the test index file ('run' must have been run first)
- e2e: Run all of the above as a single step
required: true

test-suite:
description: |
The name of the test suite, used to scope the test binary hash index.
Has no effect otherwise.
required: true

module-directory:
description: "Directory containing the go module"
required: true
default: "."

run-all-tests:
description: |
Ignore the difference between the current test indexes, and run all tests.
default: "false"

collect-coverage:
description: |
Collect coverage information for the tests. Required during both build and run steps.
default: "false"

build-flags:
description: "Flags to pass when running go test -c"
default: ""

hashes-branch:
description: |
Branch which contains the test-hashes.json file used for
comparing test hashes
default: "test-hashes"

build-concurrency:
description: |
Number of concurrent builds to run. Defaults to the number of CPU cores.
required: false

run-concurrency:
description: |
Number of concurrent test packages runs to run. Defaults to the number of CPU cores.
required: false

github-token:
description:
"Github token with read permissions on all smartcontractkit repos"
default: ${{ github.token }}

runs:
using: "node20"
main: "dist/index.js"
Loading

0 comments on commit 9a266f6

Please sign in to comment.