Pants integrates with these tools to empower you to follow best practices with your Shell scripts:
[Shellcheck](🔗): lint for common Shell mistakes.
[shfmt](🔗): autoformat Shell code so that you can instead focus on the logic.
[shUnit2](🔗): write light-weight unit tests for your Shell code.
Pants installs these tools deterministically and integrates them into the workflows you already use: `./pants fmt
`, `./pants lint
`, and `./pants test
`.
## Initial setup: add `shell_sources
` targets
Pants uses [`shell_source
`](🔗) and [`shunit2_test
`](🔗) [targets](🔗) to know which Shell files you want to operate on and to set any metadata.
To reduce boilerplate, the [`shell_sources
`](🔗) target generates a `shell_source
` target for each file in its `sources
` field, and [`shunit2_tests
`](🔗) generates a `shunit2_test
` target for each file in its `sources
` field.
First, activate the Shell backend in your `pants.toml
`:
Then, run [`./pants tailor
`](🔗) to generate BUILD files:
You can also manually add targets, which is necessary if you have any scripts that don't end in `.sh
`:
Shell dependency inference
Pants will [infer dependencies](🔗) by looking for imports like `
source script.sh
` and `. script.sh
`. You can check that the correct dependencies are inferred by running `./pants dependencies path/to/script.sh
` and `./pants dependencies --transitive path/to/script.sh
`.Normally, Pants will not understand dynamic sources, e.g. using variable expansion. However, Pants uses Shellcheck for parsing, so you can use Shellcheck's syntax to give a hint to Pants:
Alternatively, you can explicitly add `
dependencies
` in the relevant BUILD file.
## shfmt autoformatter
To activate, add this to your `pants.toml
`:
Make sure that you also have set up `shell_source
`/`shell_sources
` or `shunit2_test
`/`shunit2_tests
` targets so that Pants knows to operate on the relevant files.
Now you can run `./pants fmt
` and `./pants lint
`:
Use `./pants fmt lint dir:
` to run on all files in the directory, and `./pants fmt lint dir::
` to run on all files in the directory and subdirectories.
Pants will automatically include any relevant `.editorconfig
` files in the run. You can also pass command line arguments with `--shfmt-args='-ci -sr'
` or permanently set them in `pants.toml
`:
Temporarily disable shfmt with `--shfmt-skip
`:
Only run shfmt with `--lint-only
` and `--fmt-only
`:
Benefit of Pants: shfmt runs in parallel with Python, Java, Scala, and Go formatters
Normally, Pants runs formatters sequentially so that it can pipe the results of one formatter into the next. However, Pants will run shfmt in parallel to formatters for other languages, [like Python](🔗), because shfmt does not operate on those languages.
You can see this concurrency through Pants's dynamic UI.
## Shellcheck linter
To activate, add this to your `pants.toml
`:
Make sure that you also have set up `shell_source
` / `shell_sources
` or `shunit2_test
` / `shunit_tests
` targets so that Pants knows to operate on the relevant files.
Now you can run `./pants lint
`:
Use `./pants fmt lint dir:
` to run on all files in the directory, and `./pants fmt lint dir::
` to run on all files in the directory and subdirectories.
Pants will automatically include any relevant `.shellcheckrc
` and `shellcheckrc
` files in the run. You can also pass command line arguments with `--shellcheck-args='-x -W 3'
` or permanently set them in `pants.toml
`:
Temporarily disable Shellcheck with `--shellcheck-skip
`:
Only run Shellcheck with `--lint-only
`:
Benefit of Pants: Shellcheck runs in parallel with other linters
Pants will attempt to run all activated linters and formatters at the same time for improved performance, including [Python](🔗), Go, Java, and Scala linters. You can see this through Pants's dynamic UI.
## shUnit2 test runner
[shUnit2](🔗) allows you to write lightweight unit tests for your Shell code.
To use shunit2 with Pants:
Create a test file like `
tests.sh
`, `test_foo.sh
`, or `foo_test.sh
`.Refer to https://github.com/kward/shunit2/ for how to write shUnit2 tests.
Create a `
shunit2_test
` or `shunit2_tests
` target in the directory's BUILD file.You can run [`
./pants tailor
`](🔗) to automate this step.
Specify which shell to run your tests with, either by setting a shebang directly in the test file or by setting the field `
shell
` on the `shunit2_test
` / `shunit2_tests
` target.See [here](🔗) for all supported shells.
You can then run your tests like this:
Pants will download the `./shunit2
` script and will add `source ./shunit2
` with the correct relpath for you.
You can import your production code by using `source
`. Make sure the code belongs to a `shell_source
` or `shell_sources
` target. Pants's [dependency inference](🔗) will add the relevant dependencies, which you can confirm by running `./pants dependencies scripts/tests.sh
`. You can also manually add to the `dependencies
` field of your `shunit2_tests
` target.
Running your tests with multiple shells
Pants allows you to run the same tests against multiple shells, e.g. Bash and Zsh, to ensure your code works with each shell.
To test multiple shells, create a distinct `
shunit2_test
` or `shunit2_tests
` target for each shell, like this:Then, use `
./pants test
`:Pants 2.11 will add first-class support for this parametrization.
### Controlling output
By default, Pants only shows output for failed tests. You can change this by setting `--test-output
` to one of `all
`, `failed
`, or `never
`, e.g. `./pants test --output=all ::
`.
You can permanently set the output format in your `pants.toml
` like this:
### Force reruns with `--force
`
To force your tests to run again, rather than reading from the cache, run `./pants test --force path/to/test.sh
`.
### Setting environment variables
Test runs are _hermetic_, meaning that they are stripped of the parent `./pants
` process's environment variables. This is important for reproducibility, and it also increases cache hits.
To add any arbitrary environment variable back to the process, use the option `extra_env_vars
` in the `[test]
` options scope. You can hardcode a value for the option, or leave off a value to "allowlist" it and read from the parent `./pants
` process's environment.
Use `[bash-setup].executable_search_paths
` to change the `$PATH
` env var used during test runs. You can use the special string `"<PATH>"
` to read the value from the parent `./pants
` process's environment.
### Timeouts
Pants can cancel tests that take too long, which is useful to prevent tests from hanging indefinitely.
To add a timeout, set the `timeout
` field to an integer value of seconds, like this:
When you set `timeout
` on the `shunit2_tests
` target generator, the same timeout will apply to every generated `shunit2_test
` target. Instead, you can use the `overrides
` field:
Unlike [with Python](🔗), you cannot yet set a default or maximum timeout value, nor temporarily disable all timeouts. Please [let us know](🔗) if you would like this feature.
### Testing your packaging pipeline
You can include the result of `./pants package
` in your test through the `runtime_package_dependencies field
`. Pants will run the equivalent of `./pants package
` beforehand and copy the built artifact into the test's chroot, allowing you to test things like that the artifact has the correct files present and that it's executable.
This allows you to test your packaging pipeline by simply running `./pants test ::
`, without needing custom integration test scripts.
To depend on a built package, use the `runtime_package_dependencies
` field on the `shunit2_test
` / `shunit2_tests
` targets, which is a list of addresses to targets that can be built with `./pants package
`, such as [`pex_binary
`](🔗), [`python_awslambda
`](🔗), and [`archive
`](🔗) targets. Pants will build the package before running your test, and insert the file into the test's chroot. It will use the same name it would normally use with `./pants package
`, except without the `dist/
` prefix.
For example: