{"id":20330561,"url":"https://github.com/mle86/sh-tests","last_synced_at":"2026-05-13T02:13:00.237Z","repository":{"id":161260186,"uuid":"59849225","full_name":"mle86/sh-tests","owner":"mle86","description":"A tiny test framework, written in shell scripts.","archived":false,"fork":false,"pushed_at":"2021-01-15T11:12:40.000Z","size":57,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-12-06T01:26:04.033Z","etag":null,"topics":["assert","assertion-functions","bash","bash-script","shell","shell-script","test-framework","testing-tools"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mle86.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-05-27T16:41:47.000Z","updated_at":"2021-01-15T14:47:55.000Z","dependencies_parsed_at":null,"dependency_job_id":"94d4a624-fa4d-47ec-9468-f53472209c44","html_url":"https://github.com/mle86/sh-tests","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/mle86/sh-tests","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mle86%2Fsh-tests","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mle86%2Fsh-tests/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mle86%2Fsh-tests/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mle86%2Fsh-tests/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mle86","download_url":"https://codeload.github.com/mle86/sh-tests/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mle86%2Fsh-tests/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32964483,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-12T23:30:32.555Z","status":"online","status_checked_at":"2026-05-13T02:00:07.132Z","response_time":115,"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":["assert","assertion-functions","bash","bash-script","shell","shell-script","test-framework","testing-tools"],"created_at":"2024-11-14T20:16:42.114Z","updated_at":"2026-05-13T02:13:00.194Z","avatar_url":"https://github.com/mle86.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# sh-tests\n\n[![Build Status](https://travis-ci.org/mle86/sh-tests.svg?branch=master)](https://travis-ci.org/mle86/sh-tests)\n\nThis is a shell-based test framework.\nIt's mostly useful for projects written in a language without native unit test support (like other shell scripts)\nor for tiny projects for which an entire unit test framework would be overkill.\n\n[subshells]: doc/Subshell_Testing.md\n\n\n# Writing a simple test\n\nThis is what a minimal test script could look like:\n\n```sh\n#!/bin/sh\n\n# Load init.sh, assuming it's in the same dir as the test script.\n# This provides us with all assertion functions and some additional helper functions and variables.\n. $(dirname \"$0\")/init.sh\n\n# Try to compile our project files. init.sh has set $HERE to the test script's dir.\n# assertCmd() by default expects a return status of zero,\n# i.e. it expects the make command to succeed.\nassertCmd \"make -C $HERE/../\"\n\n# Last line: cleanup and show a green success line for this test script!\nsuccess\n```\n\nCalling this script will look something like this,\nif everything works:\n\n```\n$ ./00-test-compile.sh\nStarting 00-test-compile...\nSuccess: 00-test-compile\n```\n\n\n# Package structure\n\nTest scripts only need to source the *[init.sh](init.sh)* file to be operational.\nThe *[assert.sh](assert.sh)* file will be sourced automatically.\n\nThe *init.sh* and *assert.sh* framework scripts need to be located\nin the same directory as the test script(s),\nbut symlinks to them work fine.\n\n(*init.sh* must be sourced from the test script to be able to define new variables and functions.\nIn some shells, e.g. *dash*, sourced scripts have no way of knowing their own path,\nso they cannot call other files in the same directory –\nunless of course they can rely on their caller living in the same directory,\nin which case they can get that directory from `$0`.)\n\nThe *[run-all-tests.sh](run-all-tests.sh)* script executes all files in the script's directory\nthat match the filename pattern\n`??-test-*.sh`.\nThe files are run in lexicographic order.  \nIt aborts immediately if one of the test script fails.\nIf all test scripts were run successfully,\nit prints a green\n“All _N_ tests executed successfully”\nline and ends.\n\n\n# Assertion functions\n\nAll assertion functions produce no output by themselves if they succeed.\nIf they fail, they will print an error message detailing the failure\n(unless overridden with the *errorMessage* argument accepted by some assertion functions),\nand abort the test script,\nexiting with a return status of 99.\n\n* \u003ccode\u003e\u003cb\u003eassertCmd\u003c/b\u003e [-v] command [expectedReturnStatus=0]\u003c/code\u003e  \n\tTries to run a command and checks its return status.\n\tThe command's *stdout* output will be saved in *$ASSERTCMDOUTPUT*,\n\tand won't be visible (unless the *-v* option is present).\n\tThe command's *stderr* output will NOT be redirected.\n\t*expectedReturnStatus* can be any number (0..255),\n\tor the special value `any`, in which case\n\tall return status values will be accepted\n\t(except 126 and 127, those are still considered a failure,\n\tbecause they usually signify a [shell command invocation error](http://www.tldp.org/LDP/abs/html/exitcodes.html)).\n\n* \u003ccode\u003e\u003cb\u003eassertEq\u003c/b\u003e valueActual valueExpected [errorMessage]\u003c/code\u003e  \n\tThis assertion compares two strings and tests them for equality.\n\n* \u003ccode\u003e\u003cb\u003eassertContains\u003c/b\u003e valueActual valueExpectedPart [errorMessage]\u003c/code\u003e  \n\tThis assertion compares two strings,\n\texpecting the second to be contained somewhere in the first.\n\n* \u003ccode\u003e\u003cb\u003eassertRegex\u003c/b\u003e valueActual regex [errorMessage]\u003c/code\u003e  \n\tThis assertion checks whether the *regex* (PCRE regular expression)\n\tmatches the *valueActual* string.\n\tThe regex argument must be enclosed by slashes,\n\tcan start with a `!` to negate the matching sense,\n\tand can end with `i`/`m`/`s` modifier(s).\n\n* \u003ccode\u003e\u003cb\u003eassertEmpty\u003c/b\u003e valueActual [errorMessage]\u003c/code\u003e  \n\tThis assertion tests a string, expecting it to be empty.\n\n* \u003ccode\u003e\u003cb\u003eassertCmdEq\u003c/b\u003e command expectedOutput [errorMessage]\u003c/code\u003e  \n\tThis assertion is a combination of *assertCmd*+*assertEq*.\n\tIt executes a command, then compares its entire *stdout* output against *expectedOutput*.\n\tThe command is expected to always have a return status of zero.\n\n* \u003ccode\u003e\u003cb\u003eassertCmdEmpty\u003c/b\u003e command [errorMessage]\u003c/code\u003e  \n\tThis assertion is a combination of *assertCmd*+*assertEmpty*.\n\tIt executes a command, then compares its entire *stdout* output against the empty string.\n\tThe command is expected to always have a return status of zero.\n\n* \u003ccode\u003e\u003cb\u003eassertFileSize\u003c/b\u003e fileName expectedSize [errorMessage]\u003c/code\u003e  \n\tThis assertion checks than a file exists and compares its total size (in bytes) against *expectedSize*.\n\n* \u003ccode\u003e\u003cb\u003eassertFileMode\u003c/b\u003e fileName expectedOctalMode [errorMessage]\u003c/code\u003e  \n\tThis assertion checks that a file exists and compares its octal access mode\n\t(as printed by “*stat -c %a*”, e.g. *755*)\n\tagainst *expectedOctalMode*.\n\n* \u003ccode\u003e\u003cb\u003eassertSubshellWasExecuted\u003c/b\u003e [errorMessage]\u003c/code\u003e  \n\tThis assertion checks whether the last subshell script created via *prepare\\_subshell()* has been executed.\n\tIt does so by checking the existence of the marker file which the subshell script should have created.\n\tSee “[Subshell Testing][subshells]”.\n\n\n# Project configuration\n\nIf your project needs some extra configuration for all or most of its tests,\ncreate a file called *config.sh* in the same directory as the test scripts.\n*init.sh* will automatically source that file if it exists.\n\nThis is the place to define additional helper variables and functions used across multiple tests,\noverride hook functions,\nand to build custom assertion functions.\nIf most of your tests run the same binary,\nit might be handy to define a variable with the binary's path in the *config.sh* file.\n\n\n# Writing custom assertions\n\nYou can easily build new assertion functions\nwhich may of course use framework assertions functions\nand/or shell built-ins\nand/or the framework's helper functions such as *fail()*.\n\nIf you use them in one test script only, put them there;\nif several of your test scripts use them,\nput them in your project's [config.sh](#project-configuration) file.\n\n#### Assertion counter\n\nAll assertion functions provided by this framework\nincrease the *$ASSERTCNT* variable by 1.\n\nSo if a custom assertion function\nonly calls one framework assertion,\nthe counter will be correct.\n\nIn all other cases you may use\nthe [addAssertionCount()](#helper-functions) helper function\nto correct to counter.\n\nAlternatively,\nset the *SKIP\\_ASSERTCNT* variable\nto some non-empty value;\nit will disable *addAssertionCount()* completely,\neven for the framework-provided assertions.\n\n#### Example\n\n```bash\nassertAbsolutePathExists () {\n    addAssertionCount +1\n    local SKIP_ASSERTCNT=yes  # this prevents builtin assertions from increasing $ASSERTCNT.\n    assertRegex \"$1\" \"/^\\//\" \\\n        \"The argument is not an absolute path.\"\n    assertRegex \"$1\" \"!/\\/\\.\\.?\\//\" \\\n        \"The argument is not a canonical path as it contains '.' or '..' components.\"\n    [ -e \"$1\" ] || fail \\\n        \"Path does not exist: '$1'\"\n}\n```\n\n\n# Helper variables\n\n*init.sh* also provides these environment variables:\n\n* **$THIS**, the full path of the currently-running test script.  \n\tExample: `/home/mle/project-x/test/00-test-compile.sh`\n\n* **$TESTNAME**, the current test script name (without its *.sh* suffix).  \n\tExample: `00-test-compile`\n\n* **$HERE**, the directory where the current test script is located.  \n\tExample: `/home/mle/project-x/test`\n\n* **$ASSERTSH**, the full path of the *assert.sh* script file, which has already been sourced by *init.sh*.\n\n* **$ASSERTCNT**, the number of assertions performed so far.\n    Starts at zero.\n    Can be changed manually or with [addAssertionCount()](#helper-functions).\n\n* **$SKIP\\_ASSERTCNT**, set to the empty string.\n    You can set this to a non-empty string to prevent addAssertionCount() from doing anything,\n    e.g. inside custom assertions.\n\n\n# Helper functions\n\n*init.sh* also provides these helper functions:\n\n* \u003ccode\u003e\u003cb\u003esuccess\u003c/b\u003e\u003c/code\u003e  \n\tThis function prints a green “*Success: $TESTNAME*” line,\n\tperforms some cleanup,\n\tand ends the test script with exit status zero.\n\tCall it at the end of every test script!\n\n* \u003ccode\u003e\u003cb\u003efail\u003c/b\u003e errorMessage\u003c/code\u003e  \n\tThis function prints an error message in red on *stderr*,\n\tperforms some cleanup,\n\tand ends the test script with exit status 99.\n\tThis can be used for one-line mini-assertions:  \n\t`[ -x binfile ] || fail \"binfile not found or not executable!\"`\n\n* \u003ccode\u003e\u003cb\u003eerr\u003c/b\u003e errorMessage\u003c/code\u003e  \n\tThis function prints an error message in red on *stderr*\n\t(like *fail()*),\n\tbut does NOT abort the test script.\n\tUse it if you want to print multi-line error messages before calling *fail()*.\n\n* \u003ccode\u003e\u003cb\u003eskip\u003c/b\u003e [errorMessage]\u003c/code\u003e  \n\tThis function prints an error message in yellow on *stderr*\n\tand stops the test script,\n\tbut with exit status zero (success).\n\tUse this, for example, if a precondition of your test script was not met\n\tand you don't consider that an actual test failure.\n\n* \u003ccode\u003e\u003cb\u003ecd\\_tmpdir\u003c/b\u003e\u003c/code\u003e  \n\tCreates a temporary directory to work in and changes into it.\n\tUse this if your test script wants to create some files/directories.\n\tThe temporary directory will be automatically removed when the test script ends,\n\tprovided it is empty.\n\n\n* \u003ccode\u003e\u003cb\u003eadd\\_cleanup\u003c/b\u003e filename...\u003c/code\u003e  \n\tAdds one or more filenames to the *$CLEANUP\\_FILES* list.\n\tUse this if your test script creates files in the temporary directory\n\twhich should be automatically deleted as soon as the test script ends.\n\tBe careful, they'll be deleted with “rm -fd” and must not contain spaces.\n\n* \u003ccode\u003e\u003cb\u003eaddAssertionCount\u003c/b\u003e [count]\u003c/code\u003e  \n        Adds a number to the *$ASSERTCNT* env var.\n        The default number argument is `+1`.\n        The argument may be negative.\n        This may be useful if you want to implement custom assertion functions.\n\n\n# Hook functions\n\nAll available hook functions are empty stub functions defined in *init.sh*.\nOverride them in your test script or in your *config.sh* as necessary.\n\n* **hook\\_cleanup()**, called on cleanup, i.e. when the test script ends (either because of a failed assertion or because it called *success()*). Use this instead of *add\\_cleanup()* if your test script needs to do some serious cleanup, especially if it might need to remove files with spaces in their names (which would not be safe for *add\\_cleanup()*, as its *$CLEANUP\\_FILES* list is space-delimited).\n\n\n# Author\n\nMaximilian Eul\n\\\u003c[maximilian@eul.cc](mailto:maximilian@eul.cc)\\\u003e\n\nhttps://github.com/mle86/sh-tests\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmle86%2Fsh-tests","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmle86%2Fsh-tests","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmle86%2Fsh-tests/lists"}