{"id":24416745,"url":"https://github.com/sha1n/zsh-scriptest","last_synced_at":"2026-05-15T01:38:15.500Z","repository":{"id":46764513,"uuid":"410833974","full_name":"sha1n/zsh-scriptest","owner":"sha1n","description":"Utilities for Zsh script testing","archived":false,"fork":false,"pushed_at":"2021-10-22T20:37:33.000Z","size":9,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-06T03:08:48.886Z","etag":null,"topics":["testing","testing-library","testing-tools","zsh"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sha1n.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-09-27T10:09:45.000Z","updated_at":"2021-10-22T20:37:35.000Z","dependencies_parsed_at":"2022-09-14T22:54:48.790Z","dependency_job_id":null,"html_url":"https://github.com/sha1n/zsh-scriptest","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/sha1n/zsh-scriptest","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sha1n%2Fzsh-scriptest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sha1n%2Fzsh-scriptest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sha1n%2Fzsh-scriptest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sha1n%2Fzsh-scriptest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sha1n","download_url":"https://codeload.github.com/sha1n/zsh-scriptest/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sha1n%2Fzsh-scriptest/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33050481,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-13T13:14:54.681Z","status":"online","status_checked_at":"2026-05-14T02:00:06.663Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["testing","testing-library","testing-tools","zsh"],"created_at":"2025-01-20T08:14:12.670Z","updated_at":"2026-05-15T01:38:15.489Z","avatar_url":"https://github.com/sha1n.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![CI](https://github.com/sha1n/zsh-scriptest/actions/workflows/ci.yml/badge.svg)](https://github.com/sha1n/zsh-scriptest/actions/workflows/ci.yml)\n\n# Zsh ScripTest\n\nA lightweight, generic test runner framework for zsh scripts. ScripTest provides test discovery, execution isolation, assertion matchers, and formatted output — everything you need to test your shell scripts with minimal setup.\n\n## Quick Start\n\nCreate a test file named `my_feature.test.sh`:\n\n```zsh\n#!/usr/bin/env zsh\n\nsource \"$__ZSH_SCRIPTEST_HOME/matchers.sh\"\nsource \"$__ZSH_SCRIPTEST_HOME/test_util.sh\"\n\nfunction test_greeting() {\n  test_case_title\n  local result=\"hello world\"\n  assert_contains \"$result\" \"hello\"\n  assert_not_empty \"$result\"\n}\n\nrun_test test_greeting\nfinish_tests\n```\n\nRun it:\n\n```sh\n./run_tests.sh .\n```\n\n## Installation\n\nAdd as a git submodule in your project:\n\n```sh\ngit submodule add https://github.com/sha1n/zsh-scriptest.git tests/zsh-scriptest\n```\n\nCreate a thin wrapper script (e.g. `tests/run_tests.sh`):\n\n```zsh\n#!/usr/bin/env zsh\nexport MY_TESTS_HOME=\"${${(%):-%x}:a:h}\"\n\"$MY_TESTS_HOME/zsh-scriptest/run_tests.sh\" \"$MY_TESTS_HOME\"\n```\n\nAdd a Makefile target:\n\n```makefile\ntest:\n\t./tests/run_tests.sh\n```\n\n## Writing Tests\n\n### File Naming\n\nTest files must match the pattern `*.test.sh` and be executable. The runner discovers all matching files in the given directory (one level deep).\n\n### Test Structure\n\nEach test file should:\n1. Source `matchers.sh` and `test_util.sh`\n2. Define test functions\n3. Run them with `run_test` and call `finish_tests` at the end\n\n```zsh\n#!/usr/bin/env zsh\n\nsource \"$__ZSH_SCRIPTEST_HOME/matchers.sh\"\nsource \"$__ZSH_SCRIPTEST_HOME/test_util.sh\"\n\nfunction test_something() {\n  test_case_title\n  # ... test logic and assertions ...\n}\n\nfunction test_another_thing() {\n  test_case_title\n  # ... test logic and assertions ...\n}\n\nrun_test test_something\nrun_test test_another_thing\nfinish_tests\n```\n\n`run_test` executes each test function in a subshell, so a failure in one test does not prevent other tests from running. It emits TAP-compliant `ok`/`not ok` lines, and on failure includes the test output as diagnostic lines.\n\n### Setup and Teardown\n\nUse `test_setup_title` and `test_teardown_title` to mark setup and teardown phases:\n\n```zsh\nfunction setup() {\n  test_setup_title\n  export TEST_DIR=\"$(mktemp -d)\"\n  # ... create test fixtures ...\n}\n\nfunction teardown() {\n  test_teardown_title\n  rm -rf \"$TEST_DIR\"\n}\n\nfunction test_my_feature() {\n  test_case_title\n  assert_dir_exists \"$TEST_DIR\"\n}\n\nsetup\nrun_test test_my_feature\nfinish_tests\nteardown\n```\n\n### Test Isolation\n\nThe test runner automatically:\n- Runs each test **file** in a subshell (failures don't affect other files)\n- Sets `$HOME` to a temporary directory (your real home is never touched)\n- Restores `$HOME` after all tests complete\n\nFor isolation **within** a test file, use subshells or define a `before_each` function:\n\n```zsh\nfunction before_each() {\n  test_case_title\n  # reset state between tests\n}\n```\n\n## Matchers Reference\n\nAll matchers exit with code `1` on failure and print a diagnostic message.\n\n### Equality\n\n| Matcher | Description | Example |\n|---------|-------------|---------|\n| `assert_equal` | Values must be identical | `assert_equal \"$actual\" \"expected\"` |\n| `assert_not_equal` | Values must differ | `assert_not_equal \"$actual\" \"unexpected\"` |\n\n```zsh\nassert_equal \"$result\" \"42\"\nassert_not_equal \"$status\" \"error\"\n```\n\n### String Content\n\n| Matcher | Description | Example |\n|---------|-------------|---------|\n| `assert_empty` | Value must be empty | `assert_empty \"$var\"` |\n| `assert_not_empty` | Value must not be empty | `assert_not_empty \"$var\"` |\n| `assert_contains` | Haystack must contain needle | `assert_contains \"$output\" \"success\"` |\n| `assert_not_contains` | Haystack must not contain needle | `assert_not_contains \"$output\" \"error\"` |\n| `assert_match` | Value must match regex pattern | `assert_match \"$val\" \"^[0-9]+$\"` |\n\n```zsh\nassert_empty \"$error_msg\"\nassert_not_empty \"$PATH\"\nassert_contains \"$output\" \"Done\"\nassert_not_contains \"$log\" \"FATAL\"\nassert_match \"$version\" \"^[0-9]+\\.[0-9]+\\.[0-9]+$\"\n```\n\n### File System\n\n| Matcher | Description | Example |\n|---------|-------------|---------|\n| `assert_file_exists` | File must exist | `assert_file_exists \"$config_path\"` |\n| `assert_file_not_exists` | File must not exist | `assert_file_not_exists \"$tmp_file\"` |\n| `assert_dir_exists` | Directory must exist | `assert_dir_exists \"$output_dir\"` |\n| `assert_dir_not_exists` | Directory must not exist | `assert_dir_not_exists \"$old_dir\"` |\n\n```zsh\nassert_file_exists \"$HOME/.config/myapp/config\"\nassert_file_not_exists \"$HOME/.config/myapp/config.bak\"\nassert_dir_exists \"$HOME/.local/bin\"\nassert_dir_not_exists \"/tmp/stale_build\"\n```\n\n### Exit Code\n\n| Matcher | Description | Example |\n|---------|-------------|---------|\n| `assert_exit_code` | Command must exit with expected code | `assert_exit_code \"0\" \"my_command --flag\"` |\n\n```zsh\nassert_exit_code \"0\" \"my_script --validate input.txt\"\nassert_exit_code \"1\" \"my_script --validate bad_input.txt\"\n```\n\n### Testing Failure Paths\n\nTo test that a matcher correctly fails, run the assertion in a subshell and check the exit code:\n\n```zsh\n(assert_equal \"a\" \"b\" 2\u003e\u00261)\n[[ \"$?\" == \"1\" ]] || exit 1\n```\n\n## Utility Functions\n\n| Function | Description | Usage |\n|----------|-------------|-------|\n| `run_test` | Runs a test function in a subshell and emits TAP output | `run_test test_my_func` |\n| `finish_tests` | Exits with code 1 if any `run_test` calls failed | Call at the end of each test file |\n| `test_case_title` | Prints the calling function name as a test case header | Call at the top of each test function |\n| `test_setup_title` | Prints the calling function name as a setup header | Call at the top of setup functions |\n| `test_teardown_title` | Prints the calling function name as a teardown header | Call at the top of teardown functions |\n\n## Running Tests\n\nRun all tests in a directory:\n\n```sh\n./run_tests.sh \u003ctest_directory\u003e\n```\n\nUsing Make:\n\n```sh\nmake test\n```\n\nOutput follows [TAP (Test Anything Protocol)](https://testanything.org/) format, with one line per test case:\n\n```\n1..5\nok 1 - test_greeting\nok 2 - test_farewell\nnot ok 3 - test_broken\n#   NOT EQUAL!\n#   Expected: 'goodbye'\n#     Actual: 'hello'\nok 4 - test_file_created\nok 5 - test_cleanup\n```\n\nFailed test output is included as TAP diagnostic lines (prefixed with `#`). Exit code is `0` if all tests pass, `1` if any test fails.\n\n## CI Integration\n\nExample GitHub Actions workflow:\n\n```yaml\nname: CI\non:\n  push:\n    branches: [master]\n  pull_request:\n    branches: [master]\n\njobs:\n  test:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os: [ubuntu-latest, macos-latest]\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Install Zsh\n        if: runner.os == 'Linux'\n        run: |\n          sudo apt-get update\n          sudo apt-get install zsh\n\n      - name: Run Tests\n        run: make test\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsha1n%2Fzsh-scriptest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsha1n%2Fzsh-scriptest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsha1n%2Fzsh-scriptest/lists"}