├── .editorconfig ├── .githooks ├── generic └── pre-commit.shellcheck ├── .gitignore ├── .shellcheckrc ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── doc ├── CHANGES-2.1.md ├── RELEASE_NOTES-2.1.0.txt ├── RELEASE_NOTES-2.1.1.txt ├── RELEASE_NOTES-2.1.2.txt ├── RELEASE_NOTES-2.1.3.txt ├── RELEASE_NOTES-2.1.4.txt ├── RELEASE_NOTES-2.1.5.txt ├── RELEASE_NOTES-2.1.6.txt ├── RELEASE_NOTES-2.1.7.md ├── RELEASE_NOTES-2.1.8.md ├── TODO.txt ├── contributors.md └── design_doc.txt ├── examples ├── equality_test.sh ├── lineno_test.sh ├── math.inc ├── math_test.sh ├── mkdir_test.sh ├── mock_file.sh ├── mock_file_test.sh ├── output_test.sh ├── party_test.sh └── suite_test.sh ├── init_githooks.sh ├── lib ├── shflags └── versions ├── shunit2 ├── shunit2_args_test.sh ├── shunit2_asserts_test.sh ├── shunit2_failures_test.sh ├── shunit2_general_test.sh ├── shunit2_macros_test.sh ├── shunit2_misc_test.sh ├── shunit2_shopt_test.sh ├── shunit2_standalone_test.sh ├── shunit2_test_helpers ├── shunit2_tools_test.sh ├── shunit2_xml_test.sh ├── shunit2_xml_time_test.sh └── test_runner /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig for shUnit2. 2 | # https://EditorConfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.sh] 13 | indent_style = space 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /.githooks/generic: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # A generic git hook proxy. 4 | # https://git-scm.com/docs/githooks 5 | 6 | run() { 7 | hook=$1 8 | file=$2 9 | 10 | n=$(echo "${file}" |sed "s/^.*${hook}\.//") 11 | echo "running ${n} ${hook}" 12 | ${file} 13 | } 14 | 15 | die() { 16 | hook=$1 17 | echo "${hook} hook did not succeed" >&2 18 | exit 1 19 | } 20 | 21 | # Redirect output to stderr. 22 | exec 1>&2 23 | 24 | githooks='.githooks' 25 | basename=$(basename "$0") 26 | 27 | for f in $(cd ${githooks} && echo *); do 28 | case "${f}" in 29 | ${basename}.*) 30 | run ${basename} "${githooks}/${f}" || die "${f}" 31 | ;; 32 | esac 33 | done 34 | -------------------------------------------------------------------------------- /.githooks/pre-commit.shellcheck: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Git hook to run ShellCheck. 4 | # 5 | # ShellCheck 6 | 7 | # Treat unset variables as an error when performing parameter expansion. 8 | set -u 9 | 10 | TRUE=0 11 | FALSE=1 12 | 13 | die() { 14 | echo "$@" >&2 15 | exit 1 16 | } 17 | 18 | if ! command -v shellcheck >/dev/null; then 19 | echo 'unable to locate shellcheck' >&2 20 | return 0 21 | fi 22 | 23 | success=${TRUE} 24 | for f in $(git diff --cached --name-only); do 25 | # Check for file deletion. 26 | if [ ! -r "${f}" ]; then 27 | continue 28 | fi 29 | 30 | cmd=':' 31 | case "${f}" in 32 | shflags|shflags_test_helpers) cmd="shellcheck -s sh ${f}" ;; 33 | *.sh) cmd="shellcheck ${f}" ;; 34 | esac 35 | if ! ${cmd}; then 36 | success=${FALSE} 37 | echo "shellcheck error for '${f}'" >&2 38 | fi 39 | done 40 | 41 | exit ${success} 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Hidden files generated by macOS. 2 | .DS_Store 3 | ._* 4 | -------------------------------------------------------------------------------- /.shellcheckrc: -------------------------------------------------------------------------------- 1 | # ShellCheck (https://www.shellcheck.net/) 2 | # 3 | # This file is supported as of shellcheck v0.7.0. 4 | # 5 | # TODO(kward): Remove equivalent references in `*_test.sh` from file once 6 | # Travis CI upgrades its shellcheck version. 7 | 8 | # Disable source following. 9 | disable=SC1090,SC1091 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: bash 2 | 3 | env: 4 | - SHUNIT_COLOR='always' 5 | 6 | script: 7 | # Execute the unit tests. 8 | - ./test_runner 9 | 10 | addons: 11 | apt: 12 | packages: 13 | - ksh 14 | - mksh 15 | - zsh 16 | 17 | matrix: 18 | include: 19 | ### Ubuntu (https://en.wikipedia.org/wiki/Ubuntu). 20 | - os: linux 21 | # Support Ubuntu Focal 20.04 through at least Apr 2025. 22 | dist: focal 23 | - os: linux 24 | # Support Ubuntu Bionic 18.04 through at least Apr 2023. 25 | dist: bionic 26 | - os: linux 27 | # Support Ubuntu Xenial 16.04 through at least Apr 2021. 28 | dist: xenial 29 | - os: linux 30 | # Support Ubuntu Trusty 14.04 through at least Apr 2019. 31 | dist: trusty 32 | 33 | ### Other OSes. 34 | # [2021-10-22 kward] Disable FreeBSD builds until they actually work. 35 | #- os: freebsd 36 | - os: osx 37 | 38 | ### Run the source through ShellCheck (http://www.shellcheck.net). 39 | - os: linux 40 | script: 41 | - shellcheck shunit2 *_test.sh 42 | - shellcheck -s sh shunit2_test_helpers 43 | 44 | branches: 45 | only: 46 | - master 47 | - 2.1.x 48 | # Tags, e.g. v.2.1.8. 49 | - /^v\d+\.\d+(\.\d+)?(-\S*)?$/ 50 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at kate.ward@forestent.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Coding Standards 2 | ================ 3 | 4 | shFlags is more than just a simple 20 line shell script. It is a pretty 5 | significant library of shell code that at first glance is not that easy to 6 | understand. To improve code readability and usability, some guidelines have been 7 | set down to make the code more understandable for anyone who wants to read or 8 | modify it. 9 | 10 | Function declaration 11 | -------------------- 12 | 13 | Declare functions using the following form: 14 | 15 | ```sh 16 | doSomething() { 17 | echo 'done!' 18 | } 19 | ``` 20 | 21 | One-line functions are allowed if they can fit within the 80 char line limit. 22 | 23 | ```sh 24 | doSomething() { echo 'done!'; } 25 | ``` 26 | 27 | Function documentation 28 | ---------------------- 29 | 30 | Each function should be preceded by a header that provides the following: 31 | 32 | 1. A one-sentence summary of what the function does. 33 | 34 | 1. (optional) A longer description of what the function does, and perhaps some 35 | special information that helps convey its usage better. 36 | 37 | 1. Args: a one-line summary of each argument of the form: 38 | 39 | `name: type: description` 40 | 41 | 1. Output: a one-line summary of the output provided. Only output to STDOUT 42 | must be documented, unless the output to STDERR is of significance (i.e. not 43 | just an error message). The output should be of the form: 44 | 45 | `type: description` 46 | 47 | 1. Returns: a one-line summary of the value returned. Returns in shell are 48 | always integers, but if the output is a true/false for success (i.e. a 49 | boolean), it should be noted. The output should be of the form: 50 | 51 | `type: description` 52 | 53 | Here is a sample header: 54 | 55 | ``` 56 | # Return valid getopt options using currently defined list of long options. 57 | # 58 | # This function builds a proper getopt option string for short (and long) 59 | # options, using the current list of long options for reference. 60 | # 61 | # Args: 62 | # _flags_optStr: integer: option string type (__FLAGS_OPTSTR_*) 63 | # Output: 64 | # string: generated option string for getopt 65 | # Returns: 66 | # boolean: success of operation (always returns True) 67 | ``` 68 | 69 | Variable and function names 70 | --------------------------- 71 | 72 | All shFlags specific constants, variables, and functions will be prefixed 73 | appropriately with 'flags'. This is to distinguish usage in the shFlags code 74 | from users own scripts so that the shell name space remains predictable to 75 | users. The exceptions here are the standard `assertEquals`, etc. functions. 76 | 77 | All non built-in constants and variables will be surrounded with squiggle 78 | brackets, e.g. `${flags_someVariable}` to improve code readability. 79 | 80 | Due to some shells not supporting local variables in functions, care in the 81 | naming and use of variables, both public and private, is very important. 82 | Accidental overriding of the variables can occur easily if care is not taken as 83 | all variables are technically global variables in some shells. 84 | 85 | Type | Sample 86 | ---- | ------ 87 | global public constant | `FLAGS_TRUE` 88 | global private constant | `__FLAGS_SHELL_FLAGS` 89 | global public variable | `flags_variable` 90 | global private variable | `__flags_variable` 91 | global macro | `_FLAGS_SOME_MACRO_` 92 | public function | `flags_function` 93 | public function, local variable | `flags_variable_` 94 | private function | `_flags_function` 95 | private function, local variable | `_flags_variable_` 96 | 97 | Where it makes sense to improve readability, variables can have the first 98 | letter of the second and later words capitalized. For example, the local 99 | variable name for the help string length is `flags_helpStrLen_`. 100 | 101 | There are three special-case global public variables used. They are used due to 102 | overcome the limitations of shell scoping or to prevent forking. The three 103 | variables are: 104 | 105 | - `flags_error` 106 | - `flags_output` 107 | - `flags_return` 108 | 109 | Local variable cleanup 110 | ---------------------- 111 | 112 | As many shells do not support local variables, no support for cleanup of 113 | variables is present either. As such, all variables local to a function must be 114 | cleared up with the `unset` built-in command at the end of each function. 115 | 116 | Indentation 117 | ----------- 118 | 119 | Code block indentation is two (2) spaces, and tabs may not be used. 120 | 121 | ```sh 122 | if [ -z 'some string' ]; then 123 | someFunction 124 | fi 125 | ``` 126 | 127 | Lines of code should be no longer than 80 characters unless absolutely 128 | necessary. When lines are wrapped using the backslash character '\', subsequent 129 | lines should be indented with four (4) spaces so as to differentiate from the 130 | standard spacing of two characters, and tabs may not be used. 131 | 132 | ```sh 133 | for x in some set of very long set of arguments that make for a very long \ 134 | that extends much too long for one line 135 | do 136 | echo ${x} 137 | done 138 | ``` 139 | 140 | When a conditional expression is written using the built-in [ command, and that 141 | line must be wrapped, place the control || or && operators on the same line as 142 | the expression where possible, with the list to be executed on its own line. 143 | 144 | ```sh 145 | [ -n 'some really long expression' -a -n 'some other long expr' ] && \ 146 | echo 'that was actually true!' 147 | ``` 148 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # shUnit2 2 | 3 | shUnit2 is a [xUnit](http://en.wikipedia.org/wiki/XUnit) unit test framework for 4 | Bourne based shell scripts, and it is designed to work in a similar manner to 5 | [JUnit](http://www.junit.org), [PyUnit](http://pyunit.sourceforge.net), etc.. If 6 | you have ever had the desire to write a unit test for a shell script, shUnit2 7 | can do the job. 8 | 9 | [![Travis CI](https://api.travis-ci.com/kward/shunit2.svg)](https://app.travis-ci.com/github/kward/shunit2) 10 | 11 | ## Table of Contents 12 | 13 | * [Introduction](#introduction) 14 | * [Credits / Contributors](#credits-contributors) 15 | * [Feedback](#feedback) 16 | * [Quickstart](#quickstart) 17 | * [Function Reference](#function-reference) 18 | * [General Info](#general-info) 19 | * [Asserts](#asserts) 20 | * [Failures](#failures) 21 | * [Setup/Teardown](#setup-teardown) 22 | * [Skipping](#skipping) 23 | * [Suites](#suites) 24 | * [Advanced Usage](#advanced-usage) 25 | * [Some constants you can use](#some-constants-you-can-use) 26 | * [Error Handling](#error-handling) 27 | * [Including Line Numbers in Asserts (Macros)](#including-line-numbers-in-asserts-macros) 28 | * [Test Skipping](#test-skipping) 29 | * [Running specific tests from the command line](#cmd-line-args) 30 | * [Generating test results in JUnit format](#junit-reports) 31 | * [Appendix](#appendix) 32 | * [Getting help](#getting-help) 33 | * [Zsh](#zsh) 34 | 35 | --- 36 | 37 | ## Introduction 38 | 39 | shUnit2 was originally developed to provide a consistent testing solution for 40 | [log4sh][log4sh], a shell based logging framework similar to 41 | [log4j](http://logging.apache.org). During the development of that product, a 42 | repeated problem of having things work just fine under one shell (`/bin/bash` on 43 | Linux to be specific), and then not working under another shell (`/bin/sh` on 44 | Solaris) kept coming up. Although several simple tests were run, they were not 45 | adequate and did not catch some corner cases. The decision was finally made to 46 | write a proper unit test framework after multiple brown-bag releases were made. 47 | _Research was done to look for an existing product that met the testing 48 | requirements, but no adequate product was found._ 49 | 50 | ### Tested software 51 | 52 | **Tested Operating Systems** (varies over time) 53 | 54 | OS | Support | Verified 55 | ----------------------------------- | --------- | -------- 56 | Ubuntu Linux (14.04.05 LTS) | Travis CI | continuous 57 | macOS High Sierra (10.13.3) | Travis CI | continuous 58 | FreeBSD | user | unknown 59 | Solaris 8, 9, 10 (inc. OpenSolaris) | user | unknown 60 | Cygwin | user | unknown 61 | 62 | **Tested Shells** 63 | 64 | * Bourne Shell (__sh__) 65 | * BASH - GNU Bourne Again SHell (__bash__) 66 | * DASH - Debian Almquist Shell (__dash__) 67 | * Korn Shell - AT&T version of the Korn shell (__ksh__) 68 | * mksh - MirBSD Korn Shell (__mksh__) 69 | * zsh - Zsh (__zsh__) (since 2.1.2) _please see the Zsh shell errata for more information_ 70 | 71 | See the appropriate Release Notes for this release 72 | (`doc/RELEASE_NOTES-X.X.X.txt`) for the list of actual versions tested. 73 | 74 | ### Credits / Contributors 75 | 76 | A list of contributors to shUnit2 can be found in `doc/contributors.md`. Many 77 | thanks go out to all those who have contributed to make this a better tool. 78 | 79 | shUnit2 is the original product of many hours of work by Kate Ward, the primary 80 | author of the code. For related software, check out https://github.com/kward. 81 | 82 | ### Feedback 83 | 84 | Feedback is most certainly welcome for this document. Send your questions, 85 | comments, and criticisms via the 86 | [shunit2-users](https://groups.google.com/a/forestent.com/forum/#!forum/shunit2-users/new) 87 | forum (created 2018-12-09), or file an issue via 88 | https://github.com/kward/shunit2/issues. 89 | 90 | --- 91 | 92 | ## Quickstart 93 | 94 | This section will give a very quick start to running unit tests with shUnit2. 95 | More information is located in later sections. 96 | 97 | Here is a quick sample script to show how easy it is to write a unit test in 98 | shell. _Note: the script as it stands expects that you are running it from the 99 | "examples" directory._ 100 | 101 | ```sh 102 | #! /bin/sh 103 | # file: examples/equality_test.sh 104 | 105 | testEquality() { 106 | assertEquals 1 1 107 | } 108 | 109 | # Load shUnit2. 110 | . ../shunit2 111 | ``` 112 | 113 | Running the unit test should give results similar to the following. 114 | 115 | ```console 116 | $ cd examples 117 | $ ./equality_test.sh 118 | testEquality 119 | 120 | Ran 1 test. 121 | 122 | OK 123 | ``` 124 | 125 | W00t! You've just run your first successful unit test. So, what just happened? 126 | Quite a bit really, and it all happened simply by sourcing the `shunit2` 127 | library. The basic functionality for the script above goes like this: 128 | 129 | * When shUnit2 is sourced, it will walk through any functions defined whose name 130 | starts with the string `test`, and add those to an internal list of tests to 131 | execute. Once a list of test functions to be run has been determined, shunit2 132 | will go to work. 133 | * Before any tests are executed, shUnit2 again looks for a function, this time 134 | one named `oneTimeSetUp()`. If it exists, it will be run. This function is 135 | normally used to setup the environment for all tests to be run. Things like 136 | creating directories for output or setting environment variables are good to 137 | place here. Just so you know, you can also declare a corresponding function 138 | named `oneTimeTearDown()` function that does the same thing, but once all the 139 | tests have been completed. It is good for removing temporary directories, etc. 140 | * shUnit2 is now ready to run tests. Before doing so though, it again looks for 141 | another function that might be declared, one named `setUp()`. If the function 142 | exists, it will be run before each test. It is good for resetting the 143 | environment so that each test starts with a clean slate. **At this stage, the 144 | first test is finally run.** The success of the test is recorded for a report 145 | that will be generated later. After the test is run, shUnit2 looks for a final 146 | function that might be declared, one named `tearDown()`. If it exists, it will 147 | be run after each test. It is a good place for cleaning up after each test, 148 | maybe doing things like removing files that were created, or removing 149 | directories. This set of steps, `setUp() > test() > tearDown()`, is repeated 150 | for all of the available tests. 151 | * Once all the work is done, shUnit2 will generate the nice report you saw 152 | above. A summary of all the successes and failures will be given so that you 153 | know how well your code is doing. 154 | 155 | We should now try adding a test that fails. Change your unit test to look like 156 | this. 157 | 158 | ```sh 159 | #! /bin/sh 160 | # file: examples/party_test.sh 161 | 162 | testEquality() { 163 | assertEquals 1 1 164 | } 165 | 166 | testPartyLikeItIs1999() { 167 | year=`date '+%Y'` 168 | assertEquals "It's not 1999 :-(" '1999' "${year}" 169 | } 170 | 171 | # Load shUnit2. 172 | . ../shunit2 173 | ``` 174 | 175 | So, what did you get? I guess it told you that this isn't 1999. Bummer, eh? 176 | Hopefully, you noticed a couple of things that were different about the second 177 | test. First, we added an optional message that the user will see if the assert 178 | fails. Second, we did comparisons of strings instead of integers as in the first 179 | test. It doesn't matter whether you are testing for equality of strings or 180 | integers. Both work equally well with shUnit2. 181 | 182 | Hopefully, this is enough to get you started with unit testing. If you want a 183 | ton more examples, take a look at the tests provided with [log4sh][log4sh] or 184 | [shFlags][shflags]. Both provide excellent examples of more advanced usage. 185 | shUnit2 was after all written to meet the unit testing need that 186 | [log4sh][log4sh] had. 187 | 188 | If you are using distribution packaged shUnit2 which is accessible from 189 | `/usr/bin/shunit2` such as Debian, you can load shUnit2 without specifying its 190 | path. So the last 2 lines in the above can be replaced by: 191 | 192 | ```sh 193 | # Load shUnit2. 194 | . shunit2 195 | ``` 196 | 197 | --- 198 | 199 | ## Function Reference 200 | 201 | ### General Info 202 | 203 | Any string values passed should be properly quoted -- they should be 204 | surrounded by single-quote (`'`) or double-quote (`"`) characters -- so that the 205 | shell will properly parse them. 206 | 207 | ### Asserts 208 | 209 | assertEquals [message] expected actual 210 | 211 | Asserts that _expected_ and _actual_ are equal to one another. The _expected_ 212 | and _actual_ values can be either strings or integer values as both will be 213 | treated as strings. The _message_ is optional, and must be quoted. 214 | 215 | assertNotEquals [message] unexpected actual 216 | 217 | Asserts that _unexpected_ and _actual_ are not equal to one another. The 218 | _unexpected_ and _actual_ values can be either strings or integer values as both 219 | will be treated as strings. The _message_ is optional, and must be quoted. 220 | 221 | assertSame [message] expected actual 222 | 223 | This function is functionally equivalent to `assertEquals`. 224 | 225 | assertNotSame [message] unexpected actual 226 | 227 | This function is functionally equivalent to `assertNotEquals`. 228 | 229 | assertContains [message] container content 230 | 231 | Asserts that _container_ contains _content_. The _container_ and _content_ 232 | values can be either strings or integer values as both will be treated as 233 | strings. The _message_ is optional, and must be quoted. 234 | 235 | assertNotContains [message] container content 236 | 237 | Asserts that _container_ does not contain _content_. The _container_ and 238 | _content_ values can be either strings or integer values as both will be treated 239 | as strings. The _message_ is optional, and must be quoted. 240 | 241 | assertNull [message] value 242 | 243 | Asserts that _value_ is _null_, or in shell terms, a zero-length string. The 244 | _value_ must be a string as an integer value does not translate into a zero- 245 | length string. The _message_ is optional, and must be quoted. 246 | 247 | assertNotNull [message] value 248 | 249 | Asserts that _value_ is _not null_, or in shell terms, a non-empty string. The 250 | _value_ may be a string or an integer as the latter will be parsed as a non-empty 251 | string value. The _message_ is optional, and must be quoted. 252 | 253 | assertTrue [message] condition 254 | 255 | Asserts that a given shell test _condition_ is _true_. The condition can be as 256 | simple as a shell _true_ value (the value `0` -- equivalent to 257 | `${SHUNIT_TRUE}`), or a more sophisticated shell conditional expression. The 258 | _message_ is optional, and must be quoted. 259 | 260 | A sophisticated shell conditional expression is equivalent to what the __if__ or 261 | __while__ shell built-ins would use (more specifically, what the __test__ 262 | command would use). Testing for example whether some value is greater than 263 | another value can be done this way. 264 | 265 | assertTrue "[ 34 -gt 23 ]" 266 | 267 | Testing for the ability to read a file can also be done. This particular test 268 | will fail. 269 | 270 | assertTrue 'test failed' "[ -r /some/non-existant/file ]" 271 | 272 | As the expressions are standard shell __test__ expressions, it is possible to 273 | string multiple expressions together with `-a` and `-o` in the standard fashion. 274 | This test will succeed as the entire expression evaluates to _true_. 275 | 276 | assertTrue 'test failed' '[ 1 -eq 1 -a 2 -eq 2 ]' 277 | 278 | One word of warning: be very careful with your quoting as shell is not the 279 | most forgiving of bad quoting, and things will fail in strange ways. 280 | 281 | assertFalse [message] condition 282 | 283 | Asserts that a given shell test _condition_ is _false_. The condition can be as 284 | simple as a shell _false_ value (the value `1` -- equivalent to 285 | `${SHUNIT_FALSE}`), or a more sophisticated shell conditional expression. The 286 | _message_ is optional, and must be quoted. 287 | 288 | _For examples of more sophisticated expressions, see `assertTrue`._ 289 | 290 | ### Failures 291 | 292 | Just to clarify, failures __do not__ test the various arguments against one 293 | another. Failures simply fail, optionally with a message, and that is all they 294 | do. If you need to test arguments against one another, use asserts. 295 | 296 | If all failures do is fail, why might one use them? There are times when you may 297 | have some very complicated logic that you need to test, and the simple asserts 298 | provided are simply not adequate. You can do your own validation of the code, 299 | use an `assertTrue ${SHUNIT_TRUE}` if your own tests succeeded, and use a 300 | failure to record a failure. 301 | 302 | fail [message] 303 | 304 | Fails the test immediately. The _message_ is optional, and must be quoted. 305 | 306 | failNotEquals [message] unexpected actual 307 | 308 | Fails the test immediately, reporting that the _unexpected_ and _actual_ values 309 | are not equal to one another. The _message_ is optional, and must be quoted. 310 | 311 | _Note: no actual comparison of unexpected and actual is done._ 312 | 313 | failSame [message] expected actual 314 | 315 | Fails the test immediately, reporting that the _expected_ and _actual_ values 316 | are the same. The _message_ is optional, and must be quoted. 317 | 318 | _Note: no actual comparison of expected and actual is done._ 319 | 320 | failNotSame [message] expected actual 321 | 322 | Fails the test immediately, reporting that the _expected_ and _actual_ values 323 | are not the same. The _message_ is optional, and must be quoted. 324 | 325 | _Note: no actual comparison of expected and actual is done._ 326 | 327 | failFound [message] content 328 | 329 | Fails the test immediately, reporting that the _content_ was found. The 330 | _message_ is optional, and must be quoted. 331 | 332 | _Note: no actual search of content is done._ 333 | 334 | failNotFound [message] content 335 | 336 | Fails the test immediately, reporting that the _content_ was not found. The 337 | _message_ is optional, and must be quoted. 338 | 339 | _Note: no actual search of content is done._ 340 | 341 | ### Setup/Teardown 342 | 343 | oneTimeSetUp 344 | 345 | This function can be optionally overridden by the user in their test suite. 346 | 347 | If this function exists, it will be called once before any tests are run. It is 348 | useful to prepare a common environment for all tests. 349 | 350 | oneTimeTearDown 351 | 352 | This function can be optionally overridden by the user in their test suite. 353 | 354 | If this function exists, it will be called once after all tests are completed. 355 | It is useful to clean up the environment after all tests. 356 | 357 | setUp 358 | 359 | This function can be optionally overridden by the user in their test suite. 360 | 361 | If this function exists, it will be called before each test is run. It is useful 362 | to reset the environment before each test. 363 | 364 | tearDown 365 | 366 | This function can be optionally overridden by the user in their test suite. 367 | 368 | If this function exists, it will be called after each test completes. It is 369 | useful to clean up the environment after each test. 370 | 371 | ### Skipping 372 | 373 | startSkipping 374 | 375 | This function forces the remaining _assert_ and _fail_ functions to be 376 | "skipped", i.e. they will have no effect. Each function skipped will be recorded 377 | so that the total of asserts and fails will not be altered. 378 | 379 | endSkipping 380 | 381 | This function returns calls to the _assert_ and _fail_ functions to their 382 | default behavior, i.e. they will be called. 383 | 384 | isSkipping 385 | 386 | This function returns the current state of skipping. It can be compared against 387 | `${SHUNIT_TRUE}` or `${SHUNIT_FALSE}` if desired. 388 | 389 | ### Suites 390 | 391 | The default behavior of shUnit2 is that all tests will be found dynamically. If 392 | you have a specific set of tests you want to run, or you don't want to use the 393 | standard naming scheme of prefixing your tests with `test`, these functions are 394 | for you. Most users will never use them though. 395 | 396 | suite 397 | 398 | This function can be optionally overridden by the user in their test suite. 399 | 400 | If this function exists, it will be called when `shunit2` is sourced. If it does 401 | not exist, shUnit2 will search the parent script for all functions beginning 402 | with the word `test`, and they will be added dynamically to the test suite. 403 | 404 | suite_addTest name 405 | 406 | This function adds a function named _name_ to the list of tests scheduled for 407 | execution as part of this test suite. This function should only be called from 408 | within the `suite()` function. 409 | 410 | --- 411 | 412 | ## Advanced Usage 413 | 414 | ### Some constants you can use 415 | 416 | There are several constants provided by shUnit2 as variables that might be of 417 | use to you. 418 | 419 | *Predefined* 420 | 421 | | Constant | Value | 422 | | --------------- | ----- | 423 | | SHUNIT\_TRUE | Standard shell `true` value (the integer value 0). | 424 | | SHUNIT\_FALSE | Standard shell `false` value (the integer value 1). | 425 | | SHUNIT\_ERROR | The integer value 2. | 426 | | SHUNIT\_TMPDIR | Path to temporary directory that will be automatically cleaned up upon exit of shUnit2. | 427 | | SHUNIT\_VERSION | The version of shUnit2 you are running. | 428 | 429 | *User defined* 430 | 431 | | Constant | Value | 432 | | ----------------- | ----- | 433 | | SHUNIT\_CMD\_EXPR | Override which `expr` command is used. By default `expr` is used, except on BSD systems where `gexpr` is used. | 434 | | SHUNIT\_COLOR | Enable colorized output. Options are 'auto', 'always', or 'none', with 'auto' being the default. | 435 | | SHUNIT\_PARENT | The filename of the shell script containing the tests. This is needed specifically for Zsh support. | 436 | | SHUNIT\_TEST\_PREFIX | Define this variable to add a prefix in front of each test name that is output in the test report. | 437 | 438 | ### Error handling 439 | 440 | The constants values `SHUNIT_TRUE`, `SHUNIT_FALSE`, and `SHUNIT_ERROR` are 441 | returned from nearly every function to indicate the success or failure of the 442 | function. Additionally the variable `flags_error` is filled with a detailed 443 | error message if any function returns with a `SHUNIT_ERROR` value. 444 | 445 | ### Including Line Numbers in Asserts (Macros) 446 | 447 | If you include lots of assert statements in an individual test function, it can 448 | become difficult to determine exactly which assert was thrown unless your 449 | messages are unique. To help somewhat, line numbers can be included in the 450 | assert messages. To enable this, a special shell "macro" must be used rather 451 | than the standard assert calls. _Shell doesn't actually have macros; the name is 452 | used here as the operation is similar to a standard macro._ 453 | 454 | For example, to include line numbers for a `assertEquals()` function call, 455 | replace the `assertEquals()` with `${_ASSERT_EQUALS_}`. 456 | 457 | _**Example** -- Asserts with and without line numbers_ 458 | 459 | ```shell 460 | #! /bin/sh 461 | # file: examples/lineno_test.sh 462 | 463 | testLineNo() { 464 | # This assert will have line numbers included (e.g. "ASSERT:[123] ..."). 465 | echo "ae: ${_ASSERT_EQUALS_}" 466 | ${_ASSERT_EQUALS_} 'not equal' 1 2 467 | 468 | # This assert will not have line numbers included (e.g. "ASSERT: ..."). 469 | assertEquals 'not equal' 1 2 470 | } 471 | 472 | # Load shUnit2. 473 | . ../shunit2 474 | ``` 475 | 476 | Notes: 477 | 478 | 1. Due to how shell parses command-line arguments, _**all strings used with 479 | macros should be quoted twice**_. Namely, single-quotes must be converted to single-double-quotes, and vice-versa.
480 |
481 | Normal `assertEquals` call.
482 | `assertEquals 'some message' 'x' ''`
483 |
484 | Macro `_ASSERT_EQUALS_` call. Note the extra quoting around the _message_ and 485 | the _null_ value.
486 | `_ASSERT_EQUALS_ '"some message"' 'x' '""'` 487 | 488 | 1. Line numbers are not supported in all shells. If a shell does not support 489 | them, no errors will be thrown. Supported shells include: __bash__ (>=3.0), 490 | __ksh__, __mksh__, and __zsh__. 491 | 492 | ### Test Skipping 493 | 494 | There are times where the test code you have written is just not applicable to 495 | the system you are running on. This section describes how to skip these tests 496 | but maintain the total test count. 497 | 498 | Probably the easiest example would be shell code that is meant to run under the 499 | __bash__ shell, but the unit test is running under the Bourne shell. There are 500 | things that just won't work. The following test code demonstrates two sample 501 | functions, one that will be run under any shell, and the another that will run 502 | only under the __bash__ shell. 503 | 504 | _**Example** -- math include_ 505 | ```sh 506 | # file: examples/math.inc. 507 | 508 | add_generic() { 509 | num_a=$1 510 | num_b=$2 511 | 512 | expr $1 + $2 513 | } 514 | 515 | add_bash() { 516 | num_a=$1 517 | num_b=$2 518 | 519 | echo $(($1 + $2)) 520 | } 521 | ``` 522 | 523 | And here is a corresponding unit test that correctly skips the `add_bash()` function when the unit test is not running under the __bash__ shell. 524 | 525 | _**Example** -- math unit test_ 526 | ```sh 527 | #! /bin/sh 528 | # file: examples/math_test.sh 529 | 530 | testAdding() { 531 | result=`add_generic 1 2` 532 | assertEquals \ 533 | "the result of '${result}' was wrong" \ 534 | 3 "${result}" 535 | 536 | # Disable non-generic tests. 537 | [ -z "${BASH_VERSION:-}" ] && startSkipping 538 | 539 | result=`add_bash 1 2` 540 | assertEquals \ 541 | "the result of '${result}' was wrong" \ 542 | 3 "${result}" 543 | } 544 | 545 | oneTimeSetUp() { 546 | # Load include to test. 547 | . ./math.inc 548 | } 549 | 550 | # Load and run shUnit2. 551 | . ../shunit2 552 | ``` 553 | 554 | Running the above test under the __bash__ shell will result in the following 555 | output. 556 | 557 | ```console 558 | $ /bin/bash math_test.sh 559 | testAdding 560 | 561 | Ran 1 test. 562 | 563 | OK 564 | ``` 565 | 566 | But, running the test under any other Unix shell will result in the following 567 | output. 568 | 569 | ```console 570 | $ /bin/ksh math_test.sh 571 | testAdding 572 | 573 | Ran 1 test. 574 | 575 | OK (skipped=1) 576 | ``` 577 | 578 | As you can see, the total number of tests has not changed, but the report 579 | indicates that some tests were skipped. 580 | 581 | Skipping can be controlled with the following functions: `startSkipping()`, 582 | `endSkipping()`, and `isSkipping()`. Once skipping is enabled, it will remain 583 | enabled until the end of the current test function call, after which skipping is 584 | disabled. 585 | 586 | ### Running specific tests from the command line. 587 | 588 | When running a test script, you may override the default set of tests, or the suite-specified set of tests, by providing additional arguments on the command line. Each additional argument after the `--` marker is assumed to be the name of a test function to be run in the order specified. e.g. 589 | 590 | ```console 591 | test-script.sh -- testOne testTwo otherFunction 592 | ``` 593 | 594 | or 595 | 596 | ```console 597 | shunit2 test-script.sh testOne testTwo otherFunction 598 | ``` 599 | 600 | In either case, three functions will be run as tests, `testOne`, `testTwo`, and `otherFunction`. Note that the function `otherFunction` would not normally be run by `shunit2` as part of the implicit collection of tests as it's function name does not match the test function name pattern `test*`. 601 | 602 | If a specified test function does not exist, `shunit2` will still attempt to run that function and thereby cause a failure which `shunit2` will catch and mark as a failed test. All other tests will run normally. 603 | 604 | The specification of tests does not affect how `shunit2` looks for and executes the setup and tear down functions, which will still run as expected. 605 | 606 | ### Generating test results in JUnit format. 607 | 608 | Most continuous integration tools like CircleCI, are capable to interpret test results in JUnit format, helping you with spacilized sections and triggers tailored to identify faster a failing test. This functionality is still unreleased but you can test it right away, installing shunit2 from source. 609 | 610 | Given that you execute your test script in the following way 611 | 612 | ```sh 613 | test-script.sh 614 | ``` 615 | 616 | You can generate the JUnit report like this 617 | 618 | ```sh 619 | mkdir -p results 620 | test-script.sh -- --output-junit-xml=results/test-script.xml 621 | ``` 622 | 623 | It will generate something like 624 | 625 | ```xml 626 | 627 | 633 | 638 | 639 | 640 | ``` 641 | 642 | You can also specify a more verbose suite name 643 | 644 | ```sh 645 | test-script.sh -- --output-junit-xml=results/test-script.xml --suite-name=Test_Script 646 | ``` 647 | 648 | Then say to your CI tool where the results are. In the case of CircleCI is like the following 649 | 650 | ```yaml 651 | - store_test_results: 652 | path: results 653 | ``` 654 | 655 | --- 656 | 657 | ## Appendix 658 | 659 | ### Getting Help 660 | 661 | For help, please send requests to either the shunit2-users@forestent.com mailing 662 | list (archives available on the web at 663 | https://groups.google.com/a/forestent.com/forum/#!forum/shunit2-users) or 664 | directly to Kate Ward . 665 | 666 | ### Zsh 667 | 668 | For compatibility with Zsh, there is one requirement that must be met -- the 669 | `shwordsplit` option must be set. There are three ways to accomplish this. 670 | 671 | 1. In the unit-test script, add the following shell code snippet before sourcing 672 | the `shunit2` library. 673 | 674 | ```sh 675 | setopt shwordsplit 676 | ``` 677 | 678 | 2. When invoking __zsh__ from either the command-line or as a script with `#!`, 679 | add the `-y` parameter. 680 | 681 | ```sh 682 | #! /bin/zsh -y 683 | ``` 684 | 685 | 3. When invoking __zsh__ from the command-line, add `-o shwordsplit --` as 686 | parameters before the script name. 687 | 688 | ```console 689 | $ zsh -o shwordsplit -- some_script 690 | ``` 691 | 692 | [log4sh]: https://github.com/kward/log4sh 693 | [shflags]: https://github.com/kward/shflags 694 | -------------------------------------------------------------------------------- /doc/CHANGES-2.1.md: -------------------------------------------------------------------------------- 1 | # shUnit2 2.1.x Changes 2 | 3 | ## Changes with 2.1.9 4 | 5 | ### Fixed 6 | 7 | Issue #37. shUnit2 now works properly for tests that have the `-e` shell option enabled. This took *way* longer than originally anticipated, especially once older Ubuntu releases were added to the testing on Travis CI. 8 | 9 | Issue #129. `assertFalse ''` now returns `SHUNIT_TRUE`. 10 | 11 | Issue #54 (again). To fix #37, this fix had to be reworked to use 'builtin' instead of 'command' as the latter caused problems with `set -e`. 12 | 13 | ## Changes with 2.1.8 14 | 15 | ### New 16 | 17 | Issue #29. Add support for user defined prefix for test names. A prefix can be added by defining the `SHUNIT_TEST_PREFIX` variable. 18 | 19 | Issue #59. Added `assertContains` and `assertNotContains` functionality. 20 | 21 | ### Improvements 22 | 23 | Issue #78. Added an example for using suite tests. 24 | 25 | Run continuous integration additionally against Ubuntu Trusty. 26 | 27 | ### Fixed 28 | 29 | Issue #94. Removed the `gen_test_report.sh` script as the Travis CI output can be used instead. Reports were used before Travis CI was used. 30 | 31 | Issue #84. Treat syntax errors in functions as test failures. 32 | 33 | Issue #77. Fail tests when the environment functions (e.g. `setup()` or`tearDown()`) fail. 34 | 35 | 36 | ## Changes with 2.1.7 37 | 38 | ### Bug fixes 39 | 40 | Issue #69. shUnit2 should not exit with 0 when it has (syntax) errors. 41 | 42 | ### Enhancements 43 | 44 | Issue #54. Shell commands prefixed with '\' so that they can be stubbed in tests. 45 | 46 | Issue #68. Ran all code through [ShellCheck](http://www.shellcheck.net/). 47 | 48 | Issue #60. Continuous integration tests now run with [Travis CI](https://travis-ci.org/kward/shunit2). 49 | 50 | Issue #56. Added color support. Color is enabled automatically when supported, but can be disabled by defining the `SHUNIT_COLOR` environment variable before sourcing shunit2. Accepted values are `always`, `auto` (the default), and `none`. 51 | 52 | Issue #35. Add colored output. 53 | 54 | ### Other 55 | 56 | Moved code to [GitHub](https://github.com/kward/shunit2), and restructured to be more GitHub like. 57 | 58 | Changed to the Apache 2.0 license. 59 | 60 | 61 | ## Changes with 2.1.6 62 | 63 | Removed all references to the DocBook documentation. 64 | 65 | Simplified the 'src' structure. 66 | 67 | Fixed error message in `fail()` that stated wrong number of required arguments. 68 | 69 | Updated `lib/versions`. 70 | 71 | Fixed bug in `_shunit_mktempDir()` where a failure occurred when the 'od' command was not present in `/usr/bin`. 72 | 73 | Renamed `shunit_tmpDir` variable to `SHUNIT_TMPDIR` to closer match the standard `TMPDIR` variable. 74 | 75 | Added support for calling shunit2 as an executable, in addition to the existing method of sourcing it in as a library. This allows users to keep tests working despite the location of the shunit2 executable being different for each OS distribution. 76 | 77 | Issue #14: Improved handling of some strange chars (e.g. single and double quotes) in messages. 78 | 79 | Issue# 27: Fixed error message for `assertSame()`. 80 | 81 | Issue# 25: Added check and error message to user when phantom functions are written to a partition mounted with `noexec`. 82 | 83 | Issue# 11: Added support for defining functions like `function someFunction()`. 84 | 85 | 86 | ## Changes with 2.1.5 87 | 88 | Issue# 1: Fixed bug pointed out by R Bernstein in the trap code where certain types of exit conditions did not generate the ending report. 89 | 90 | Issue# 2: Added `assertNotEquals()` assert. 91 | 92 | Issue# 3: Moved check for unset variables out of shUnit2 into the unit tests. Testing poorly written software blows up if this check is in, but it is only interesting for shUnit2 itself. Added `shunit_test_output.sh` unit test for this. Some shells still do not catch such errors properly (e.g. Bourne shell and BASH 2.x). 93 | 94 | Added new custom assert in test_helpers to check for output to STDOUT, and none to STDERR. 95 | 96 | Replaced fatal message in the temp directory creation with a `_shunit_fatal()` function call. 97 | 98 | Fixed `test_output` unit test so it works now that the `set -u` stuff was removed for Issue# 3. 99 | 100 | Flushed out the coding standards in the `README.txt` a bit more, and brought the shunit2 code up to par with the documented standards. 101 | 102 | Issue# 4: Completely changed the reporting output to be a closer match for JUnit and PyUnit. As a result, tests are counted separately from assertions. 103 | 104 | Provide public `shunit_tmpDir` variable that can be used by unit test scripts that need automated and guaranteed cleanup. 105 | 106 | Issue# 7: Fixed duplicated printing of messages passed to asserts. 107 | 108 | Per code review, fixed wording of `failSame()` and `failNotSame()` messages. 109 | 110 | Replaced `version_info.sh` with versions library and made appropriate changes in other scripts to use it. 111 | 112 | Added `gen_test_results.sh` to make releases easier. 113 | 114 | Fixed bugs in `shlib_relToAbsPath()` in shlib. 115 | 116 | Converted DocBook documentation to reStructuredText for easier maintenance. The DocBook documentation is now considered obsolete, and will be removed in a future release. 117 | 118 | Issue# 5: Fixed the documentation around the usage of failures. 119 | 120 | Issue# 9: Added unit tests and updated documentation to demonstrate the requirement of quoting values twice when macros are used. This is due to how shell parses arguments. 121 | 122 | When an invalid number of arguments is passed to a function, the invalid number is returned to the user so they are more aware of what the cause might be. 123 | 124 | 125 | ## Changes with 2.1.4 126 | 127 | Removed the `_shunit_functionExists()` function as it was dead code. 128 | 129 | Fixed zsh version number check in `version_info`. 130 | 131 | Fixed bug in last resort temporary directory creation. 132 | 133 | Fixed off-by-one in exit value for scripts caught by the trap handler. 134 | 135 | Added argument count error checking to all functions. 136 | 137 | Added `mkdir_test.sh` example. 138 | 139 | Moved `src/test` into `src/shell` to better match structure used with shFlags. 140 | 141 | Fixed problem where null values were not handled properly under ksh. 142 | 143 | Added support for outputting line numbers as part of assert messages. 144 | 145 | Started documenting the coding standards, and changed some variable names as a result. 146 | 147 | Improved zsh version and option checks. 148 | 149 | Renamed the `__SHUNIT_VERSION` variable to `SHUNIT_VERSION`. 150 | 151 | 152 | ## Changes with 2.1.3 153 | 154 | Added some explicit variable defaults, even though the variables are set, as they sometimes behave strange when the script is canceled. 155 | 156 | Additional workarounds for zsh compatibility. 157 | 158 | shUnit2 now exits with a non-zero exit code if any of the tests failed. This was done for automated testing frameworks. Tests that were skipped are not considered failures, and do not affect the exit code. 159 | 160 | Changed detection of STDERR output in unit tests. 161 | 162 | 163 | ## Changes with 2.1.2 164 | 165 | Unset additional variables that were missed. 166 | 167 | Added checks and workarounds to improve zsh compatibility. 168 | 169 | Added some argument count checks `assertEquals()`, `assertNull()`, and 170 | `assertSame()`. 171 | 172 | 173 | ## Changes with 2.1.1 174 | 175 | Fixed bug where `fail()` was not honoring skipping. 176 | 177 | Fixed problem with `docs-docbook-prep` target that prevented it from working. (Thanks to Bryan Larsen for pointing this out.) 178 | 179 | Changed the test in `assertFalse()` so that any non-zero value registers as false. (Credits to Bryan Larsen) 180 | 181 | Major fiddling to bring more in line with [JUnit](http://junit.org/). Asserts give better output when no message is given, and failures now just fail. 182 | 183 | It was pointed out that the simple 'failed' message for a failed assert was not only insufficient, it was nonstandard (when compared to JUnit) and didn't provide the user with an expected vs actual result. The code was revised somewhat to bring closer into alignment with JUnit (v4.3.1 specifically) so that it feels more "normal". (Credits to Richard Jensen) 184 | 185 | As part of the JUnit realignment, it was noticed that `fail*()` functions in JUnit don't actually do any comparisons themselves. They only generate a failure message. Updated the code to match. 186 | 187 | Added self-testing unit tests. Kinda horkey, but they did find bugs during the JUnit realignment. 188 | 189 | Fixed the code for returning from asserts as the return was being called before the unsetting of variables occurred. (Credits to Mathias Goldau) 190 | 191 | The assert(True|False)() functions now accept an integer value for a conditional test. A value of '0' is considered 'true', while any non-zero value is considered 'false'. 192 | 193 | All public functions now fill use default values to work properly with the '-x' shell debugging flag. 194 | 195 | Fixed the method of percent calculation for the report to get achieve better accuracy. 196 | 197 | 198 | ## Changes with 2.1.0 (since 2.0.1) 199 | 200 | This release is a branch of the 2.0.1 release. 201 | 202 | Moving to [reStructured Text](http://docutils.sourceforge.net/rst.html) for the documentation. 203 | 204 | Fixed problem with `fail()`. The failure message was not properly printed. 205 | 206 | Fixed the `Makefile` so that the DocBook XML and XSLT files would be downloaded before parsing can continue. 207 | 208 | Renamed the internal `__SHUNIT_TRUE` and `__SHUNIT_FALSE` variables to`SHUNIT_TRUE` and `SHUNIT_FALSE` so that unit tests can "use" them. 209 | 210 | Added support for test "skipping". If skipping is turned on with the `startSkip()` function, `assert` and `fail` functions will return immediately, and the skip will be recorded. 211 | 212 | The report output format was changed to include the percentage for each test result, rather than just those successful. 213 | -------------------------------------------------------------------------------- /doc/RELEASE_NOTES-2.1.0.txt: -------------------------------------------------------------------------------- 1 | Release Notes for shUnit2 2.1.0 2 | =============================== 3 | 4 | This release was branched from shUnit2 2.0.1. It mostly adds new functionality, 5 | but there are couple of bugs fixed from the previous release. 6 | 7 | See the ``CHANGES-2.1.rst`` file for a full list of changes. 8 | 9 | 10 | Tested Platforms 11 | ---------------- 12 | 13 | This list of platforms comes from the latest version of log4sh as shUnit2 is 14 | used in the testing of log4sh on each of these platforms. 15 | 16 | Cygwin 17 | 18 | - bash 3.2.9(10) 19 | - pdksh 5.2.14 20 | 21 | Linux 22 | 23 | - bash 3.1.17(1), 3.2.10(1) 24 | - dash 0.5.3 25 | - ksh 1993-12-28 26 | - pdksh 5.2.14 27 | - zsh 4.3.2 (does not work) 28 | 29 | Mac OS X 10.4.8 (Darwin 8.8) 30 | 31 | - bash 2.05b.0(1) 32 | - ksh 1993-12-28 33 | 34 | Solaris 8 U3 (x86) 35 | 36 | - /bin/sh 37 | - bash 2.03.0(1) 38 | - ksh M-11/16/88i 39 | 40 | Solaris 10 U2 (sparc) 41 | 42 | - /bin/sh 43 | - bash 3.00.16(1) 44 | - ksh M-11/16/88i 45 | 46 | Solaris 10 U2 (x86) 47 | 48 | - /bin/sh 49 | - bash 3.00.16(1) 50 | - ksh M-11/16/88i 51 | 52 | 53 | New Features 54 | ------------ 55 | 56 | Test skipping 57 | 58 | Support added for test "skipping". A skip mode can be enabled so that 59 | subsequent ``assert`` and ``fail`` functions that are called will be recorded 60 | as "skipped" rather than as "passed" or "failed". This functionality can be 61 | used such that when a set of tests makes sense on one platform but not on 62 | another, they can be effectively disabled without altering the total number 63 | of tests. 64 | 65 | One example might be when something is supported under ``bash``, but not 66 | under a standard Bourne shell. 67 | 68 | New functions: ``startSkipping()``, ``endSkipping``, ``isSkipping`` 69 | 70 | 71 | Changes and Enhancements 72 | ------------------------ 73 | 74 | Moving to the use of `reStructured Text 75 | `_ for documentation. It is easy to 76 | read and edit in textual form, but converts nicely to HTML. 77 | 78 | The report format has changed. Rather than including a simple "success" 79 | percentage at the end, a percentage is given for each type of test. 80 | 81 | 82 | Bug Fixes 83 | --------- 84 | 85 | The ``fail()`` function did not output the optional failure message. 86 | 87 | Fixed the ``Makefile`` so that the DocBook XML and XSLT files would be 88 | downloaded before documentation parsing will continue. 89 | 90 | 91 | Deprecated Features 92 | ------------------- 93 | 94 | None. 95 | 96 | 97 | Known Bugs and Issues 98 | --------------------- 99 | 100 | None. 101 | 102 | 103 | .. $Revision$ 104 | .. vim:fileencoding=latin1:spell:syntax=rst:textwidth=80 105 | -------------------------------------------------------------------------------- /doc/RELEASE_NOTES-2.1.1.txt: -------------------------------------------------------------------------------- 1 | Release Notes for shUnit2 2.1.1 2 | =============================== 3 | 4 | This is mainly a bug fix release, but it also incorporates a realignment with 5 | the JUnit 4 code. Asserts now provide better failure messages, and the failure 6 | functions no longer perform tests. 7 | 8 | See the ``CHANGES-2.1.txt`` file for a full list of changes. 9 | 10 | 11 | Tested Platforms 12 | ---------------- 13 | 14 | This list of platforms comes from the latest version of log4sh as shUnit2 is 15 | used in the testing of log4sh on each of these platforms. 16 | 17 | Cygwin 18 | 19 | - bash 3.2.15(13) 20 | - pdksh 5.2.14 21 | 22 | Linux 23 | 24 | - bash 3.1.17(1), 3.2.10(1) 25 | - dash 0.5.3 26 | - ksh 1993-12-28 27 | - pdksh 5.2.14 28 | - zsh 4.3.2 (does not work) 29 | 30 | Mac OS X 10.4.9 (Darwin 8.9.1) 31 | 32 | - bash 2.05b.0(1) 33 | - ksh 1993-12-28 34 | 35 | Solaris 8 U3 (x86) 36 | 37 | - /bin/sh 38 | - bash 2.03.0(1) 39 | - ksh M-11/16/88i 40 | 41 | Solaris 10 U2 (sparc, x86) 42 | 43 | - /bin/sh 44 | - bash 3.00.16(1) 45 | - ksh M-11/16/88i 46 | 47 | 48 | New Features 49 | ------------ 50 | 51 | None. 52 | 53 | 54 | Changes and Enhancements 55 | ------------------------ 56 | 57 | The internal test in ``assertFalse()`` now accepts any non-zero value as false. 58 | 59 | The ``assertTrue()`` and ``assertFalse()`` functions now accept an integer value 60 | for a conditional test. A value of '0' is considered 'true', while any non-zero 61 | value is considered 'false'. 62 | 63 | Self-testing unit tests were added. 64 | 65 | 66 | Bug Fixes 67 | --------- 68 | 69 | The ``fail()`` assert now honors skipping. 70 | 71 | The ``docs-docbook-prep`` target now works properly. 72 | 73 | All asserts now properly unset their variables. 74 | 75 | 76 | Deprecated Features 77 | ------------------- 78 | 79 | None. 80 | 81 | 82 | Known Bugs and Issues 83 | --------------------- 84 | 85 | Functions do not properly test for an invalid number of arguments. 86 | 87 | 88 | .. vim:fileencoding=latin1:ft=rst:spell:textwidth=80 89 | -------------------------------------------------------------------------------- /doc/RELEASE_NOTES-2.1.2.txt: -------------------------------------------------------------------------------- 1 | Release Notes for shUnit2 2.1.2 2 | =============================== 3 | 4 | This release adds initial support for the zsh shell. Due to some differences 5 | with this shell as compared with others, some special checks have been added, 6 | and there are some extra requirements necessary when this shell is to be used. 7 | 8 | To use zsh with shUnit2, the following two requirements must be met: 9 | * The ``shwordsplit`` option must be set. 10 | * The ``function_argzero`` option must be unset. 11 | 12 | Please read the Shell Errata section of the documentation for guidance on how 13 | to meet these requirements. 14 | 15 | 16 | See the ``CHANGES-2.1.txt`` file for a full list of changes. 17 | 18 | 19 | Tested Platforms 20 | ---------------- 21 | 22 | This list of platforms comes from the latest version of log4sh as shUnit2 is 23 | used in the testing of log4sh on each of these platforms. 24 | 25 | Linux 26 | 27 | - bash 3.1.17(1), 3.2.25(1) 28 | - dash 0.5.4 29 | - ksh 1993-12-28 30 | - pdksh 5.2.14 31 | - zsh 4.2.5, 4.3.4 32 | 33 | Mac OS X 10.4.11 (Darwin 8.11.1) 34 | 35 | - bash 2.05b.0(1) 36 | - ksh 1993-12-28 37 | - zsh 4.2.3 38 | 39 | Solaris 10 U3 (x86) 40 | 41 | - /bin/sh 42 | - bash 3.00.16(1) 43 | - ksh M-11/16/88i 44 | - zsh 4.2.1 45 | 46 | 47 | New Features 48 | ------------ 49 | 50 | Support for the zsh shell. 51 | 52 | 53 | Changes and Enhancements 54 | ------------------------ 55 | 56 | Added some argument count checks. 57 | 58 | 59 | Bug Fixes 60 | --------- 61 | 62 | None. 63 | 64 | 65 | Deprecated Features 66 | ------------------- 67 | 68 | None. 69 | 70 | 71 | Known Bugs and Issues 72 | --------------------- 73 | 74 | Functions do not properly test for an invalid number of arguments. 75 | 76 | ksh and pdksh do not pass null arguments (i.e. empty strings as '') properly, 77 | and as such checks do not work properly. 78 | 79 | zsh requires the ``shwordsplit`` option to be set, and the ``function_argzero`` 80 | option to be unset for proper operation. 81 | 82 | 83 | .. vim:fileencoding=latin1:ft=rst:spell:textwidth=80 84 | -------------------------------------------------------------------------------- /doc/RELEASE_NOTES-2.1.3.txt: -------------------------------------------------------------------------------- 1 | Release Notes for shUnit2 2.1.3 2 | =============================== 3 | 4 | This release is minor feature release. It improves support for zsh (although it 5 | still isn't what it could be) and adds automated testing framework support by 6 | returning a non-zero exit when tests fail. 7 | 8 | To use zsh with shUnit2, the following two requirements must be met: 9 | * The ``shwordsplit`` option must be set. 10 | * The ``function_argzero`` option must be unset. 11 | 12 | Please read the Shell Errata section of the documentation for guidance on how 13 | to meet these requirements. 14 | 15 | See the ``CHANGES-2.1.txt`` file for a full list of changes. 16 | 17 | 18 | Tested Platforms 19 | ---------------- 20 | 21 | Cygwin 22 | 23 | - bash 3.2.33(18) 24 | - pdksh 5.2.14 25 | 26 | Linux 27 | 28 | - bash 3.2.33(1) 29 | - dash 0.5.4 30 | - ksh 1993-12-28 31 | - pdksh 5.2.14 32 | - zsh 4.3.4 33 | 34 | Mac OS X 10.5.2 (Darwin 9.2.2) 35 | 36 | - bash 3.2.17(1) 37 | - ksh 1993-12-28 38 | - zsh 4.3.4 39 | 40 | Solaris 11 x86 (Nevada 77) 41 | 42 | - /bin/sh 43 | - bash 3.2.25(1) 44 | - ksh M-11/16/88i 45 | - zsh 4.3.4 46 | 47 | 48 | New Features 49 | ------------ 50 | 51 | None. 52 | 53 | 54 | Changes and Enhancements 55 | ------------------------ 56 | 57 | Support for automated testing frameworks. 58 | 59 | 60 | Bug Fixes 61 | --------- 62 | 63 | Fixed some issues with zsh support. 64 | 65 | 66 | Deprecated Features 67 | ------------------- 68 | 69 | None. 70 | 71 | 72 | Known Bugs and Issues 73 | --------------------- 74 | 75 | Functions do not properly test for an invalid number of arguments. 76 | 77 | ksh and pdksh do not pass null arguments (i.e. empty strings as '') properly, 78 | and as such checks do not work properly. 79 | 80 | zsh requires the ``shwordsplit`` option to be set, and the ``function_argzero`` 81 | option to be unset for proper operation. 82 | 83 | 84 | .. vim:fileencoding=latin1:ft=rst:spell:textwidth=80 85 | -------------------------------------------------------------------------------- /doc/RELEASE_NOTES-2.1.4.txt: -------------------------------------------------------------------------------- 1 | Release Notes for shUnit2 2.1.4 2 | =============================== 3 | 4 | This release contains lots of bug fixes and changes. Mostly, it fixes zsh 5 | support in zsh 3.0, and the handling of null values in ksh. 6 | 7 | To use zsh with shUnit2, the following requirement must be met: 8 | 9 | - The ``shwordsplit`` option must be set. 10 | 11 | Please read the Shell Errata section of the documentation for guidance on how 12 | to meet these requirements. 13 | 14 | See the ``CHANGES-2.1.txt`` file for a full list of changes. 15 | 16 | 17 | Tested Platforms 18 | ---------------- 19 | 20 | Cygwin 21 | 22 | - bash 3.2.39(19) 23 | - pdksh 5.2.14 24 | - zsh 4.3.4 25 | 26 | Linux (Ubuntu Dapper 6.06) 27 | 28 | - bash 3.1.17(1) 29 | - pdksh 5.2.14 30 | - zsh 4.2.5 31 | 32 | Linux (Ubuntu Hardy 8.04) 33 | 34 | - bash 3.2.39(1) 35 | - dash 0.5.4 36 | - ksh 1993-12-28 37 | - pdksh 5.2.14 38 | - zsh 4.3.4 39 | 40 | Mac OS X 10.5.4 (Darwin 9.4.0) 41 | 42 | - bash 3.2.17(1) 43 | - ksh 1993-12-28 44 | - zsh 4.3.4 45 | 46 | Solaris 9 U6 x86 47 | 48 | - /bin/sh 49 | - bash 2.05.0(1) 50 | - ksh M-11/16/88i 51 | - zsh 3.0.8 52 | 53 | Solaris 11 x86 (Nevada 77) 54 | 55 | - /bin/sh 56 | - bash 3.2.25(1) 57 | - ksh M-11/16/88i 58 | - zsh 4.3.4 59 | 60 | 61 | New Features 62 | ------------ 63 | 64 | Support added to output assert source line number as part of assert messages. 65 | 66 | 67 | Changes and Enhancements 68 | ------------------------ 69 | 70 | Support for automated testing frameworks. 71 | 72 | Added argument count error checking to all functions. 73 | 74 | 75 | Bug Fixes 76 | --------- 77 | 78 | Fixed some issues with ksh and zsh support. 79 | 80 | Fixed off-by-one of exit value in trap handler. 81 | 82 | Fixed handling of null values under ksh. 83 | 84 | Fixed bug in last resort temporary directory creation. 85 | 86 | 87 | Deprecated Features 88 | ------------------- 89 | 90 | None. 91 | 92 | 93 | Known Bugs and Issues 94 | --------------------- 95 | 96 | zsh requires the ``shwordsplit`` option to be set. 97 | 98 | Line numbers in assert messages do not work properly with Bash 2.x. 99 | 100 | .. vim:fileencoding=latin1:ft=rst:spell:tw=80 101 | -------------------------------------------------------------------------------- /doc/RELEASE_NOTES-2.1.5.txt: -------------------------------------------------------------------------------- 1 | Release Notes for shUnit2 2.1.5 2 | =============================== 3 | 4 | This release contains several bug fixes and changes. Additionally, it includes 5 | a rewrite of the test output to better match JUnit and PyUnit. 6 | 7 | This version also includes a slightly expanded set of coding standards by which 8 | shUnit2 is coded. It should help anyone reading the code to better understand 9 | it. 10 | 11 | 12 | 13 | Please read the Shell Errata section of the documentation for guidance on how 14 | to meet these requirements. 15 | 16 | See the ``CHANGES-2.1.txt`` file for a full list of changes. 17 | 18 | 19 | Tested Platforms 20 | ---------------- 21 | 22 | Cygwin 23 | 24 | - bash 3.2.39(20) 25 | - ksh (sym-link to pdksh) 26 | - pdksh 5.2.14 27 | - zsh 4.3.4 28 | 29 | Linux (Ubuntu Dapper 6.06) 30 | 31 | - bash 3.1.17(1) 32 | - ksh M-1993-12-28 33 | - pdksh 5.2.14-99/07/13.2 34 | - zsh 4.2.5 35 | 36 | Linux (Ubuntu Hardy 8.04) 37 | 38 | - bash 3.2.39(1) 39 | - dash 0.5.4 40 | - ksh M-1993-12-28 41 | - pdksh 5.2.14-99/07/13.2 42 | - zsh 4.3.4 43 | 44 | Mac OS X 10.5.4 (Darwin 9.4.0) 45 | 46 | - bash 3.2.17(1) 47 | - ksh M-1993-12-28 48 | - zsh 4.3.4 49 | 50 | Solaris 9 U6 x86 51 | 52 | - /bin/sh 53 | - bash 2.05.0(1) 54 | - ksh M-11/16/88i 55 | - zsh 3.0.8 56 | 57 | Solaris 11 x86 (Nevada 77) 58 | 59 | - /bin/sh 60 | - bash 3.2.25(1) 61 | - ksh M-11/16/88i 62 | - zsh 4.3.4 63 | 64 | 65 | New Features 66 | ------------ 67 | 68 | Support added for output assert source line number as part of assert messages. 69 | 70 | Issue #2: Added assertNotEquals() assert. 71 | 72 | Provided a public ``shunit_tmpDir`` variable that can be used by unit test 73 | scripts that need automated and guaranteed cleanup. 74 | 75 | 76 | Changes and Enhancements 77 | ------------------------ 78 | 79 | Issue #3: Removed the check for unset variables as shUnit2 should not expect 80 | scripts being tested to be clean. 81 | 82 | Issue #4: Rewrote the test summary. It is now greatly simplified and much more 83 | script friendly. 84 | 85 | Issue #5: Fixed the documentation around the usage of failures. 86 | 87 | Issue #9: Added unit tests and improved documentation around the use of macros. 88 | 89 | Code updated to meet documented coding standards. 90 | 91 | Improved code reuse of ``_shunit_exit()`` and ``_shunit_fatal()`` functions. 92 | 93 | All output except shUnit2 error messages now goes to STDOUT. 94 | 95 | Converted DocBook documentation to reStructuredText for easier maintenance. 96 | 97 | 98 | Bug Fixes 99 | --------- 100 | 101 | Issue #1: Fixed bug in rap code where certain types of exit conditions did not 102 | generate the ending report. 103 | 104 | Issue #7: Fixed duplicated printing of messages passed to asserts. 105 | 106 | Fixed bugs in ``shlib_relToAbsPath()`` in ``shlib``. 107 | 108 | 109 | Deprecated Features 110 | ------------------- 111 | 112 | None. 113 | 114 | 115 | Known Bugs and Issues 116 | --------------------- 117 | 118 | Zsh requires the ``shwordsplit`` option to be set. See the documentation for 119 | examples of how to do this. 120 | 121 | Line numbers in assert messages do not work properly with BASH 2.x. 122 | 123 | The Bourne shell of Solaris, BASH 2.x, and Zsh 3.0.x do not properly catch the 124 | SIGTERM signal. As such, shell interpreter failures due to such things as 125 | unbound variables cannot be caught. (See ``shunit_test_misc.sh``) 126 | 127 | 128 | .. vim:fileencoding=latin1:ft=rst:spell:tw=80 129 | -------------------------------------------------------------------------------- /doc/RELEASE_NOTES-2.1.6.txt: -------------------------------------------------------------------------------- 1 | Release Notes for shUnit2 2.1.6 2 | =============================== 3 | 4 | This release contains bug fixes and changes. It is also the first release to 5 | support running shunit2 as a standalone program. 6 | 7 | Please read the Shell Errata section of the documentation for guidance on how 8 | to meet these requirements. 9 | 10 | See the ``CHANGES-2.1.txt`` file for a full list of changes. 11 | 12 | New Features 13 | ------------ 14 | 15 | Support for running shUnit2 as a standalone program. This makes it possible for 16 | users to execute their unit tests in a manner that is not dependent on the 17 | location an OS distribution maintainer chose to place shUnit2 in the file 18 | system. 19 | 20 | Added support for functions defined like 'function someFunction()'. 21 | 22 | Changes and Enhancements 23 | ------------------------ 24 | 25 | Renamed the public ``shunit_tmpDir`` variable to ``SHUNIT_TMPDIR`` to be more 26 | consistent with the ``TMPDIR`` variable. 27 | 28 | Bug Fixes 29 | --------- 30 | 31 | Fixed issue where shunit2 would fail on some distributions when creating a 32 | temporary directory because the **od** command was not present. 33 | 34 | Deprecated Features 35 | ------------------- 36 | 37 | None. 38 | 39 | Known Bugs and Issues 40 | --------------------- 41 | 42 | Zsh requires the ``shwordsplit`` option to be set. See the documentation for 43 | examples of how to do this. 44 | 45 | Line numbers in assert messages do not work properly with BASH 2.x. 46 | 47 | The Bourne shell of Solaris, BASH 2.x, and Zsh 3.0.x do not properly catch the 48 | SIGTERM signal. As such, shell interpreter failures due to such things as 49 | unbound variables cannot be caught. (See ``shunit_test_misc.sh``) 50 | 51 | Tested Platforms 52 | ---------------- 53 | 54 | Cygwin 1.7.9 (Windows XP SP2) 55 | 56 | - bash 4.1.10(4) 57 | - dash 0.5.6.1 58 | - ksh (sym-link to pdksh) 59 | - pdksh 5.2.14 60 | - zsh 4.3.11 61 | 62 | Linux (Ubuntu Dapper 6.06.2 LTS) 63 | 64 | - bash 3.1.17(1) 65 | - dash 0.5.3 66 | - ksh (sym-link to pdksh) 67 | - pdksh 5.2.14-99/07/13.2 68 | - zsh 4.2.5 69 | 70 | Linux (Ubuntu Hardy 8.04.4 LTS) 71 | 72 | - bash 3.2.39(1) 73 | - dash 0.5.4 74 | - ksh M-1993-12-28 75 | - pdksh 5.2.14-99/07/13.2 76 | - zsh 4.3.4 77 | 78 | Linux (Ubuntu Lucid 10.04.2 LTS) 79 | 80 | - bash 4.1.5(1) 81 | - dash 0.5.5.1 82 | - ksh JM-93t+-2009-05-01 83 | - pdksh 5.2.14-99/07/13.2 84 | - zsh 4.3.10 85 | 86 | Mac OS X 10.6.7 87 | 88 | - bash 3.2.48(1) 89 | - ksh M-1993-12-28 90 | - zsh 4.3.9 91 | 92 | Solaris 8 U7 x86 93 | 94 | - /bin/sh 95 | - bash 2.03.0(1) 96 | - ksh M-11/16/88i 97 | - zsh 3.0.6 98 | 99 | Solaris 9 U6 x86 100 | 101 | - /bin/sh 102 | - bash 2.05.0(1) 103 | - ksh M-11/16/88i 104 | - zsh 3.0.8 105 | 106 | OpenSolaris 2009.06(snv_111b) x86 107 | 108 | - /bin/sh 109 | - bash 3.2.25(1) 110 | - ksh 2008-11-04 111 | 112 | .. vim:fileencoding=latin1:ft=rst:spell:tw=80 113 | -------------------------------------------------------------------------------- /doc/RELEASE_NOTES-2.1.7.md: -------------------------------------------------------------------------------- 1 | # shUnit2 2.1.7 Release Notes 2 | 3 | https://github.com/kward/shunit2 4 | 5 | This release contains bug fixes and enhancements. It is the first release since moving to GitHub. Users can now clone the latest version at any time. 6 | 7 | See the `CHANGES-2.1.md` file for a full list of changes. 8 | 9 | 10 | ## New Features 11 | 12 | Colorized output, based on popular demand. shUnit2 output is now colorized based on the result of the asserts. 13 | 14 | 15 | ## Changes and Enhancements 16 | 17 | With the move to GitHub, the shUnit2 unit tests are run on every commit using the [Travis CI][TravisCI] continuous integration framework. Additionally, all code is run through [ShellCheck](http:/www.shellcheck.net/) on every commit. 18 | 19 | [TravisCI]: https://travis-ci.org/kward/shunit2 20 | 21 | Shell commands in shUnit2 are prefixed with '\' so that they can be stubbed in tests. 22 | 23 | 24 | ## Bug Fixes 25 | 26 | shUnit2 no longer exits with an 'OK' result if there were syntax errors due to incorrect usage of the assert commands. 27 | 28 | 29 | ## Deprecated Features 30 | 31 | None. 32 | 33 | 34 | ## Known Bugs and Issues 35 | 36 | Zsh requires the `shwordsplit` option to be set. See the documentation for examples of how to do this. 37 | 38 | Line numbers in assert messages do not work properly with BASH 2.x. 39 | 40 | The Bourne shell of Solaris, BASH 2.x, and Zsh 3.0.x do not properly catch the 41 | SIGTERM signal. As such, shell interpreter failures due to such things as 42 | unbound variables cannot be caught. (See `shunit_test_misc.sh`) 43 | 44 | 45 | ## Tested Platforms 46 | 47 | Continuous integration testing is provided by 48 | [Travis CI](https://travis-ci.org/). 49 | 50 | https://travis-ci.org/github/kward/shunit2 51 | 52 | Tested OSes: 53 | 54 | - Linux 55 | - macOS 56 | 57 | Tested shells: 58 | 59 | - /bin/sh 60 | - ash 61 | - bash 62 | - dash 63 | - ksh 64 | - pdksh 65 | - zsh 66 | 67 | -------------------------------------------------------------------------------- /doc/RELEASE_NOTES-2.1.8.md: -------------------------------------------------------------------------------- 1 | # shUnit2 2.1.8 Release Notes 2 | 3 | https://github.com/kward/shunit2 4 | 5 | This release contains bug fixes and enhancements. See the `CHANGES-2.1.md` file 6 | for a full list of changes. 7 | 8 | ## New features 9 | 10 | Users can now define a custom prefix for test function names. The prefix can be 11 | configured by defining a `SHUNIT_TEST_PREFIX` variable. 12 | 13 | ## Bug fixes 14 | 15 | Syntax errors in functions are now treated as test failures. 16 | 17 | Test now fail when `setup()` or `tearDown()` fail. 18 | 19 | ## Deprecated features 20 | 21 | None. 22 | 23 | ## Known bugs and issues 24 | 25 | Zsh requires the `shwordsplit` option to be set. See the documentation for examples of how to do this. 26 | 27 | Line numbers in assert messages do not work properly with BASH 2.x. 28 | 29 | The Bourne shell of Solaris, BASH 2.x, and Zsh 3.0.x do not properly catch the 30 | SIGTERM signal. As such, shell interpreter failures due to such things as 31 | unbound variables cannot be caught. (See `shunit_test_misc.sh`) 32 | 33 | shUnit2 does not work when the `-e` shell option is set (typically done with 34 | `set -e`). 35 | 36 | ## Tested platforms 37 | 38 | Continuous integration testing is provided by 39 | [Travis CI](https://travis-ci.org/). 40 | 41 | https://travis-ci.org/github/kward/shunit2 42 | 43 | Tested OSes: 44 | 45 | - Linux 46 | - macOS 47 | 48 | Tested shells: 49 | 50 | - /bin/sh 51 | - ash 52 | - bash 53 | - dash 54 | - ksh 55 | - pdksh 56 | - zsh 57 | -------------------------------------------------------------------------------- /doc/TODO.txt: -------------------------------------------------------------------------------- 1 | Make it possible to execute a single test by passing the name of the test on 2 | the command line 3 | 4 | Add support for '--randomize-order' so that the test order is randomized to 5 | check for dependencies (which shouldn't be there) between tests. 6 | 7 | --debug option to display point in source code (line number and such) where the 8 | problem showed up. 9 | 10 | assertTrue() just gives 'ASSERT:', nothing else :-(. others too? 11 | upd: assertNull() will give message passed, but nothing else useful :-( 12 | 13 | $Revision$ 14 | -------------------------------------------------------------------------------- /doc/contributors.md: -------------------------------------------------------------------------------- 1 | The original author of shunit2 is Kate Ward. The following people have 2 | contributed in some way or another to shunit2. 3 | 4 | - [Alex Harvey](https://github.com/alexharv074) 5 | - Bryan Larsen 6 | - [David Acacio](https://github.com/dacacioa) 7 | - Kevin Van Horn 8 | - [Maciej Bliziński](https://github.com/automatthias) 9 | - Mario Sparada 10 | - Mathias Goldau 11 | - Richard Jensen 12 | - Rob Holland 13 | - Rocky Bernstein 14 | - [rugk](https://github.com/rugk) 15 | - wood4321 (of code.google.com) 16 | -------------------------------------------------------------------------------- /doc/design_doc.txt: -------------------------------------------------------------------------------- 1 | Design Doc for shUnit 2 | 3 | shUnit is based upon JUnit. The initial ideas for the script came from the book 4 | "Pragmatic Unit Testing - In Java with JUnit" by Andrew Hunt and David Thomas. 5 | 6 | The script was written to perform unit testing for log4sh. log4sh had grown 7 | enough that it was becoming difficult to easily test and and verify that the 8 | tests passed for the many different operating systems on which it was being 9 | used. 10 | 11 | The functions in shUnit are meant to match those in JUnit as much as possible 12 | where shell allows. In the initial version, there will be no concept of 13 | exceptions (as normal POSIX shell has no concept of them) but attempts to trap 14 | problems will be done. 15 | 16 | Programatic Standards: 17 | 18 | * SHUNIT_TRUE - public global constant 19 | * __SHUNIT_SHELL_FLAGS - private global constant 20 | * __shunit_oldShellFlags - private global variable 21 | 22 | * assertEquals - public unit test function 23 | * shunit_publicFunc - public shUnit function; can be called from parent unit 24 | test script 25 | * _shunit_privateFunc - private shUnit function; should not be called from 26 | parent script. meant for internal use by shUnit 27 | 28 | * _su_myVar - variable inside a public function. prefixing with '_su_' to 29 | reduce the chances that a variable outside of shUnit will be overridden. 30 | * _su__myVar - variable inside a private function. prefixing with '_su__' to 31 | reduce the chances that a variable in a shUnit public function, or a variable 32 | outside of shUnit will be overridden. 33 | 34 | $Revision$ 35 | -------------------------------------------------------------------------------- /examples/equality_test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # file: examples/equality_test.sh 3 | 4 | testEquality() { 5 | assertEquals 1 1 6 | } 7 | 8 | # Load and run shUnit2. 9 | . ../shunit2 10 | -------------------------------------------------------------------------------- /examples/lineno_test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # file: examples/lineno_test.sh 3 | 4 | testLineNo() { 5 | # This assert will have line numbers included (e.g. "ASSERT:[123] ...") if 6 | # they are supported. 7 | echo "_ASSERT_EQUALS_ macro value: ${_ASSERT_EQUALS_}" 8 | ${_ASSERT_EQUALS_} '"not equal"' 1 2 9 | 10 | # This assert will not have line numbers included (e.g. "ASSERT: ..."). 11 | assertEquals 'not equal' 1 2 12 | } 13 | 14 | # Load and run shUnit2. 15 | . ../shunit2 16 | -------------------------------------------------------------------------------- /examples/math.inc: -------------------------------------------------------------------------------- 1 | # available as examples/math.inc 2 | 3 | add_generic() 4 | { 5 | num_a=$1 6 | num_b=$2 7 | 8 | expr $1 + $2 9 | } 10 | 11 | add_bash() 12 | { 13 | num_a=$1 14 | num_b=$2 15 | 16 | echo $(($1 + $2)) 17 | } 18 | -------------------------------------------------------------------------------- /examples/math_test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # file: examples/math_test.sh 3 | 4 | testAdding() { 5 | result=`add_generic 1 2` 6 | assertEquals \ 7 | "the result of '${result}' was wrong" \ 8 | 3 "${result}" 9 | 10 | # Disable non-generic tests. 11 | [ -z "${BASH_VERSION:-}" ] && startSkipping 12 | 13 | result=`add_bash 1 2` 14 | assertEquals \ 15 | "the result of '${result}' was wrong" \ 16 | 3 "${result}" 17 | } 18 | 19 | oneTimeSetUp() { 20 | # Load include to test. 21 | . ./math.inc 22 | } 23 | 24 | # Load and run shUnit2. 25 | . ../shunit2 26 | -------------------------------------------------------------------------------- /examples/mkdir_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # vim:et:ft=sh:sts=2:sw=2 3 | # 4 | # Copyright 2008-2019 Kate Ward. All Rights Reserved. 5 | # Released under the Apache 2.0 license. 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # shUnit2 -- Unit testing framework for Unix shell scripts. 9 | # https://github.com/kward/shunit2 10 | # 11 | # Author: kate.ward@forestent.com (Kate Ward) 12 | # 13 | # Example unit test for the mkdir command. 14 | # 15 | # There are times when an existing shell script needs to be tested. In this 16 | # example, we will test several aspects of the the mkdir command, but the 17 | # techniques could be used for any existing shell script. 18 | 19 | testMissingDirectoryCreation() { 20 | ${mkdirCmd} "${testDir}" >${stdoutF} 2>${stderrF} 21 | rtrn=$? 22 | th_assertTrueWithNoOutput ${rtrn} "${stdoutF}" "${stderrF}" 23 | 24 | assertTrue 'directory missing' "[ -d '${testDir}' ]" 25 | } 26 | 27 | testExistingDirectoryCreationFails() { 28 | # Create a directory to test against. 29 | ${mkdirCmd} "${testDir}" 30 | 31 | # Test for expected failure while trying to create directory that exists. 32 | ${mkdirCmd} "${testDir}" >${stdoutF} 2>${stderrF} 33 | rtrn=$? 34 | assertFalse 'expecting return code of 1 (false)' ${rtrn} 35 | assertNull 'unexpected output to stdout' "`cat ${stdoutF}`" 36 | assertNotNull 'expected error message to stderr' "`cat ${stderrF}`" 37 | 38 | assertTrue 'directory missing' "[ -d '${testDir}' ]" 39 | } 40 | 41 | testRecursiveDirectoryCreation() { 42 | testDir2="${testDir}/test2" 43 | 44 | ${mkdirCmd} -p "${testDir2}" >${stdoutF} 2>${stderrF} 45 | rtrn=$? 46 | th_assertTrueWithNoOutput ${rtrn} "${stdoutF}" "${stderrF}" 47 | 48 | assertTrue 'first directory missing' "[ -d '${testDir}' ]" 49 | assertTrue 'second directory missing' "[ -d '${testDir2}' ]" 50 | } 51 | 52 | th_assertTrueWithNoOutput() { 53 | th_return_=$1 54 | th_stdout_=$2 55 | th_stderr_=$3 56 | 57 | assertFalse 'unexpected output to STDOUT' "[ -s '${th_stdout_}' ]" 58 | assertFalse 'unexpected output to STDERR' "[ -s '${th_stderr_}' ]" 59 | 60 | unset th_return_ th_stdout_ th_stderr_ 61 | } 62 | 63 | oneTimeSetUp() { 64 | outputDir="${SHUNIT_TMPDIR}/output" 65 | mkdir "${outputDir}" 66 | stdoutF="${outputDir}/stdout" 67 | stderrF="${outputDir}/stderr" 68 | 69 | mkdirCmd='mkdir' # save command name in variable to make future changes easy 70 | testDir="${SHUNIT_TMPDIR}/some_test_dir" 71 | } 72 | 73 | tearDown() { 74 | rm -fr "${testDir}" 75 | } 76 | 77 | # Load and run shUnit2. 78 | [ -n "${ZSH_VERSION:-}" ] && SHUNIT_PARENT=$0 79 | . ../shunit2 80 | -------------------------------------------------------------------------------- /examples/mock_file.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # shUnit2 example for mocking files. 4 | # 5 | # This example demonstrates two different mechanisms for mocking files on the 6 | # system. The first method is preferred for testing specific aspects of a file, 7 | # and the second method is preferred when multiple tests need access to the 8 | # same mock data. 9 | # 10 | # When mocking files, the key thing of importance is providing the code under 11 | # test with the correct file to read. The best practice for writing code where 12 | # files need to be mocked is either: 13 | # - Pass the filename to be tested into a function and test that function, or 14 | # - Provide a function that returns the name of the filename to be read. 15 | # 16 | # The first case is preferred whenever possible as it allows the unit test to 17 | # be explicit about what is being tested. The second case is useful when the 18 | # first case is not achievable. 19 | # 20 | # For the second case, there are two common methods to mock the filename 21 | # returned by the function: 22 | # - Provide a special value (e.g. a mock variable) that is only available 23 | # during testing, or 24 | # - Override something (e.g. the constant) in the test script. 25 | # 26 | # The first case is preferred as it doesn't require the unit test to alter code 27 | # in any way. Yes, it means that the code itself knows that it is under test, 28 | # and it behaves slightly differently than under normal conditions, but a 29 | # visual inspection of the code by the developer should be sufficient to 30 | # validate proper functionality of such a simple function. 31 | 32 | # Treat unset variables as an error. 33 | set -u 34 | 35 | PASSWD='/etc/passwd' 36 | 37 | # Read the root UID from the passwd filename provided as the first argument. 38 | root_uid_from_passed_filename() { 39 | filename=$1 40 | root_uid "${filename}" 41 | unset filename 42 | } 43 | 44 | 45 | # Read the root UID from the passwd filename derived by call to the 46 | # passwd_filename() function. 47 | root_uid_from_derived_filename() { 48 | root_uid "$(passwd_filename)" 49 | } 50 | 51 | passwd_filename() { 52 | if [ -n "${MOCK_PASSWD:-}" ]; then 53 | echo "${MOCK_PASSWD}" # Mock file for testing. 54 | return 55 | fi 56 | echo "${PASSWD}" 57 | } 58 | 59 | 60 | # Extract the root UID. 61 | root_uid() { awk -F: 'u==$1{print $3}' u=root "$1"; } 62 | 63 | 64 | main() { 65 | echo "root_uid_from_passed_filename:" 66 | root_uid_from_passed_filename "${PASSWD}" 67 | 68 | echo 69 | 70 | echo "root_uid_from_derived_filename:" 71 | root_uid_from_derived_filename 72 | } 73 | 74 | 75 | # Execute main() if this is run in standalone mode (i.e. not in a unit test). 76 | ARGV0="$(basename "$0")" 77 | argv0="$(echo "${ARGV0}" |sed 's/_test$//;s/_test\.sh$//')" 78 | if [ "${ARGV0}" = "${argv0}" ]; then 79 | main "$@" 80 | fi 81 | -------------------------------------------------------------------------------- /examples/mock_file_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # shUnit2 example for mocking files. 4 | 5 | MOCK_PASSWD='' # This will be overridden in oneTimeSetUp(). 6 | 7 | test_root_uid_from_passed_filename() { 8 | result="$(root_uid_from_passed_filename "${MOCK_PASSWD}")" 9 | assertEquals 'unexpected root uid' '0' "${result}" 10 | } 11 | 12 | test_root_uid_from_derived_filename() { 13 | result="$(root_uid_from_derived_filename)" 14 | assertEquals 'unexpected root uid' '0' "${result}" 15 | } 16 | 17 | oneTimeSetUp() { 18 | # Provide a mock passwd file for testing. This will be cleaned up 19 | # automatically by shUnit2. 20 | MOCK_PASSWD="${SHUNIT_TMPDIR}/passwd" 21 | cat <"${MOCK_PASSWD}" 22 | nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false 23 | root:*:0:0:System Administrator:/var/root:/bin/sh 24 | daemon:*:1:1:System Services:/var/root:/usr/bin/false 25 | EOF 26 | 27 | # Load script under test. 28 | . './mock_file.sh' 29 | } 30 | 31 | # Load and run shUnit2. 32 | [ -n "${ZSH_VERSION:-}" ] && SHUNIT_PARENT=$0 33 | . ../shunit2 34 | -------------------------------------------------------------------------------- /examples/output_test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | generateOutput() { 4 | echo 'this output went to STDOUT' 5 | echo 'this output went to STDERR' >&2 6 | return 1 7 | } 8 | 9 | testGenerateOutput() { 10 | ( generateOutput >"${stdoutF}" 2>"${stderrF}" ) 11 | rtrn=$? 12 | 13 | # This test will fail because a non-zero return code was provided. 14 | assertTrue "the command exited with an error" ${rtrn} 15 | 16 | # Show the command output if the command provided a non-zero return code. 17 | [ ${rtrn} -eq 0 ] || showOutput 18 | 19 | # This test will pass because the grepped output matches. 20 | grep 'STDOUT' "${stdoutF}" >/dev/null 21 | assertTrue 'STDOUT message missing' $? 22 | 23 | # This test will fail because the grepped output doesn't match. 24 | grep 'ST[andar]DERR[or]' "${stderrF}" >/dev/null 25 | assertTrue 'STDERR message missing' $? 26 | 27 | return 0 28 | } 29 | 30 | showOutput() { 31 | # shellcheck disable=SC2166 32 | if [ -n "${stdoutF}" -a -s "${stdoutF}" ]; then 33 | echo '>>> STDOUT' >&2 34 | cat "${stdoutF}" >&2 35 | echo '<<< STDOUT' >&2 36 | fi 37 | # shellcheck disable=SC2166 38 | if [ -n "${stderrF}" -a -s "${stderrF}" ]; then 39 | echo '>>> STDERR' >&2 40 | cat "${stderrF}" >&2 41 | echo '<<< STDERR' >&2 42 | fi 43 | } 44 | 45 | oneTimeSetUp() { 46 | # Define global variables for command output. 47 | stdoutF="${SHUNIT_TMPDIR}/stdout" 48 | stderrF="${SHUNIT_TMPDIR}/stderr" 49 | } 50 | 51 | setUp() { 52 | # Truncate the output files. 53 | cp /dev/null "${stdoutF}" 54 | cp /dev/null "${stderrF}" 55 | } 56 | 57 | # Load and run shUnit2. 58 | # shellcheck disable=SC1091 59 | . ../shunit2 60 | -------------------------------------------------------------------------------- /examples/party_test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # file: examples/party_test.sh 3 | # 4 | # This test is mostly for fun. Technically, it is a bad example of a unit test 5 | # because of the temporal requirement, namely that the year be 1999. A better 6 | # test would have been to pass in both a known-bad and known-good year into a 7 | # function, and test for the expected result. 8 | 9 | testPartyLikeItIs1999() { 10 | year=`date '+%Y'` 11 | assertEquals "It's not 1999 :-(" \ 12 | '1999' "${year}" 13 | } 14 | 15 | # Load and run shUnit2. 16 | . ../shunit2 17 | -------------------------------------------------------------------------------- /examples/suite_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # file: examples/suite_test.sh 3 | # 4 | # This test demonstrates the use of suites. 5 | # 6 | # suite is a special function called by shUnit2 to setup a suite of tests. It 7 | # enables a developer to call a set of functions that contain tests without 8 | # needing to rename the functions to start with "test". 9 | # 10 | # Tests that are to be called from within `suite()` are added to the list of 11 | # executable tests by means of the `suite_addTest()` function. 12 | suite() { 13 | # Add the suite_test_one() function to the list of executable tests. 14 | suite_addTest suite_test_one 15 | 16 | # Call the suite_test_two() function, but note that the test results will not 17 | # be added to the global stats, and therefore not reported at the end of the 18 | # unit test execution. 19 | suite_test_two 20 | } 21 | 22 | suite_test_one() { 23 | assertEquals 1 1 24 | } 25 | 26 | suite_test_two() { 27 | assertNotEquals 1 2 28 | } 29 | 30 | # Load and run shUnit2. 31 | . ../shunit2 32 | -------------------------------------------------------------------------------- /init_githooks.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # 3 | # Initialize the local git hooks this repository. 4 | # https://git-scm.com/docs/githooks 5 | 6 | topLevel=$(git rev-parse --show-toplevel) 7 | if ! cd "${topLevel}"; then 8 | echo "filed to cd into topLevel directory '${topLevel}'" 9 | exit 1 10 | fi 11 | 12 | hooksDir="${topLevel}/.githooks" 13 | if ! hooksPath=$(git config core.hooksPath); then 14 | hooksPath="${topLevel}/.git/hooks" 15 | fi 16 | 17 | src="${hooksDir}/generic" 18 | echo "linking hooks..." 19 | for hook in \ 20 | applypatch-msg \ 21 | pre-applypatch \ 22 | post-applypatch \ 23 | pre-commit \ 24 | pre-merge-commit \ 25 | prepare-commit-msg \ 26 | commit-msg \ 27 | post-commit \ 28 | pre-rebase \ 29 | post-checkout \ 30 | post-merge \ 31 | pre-push \ 32 | pre-receive \ 33 | update \ 34 | post-receive \ 35 | post-update \ 36 | push-to-checkout \ 37 | pre-auto-gc \ 38 | post-rewrite \ 39 | sendemail-validate \ 40 | fsmonitor-watchman \ 41 | p4-pre-submit \ 42 | post-index-change 43 | do 44 | echo " ${hook}" 45 | dest="${hooksPath}/${hook}" 46 | ln -sf "${src}" "${dest}" 47 | done 48 | -------------------------------------------------------------------------------- /lib/shflags: -------------------------------------------------------------------------------- 1 | # vim:et:ft=sh:sts=2:sw=2 2 | # 3 | # Copyright 2008-2017 Kate Ward. All Rights Reserved. 4 | # Released under the Apache License 2.0 license. 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # shFlags -- Advanced command-line flag library for Unix shell scripts. 8 | # https://github.com/kward/shflags 9 | # 10 | # Author: kate.ward@forestent.com (Kate Ward) 11 | # 12 | # This module implements something like the gflags library available 13 | # from https://github.com/gflags/gflags. 14 | # 15 | # FLAG TYPES: This is a list of the DEFINE_*'s that you can do. All flags take 16 | # a name, default value, help-string, and optional 'short' name (one-letter 17 | # name). Some flags have other arguments, which are described with the flag. 18 | # 19 | # DEFINE_string: takes any input, and interprets it as a string. 20 | # 21 | # DEFINE_boolean: does not take any arguments. Say --myflag to set 22 | # FLAGS_myflag to true, or --nomyflag to set FLAGS_myflag to false. For short 23 | # flags, passing the flag on the command-line negates the default value, i.e. 24 | # if the default is true, passing the flag sets the value to false. 25 | # 26 | # DEFINE_float: takes an input and interprets it as a floating point number. As 27 | # shell does not support floats per-se, the input is merely validated as 28 | # being a valid floating point value. 29 | # 30 | # DEFINE_integer: takes an input and interprets it as an integer. 31 | # 32 | # SPECIAL FLAGS: There are a few flags that have special meaning: 33 | # --help (or -?) prints a list of all the flags in a human-readable fashion 34 | # --flagfile=foo read flags from foo. (not implemented yet) 35 | # -- as in getopt(), terminates flag-processing 36 | # 37 | # EXAMPLE USAGE: 38 | # 39 | # -- begin hello.sh -- 40 | # #! /bin/sh 41 | # . ./shflags 42 | # DEFINE_string name 'world' "somebody's name" n 43 | # FLAGS "$@" || exit $? 44 | # eval set -- "${FLAGS_ARGV}" 45 | # echo "Hello, ${FLAGS_name}." 46 | # -- end hello.sh -- 47 | # 48 | # $ ./hello.sh -n Kate 49 | # Hello, Kate. 50 | # 51 | # CUSTOMIZABLE BEHAVIOR: 52 | # 53 | # A script can override the default 'getopt' command by providing the path to 54 | # an alternate implementation by defining the FLAGS_GETOPT_CMD variable. 55 | # 56 | # NOTES: 57 | # 58 | # * Not all systems include a getopt version that supports long flags. On these 59 | # systems, only short flags are recognized. 60 | 61 | #============================================================================== 62 | # shFlags 63 | # 64 | # Shared attributes: 65 | # flags_error: last error message 66 | # flags_output: last function output (rarely valid) 67 | # flags_return: last return value 68 | # 69 | # __flags_longNames: list of long names for all flags 70 | # __flags_shortNames: list of short names for all flags 71 | # __flags_boolNames: list of boolean flag names 72 | # 73 | # __flags_opts: options parsed by getopt 74 | # 75 | # Per-flag attributes: 76 | # FLAGS_: contains value of flag named 'flag_name' 77 | # __flags__default: the default flag value 78 | # __flags__help: the flag help string 79 | # __flags__short: the flag short name 80 | # __flags__type: the flag type 81 | # 82 | # Notes: 83 | # - lists of strings are space separated, and a null value is the '~' char. 84 | # 85 | ### ShellCheck (http://www.shellcheck.net/) 86 | # $() are not fully portable (POSIX != portable). 87 | # shellcheck disable=SC2006 88 | # [ p -a q ] are well defined enough (vs [ p ] && [ q ]). 89 | # shellcheck disable=SC2166 90 | 91 | # Return if FLAGS already loaded. 92 | [ -n "${FLAGS_VERSION:-}" ] && return 0 93 | FLAGS_VERSION='1.2.3pre' 94 | 95 | # Return values that scripts can use. 96 | FLAGS_TRUE=0 97 | FLAGS_FALSE=1 98 | FLAGS_ERROR=2 99 | 100 | # Logging levels. 101 | FLAGS_LEVEL_DEBUG=0 102 | FLAGS_LEVEL_INFO=1 103 | FLAGS_LEVEL_WARN=2 104 | FLAGS_LEVEL_ERROR=3 105 | FLAGS_LEVEL_FATAL=4 106 | __FLAGS_LEVEL_DEFAULT=${FLAGS_LEVEL_WARN} 107 | 108 | # Determine some reasonable command defaults. 109 | __FLAGS_EXPR_CMD='expr --' 110 | __FLAGS_UNAME_S=`uname -s` 111 | if [ "${__FLAGS_UNAME_S}" = 'BSD' ]; then 112 | __FLAGS_EXPR_CMD='gexpr --' 113 | else 114 | _flags_output_=`${__FLAGS_EXPR_CMD} 2>&1` 115 | if [ $? -eq ${FLAGS_TRUE} -a "${_flags_output_}" = '--' ]; then 116 | # We are likely running inside BusyBox. 117 | __FLAGS_EXPR_CMD='expr' 118 | fi 119 | unset _flags_output_ 120 | fi 121 | 122 | # Commands a user can override if desired. 123 | FLAGS_EXPR_CMD=${FLAGS_EXPR_CMD:-${__FLAGS_EXPR_CMD}} 124 | FLAGS_GETOPT_CMD=${FLAGS_GETOPT_CMD:-getopt} 125 | 126 | # Specific shell checks. 127 | if [ -n "${ZSH_VERSION:-}" ]; then 128 | setopt |grep "^shwordsplit$" >/dev/null 129 | if [ $? -ne ${FLAGS_TRUE} ]; then 130 | _flags_fatal 'zsh shwordsplit option is required for proper zsh operation' 131 | fi 132 | if [ -z "${FLAGS_PARENT:-}" ]; then 133 | _flags_fatal "zsh does not pass \$0 through properly. please declare' \ 134 | \"FLAGS_PARENT=\$0\" before calling shFlags" 135 | fi 136 | fi 137 | 138 | # Can we use built-ins? 139 | ( echo "${FLAGS_TRUE#0}"; ) >/dev/null 2>&1 140 | if [ $? -eq ${FLAGS_TRUE} ]; then 141 | __FLAGS_USE_BUILTIN=${FLAGS_TRUE} 142 | else 143 | __FLAGS_USE_BUILTIN=${FLAGS_FALSE} 144 | fi 145 | 146 | 147 | # 148 | # Constants. 149 | # 150 | 151 | # Reserved flag names. 152 | __FLAGS_RESERVED_LIST=' ARGC ARGV ERROR FALSE GETOPT_CMD HELP PARENT TRUE ' 153 | __FLAGS_RESERVED_LIST="${__FLAGS_RESERVED_LIST} VERSION " 154 | 155 | # Determined getopt version (standard or enhanced). 156 | __FLAGS_GETOPT_VERS_STD=0 157 | __FLAGS_GETOPT_VERS_ENH=1 158 | 159 | # shellcheck disable=SC2120 160 | _flags_getopt_vers() { 161 | _flags_getopt_cmd_=${1:-${FLAGS_GETOPT_CMD}} 162 | case "`${_flags_getopt_cmd_} -lfoo '' --foo 2>&1`" in 163 | ' -- --foo') echo ${__FLAGS_GETOPT_VERS_STD} ;; 164 | ' --foo --') echo ${__FLAGS_GETOPT_VERS_ENH} ;; 165 | # Unrecognized output. Assuming standard getopt version. 166 | *) echo ${__FLAGS_GETOPT_VERS_STD} ;; 167 | esac 168 | unset _flags_getopt_cmd_ 169 | } 170 | # shellcheck disable=SC2119 171 | __FLAGS_GETOPT_VERS=`_flags_getopt_vers` 172 | 173 | # getopt optstring lengths 174 | __FLAGS_OPTSTR_SHORT=0 175 | __FLAGS_OPTSTR_LONG=1 176 | 177 | __FLAGS_NULL='~' 178 | 179 | # Flag info strings. 180 | __FLAGS_INFO_DEFAULT='default' 181 | __FLAGS_INFO_HELP='help' 182 | __FLAGS_INFO_SHORT='short' 183 | __FLAGS_INFO_TYPE='type' 184 | 185 | # Flag lengths. 186 | __FLAGS_LEN_SHORT=0 187 | __FLAGS_LEN_LONG=1 188 | 189 | # Flag types. 190 | __FLAGS_TYPE_NONE=0 191 | __FLAGS_TYPE_BOOLEAN=1 192 | __FLAGS_TYPE_FLOAT=2 193 | __FLAGS_TYPE_INTEGER=3 194 | __FLAGS_TYPE_STRING=4 195 | 196 | # Set the constants readonly. 197 | __flags_constants=`set |awk -F= '/^FLAGS_/ || /^__FLAGS_/ {print $1}'` 198 | for __flags_const in ${__flags_constants}; do 199 | # Skip certain flags. 200 | case ${__flags_const} in 201 | FLAGS_HELP) continue ;; 202 | FLAGS_PARENT) continue ;; 203 | esac 204 | # Set flag readonly. 205 | if [ -z "${ZSH_VERSION:-}" ]; then 206 | readonly "${__flags_const}" 207 | continue 208 | fi 209 | case ${ZSH_VERSION} in 210 | [123].*) readonly "${__flags_const}" ;; 211 | *) readonly -g "${__flags_const}" ;; # Declare readonly constants globally. 212 | esac 213 | done 214 | unset __flags_const __flags_constants 215 | 216 | # 217 | # Internal variables. 218 | # 219 | 220 | # Space separated lists. 221 | __flags_boolNames=' ' # Boolean flag names. 222 | __flags_longNames=' ' # Long flag names. 223 | __flags_shortNames=' ' # Short flag names. 224 | __flags_definedNames=' ' # Defined flag names (used for validation). 225 | 226 | __flags_columns='' # Screen width in columns. 227 | __flags_level=0 # Default logging level. 228 | __flags_opts='' # Temporary storage for parsed getopt flags. 229 | 230 | #------------------------------------------------------------------------------ 231 | # Private functions. 232 | # 233 | 234 | # Logging functions. 235 | _flags_debug() { 236 | [ ${__flags_level} -le ${FLAGS_LEVEL_DEBUG} ] || return 237 | echo "flags:DEBUG $*" >&2 238 | } 239 | _flags_info() { 240 | [ ${__flags_level} -le ${FLAGS_LEVEL_INFO} ] || return 241 | echo "flags:INFO $*" >&2 242 | } 243 | _flags_warn() { 244 | [ ${__flags_level} -le ${FLAGS_LEVEL_WARN} ] || return 245 | echo "flags:WARN $*" >&2 246 | } 247 | _flags_error() { 248 | [ ${__flags_level} -le ${FLAGS_LEVEL_ERROR} ] || return 249 | echo "flags:ERROR $*" >&2 250 | } 251 | _flags_fatal() { 252 | [ ${__flags_level} -le ${FLAGS_LEVEL_FATAL} ] || return 253 | echo "flags:FATAL $*" >&2 254 | exit ${FLAGS_ERROR} 255 | } 256 | 257 | # Get the logging level. 258 | flags_loggingLevel() { echo ${__flags_level}; } 259 | 260 | # Set the logging level. 261 | # 262 | # Args: 263 | # _flags_level_: integer: new logging level 264 | # Returns: 265 | # nothing 266 | flags_setLoggingLevel() { 267 | [ $# -ne 1 ] && _flags_fatal "flags_setLevel(): logging level missing" 268 | _flags_level_=$1 269 | [ "${_flags_level_}" -ge "${FLAGS_LEVEL_DEBUG}" \ 270 | -a "${_flags_level_}" -le "${FLAGS_LEVEL_FATAL}" ] \ 271 | || _flags_fatal "Invalid logging level '${_flags_level_}' specified." 272 | __flags_level=$1 273 | unset _flags_level_ 274 | } 275 | 276 | # Define a flag. 277 | # 278 | # Calling this function will define the following info variables for the 279 | # specified flag: 280 | # FLAGS_flagname - the name for this flag (based upon the long flag name) 281 | # __flags__default - the default value 282 | # __flags_flagname_help - the help string 283 | # __flags_flagname_short - the single letter alias 284 | # __flags_flagname_type - the type of flag (one of __FLAGS_TYPE_*) 285 | # 286 | # Args: 287 | # _flags_type_: integer: internal type of flag (__FLAGS_TYPE_*) 288 | # _flags_name_: string: long flag name 289 | # _flags_default_: default flag value 290 | # _flags_help_: string: help string 291 | # _flags_short_: string: (optional) short flag name 292 | # Returns: 293 | # integer: success of operation, or error 294 | _flags_define() { 295 | if [ $# -lt 4 ]; then 296 | flags_error='DEFINE error: too few arguments' 297 | flags_return=${FLAGS_ERROR} 298 | _flags_error "${flags_error}" 299 | return ${flags_return} 300 | fi 301 | 302 | _flags_type_=$1 303 | _flags_name_=$2 304 | _flags_default_=$3 305 | _flags_help_=${4:-§} # Special value '§' indicates no help string provided. 306 | _flags_short_=${5:-${__FLAGS_NULL}} 307 | 308 | _flags_debug "type:${_flags_type_} name:${_flags_name_}" \ 309 | "default:'${_flags_default_}' help:'${_flags_help_}'" \ 310 | "short:${_flags_short_}" 311 | 312 | _flags_return_=${FLAGS_TRUE} 313 | _flags_usName_="`_flags_underscoreName "${_flags_name_}"`" 314 | 315 | # Check whether the flag name is reserved. 316 | _flags_itemInList "${_flags_usName_}" "${__FLAGS_RESERVED_LIST}" 317 | if [ $? -eq ${FLAGS_TRUE} ]; then 318 | flags_error="flag name (${_flags_name_}) is reserved" 319 | _flags_return_=${FLAGS_ERROR} 320 | fi 321 | 322 | # Require short option for getopt that don't support long options. 323 | if [ ${_flags_return_} -eq ${FLAGS_TRUE} \ 324 | -a "${__FLAGS_GETOPT_VERS}" -ne "${__FLAGS_GETOPT_VERS_ENH}" \ 325 | -a "${_flags_short_}" = "${__FLAGS_NULL}" ] 326 | then 327 | flags_error="short flag required for (${_flags_name_}) on this platform" 328 | _flags_return_=${FLAGS_ERROR} 329 | fi 330 | 331 | # Check for existing long name definition. 332 | if [ ${_flags_return_} -eq ${FLAGS_TRUE} ]; then 333 | if _flags_itemInList "${_flags_usName_}" "${__flags_definedNames}"; then 334 | flags_error="definition for ([no]${_flags_name_}) already exists" 335 | _flags_warn "${flags_error}" 336 | _flags_return_=${FLAGS_FALSE} 337 | fi 338 | fi 339 | 340 | # Check for existing short name definition. 341 | if [ ${_flags_return_} -eq ${FLAGS_TRUE} \ 342 | -a "${_flags_short_}" != "${__FLAGS_NULL}" ] 343 | then 344 | if _flags_itemInList "${_flags_short_}" "${__flags_shortNames}"; then 345 | flags_error="flag short name (${_flags_short_}) already defined" 346 | _flags_warn "${flags_error}" 347 | _flags_return_=${FLAGS_FALSE} 348 | fi 349 | fi 350 | 351 | # Handle default value. Note, on several occasions the 'if' portion of an 352 | # if/then/else contains just a ':' which does nothing. A binary reversal via 353 | # '!' is not done because it does not work on all shells. 354 | if [ ${_flags_return_} -eq ${FLAGS_TRUE} ]; then 355 | case ${_flags_type_} in 356 | ${__FLAGS_TYPE_BOOLEAN}) 357 | if _flags_validBool "${_flags_default_}"; then 358 | case ${_flags_default_} in 359 | true|t|0) _flags_default_=${FLAGS_TRUE} ;; 360 | false|f|1) _flags_default_=${FLAGS_FALSE} ;; 361 | esac 362 | else 363 | flags_error="invalid default flag value '${_flags_default_}'" 364 | _flags_return_=${FLAGS_ERROR} 365 | fi 366 | ;; 367 | 368 | ${__FLAGS_TYPE_FLOAT}) 369 | if _flags_validFloat "${_flags_default_}"; then 370 | : 371 | else 372 | flags_error="invalid default flag value '${_flags_default_}'" 373 | _flags_return_=${FLAGS_ERROR} 374 | fi 375 | ;; 376 | 377 | ${__FLAGS_TYPE_INTEGER}) 378 | if _flags_validInt "${_flags_default_}"; then 379 | : 380 | else 381 | flags_error="invalid default flag value '${_flags_default_}'" 382 | _flags_return_=${FLAGS_ERROR} 383 | fi 384 | ;; 385 | 386 | ${__FLAGS_TYPE_STRING}) ;; # Everything in shell is a valid string. 387 | 388 | *) 389 | flags_error="unrecognized flag type '${_flags_type_}'" 390 | _flags_return_=${FLAGS_ERROR} 391 | ;; 392 | esac 393 | fi 394 | 395 | if [ ${_flags_return_} -eq ${FLAGS_TRUE} ]; then 396 | # Store flag information. 397 | eval "FLAGS_${_flags_usName_}='${_flags_default_}'" 398 | eval "__flags_${_flags_usName_}_${__FLAGS_INFO_TYPE}=${_flags_type_}" 399 | eval "__flags_${_flags_usName_}_${__FLAGS_INFO_DEFAULT}=\ 400 | \"${_flags_default_}\"" 401 | eval "__flags_${_flags_usName_}_${__FLAGS_INFO_HELP}=\"${_flags_help_}\"" 402 | eval "__flags_${_flags_usName_}_${__FLAGS_INFO_SHORT}='${_flags_short_}'" 403 | 404 | # append flag names to name lists 405 | __flags_shortNames="${__flags_shortNames}${_flags_short_} " 406 | __flags_longNames="${__flags_longNames}${_flags_name_} " 407 | [ "${_flags_type_}" -eq "${__FLAGS_TYPE_BOOLEAN}" ] && \ 408 | __flags_boolNames="${__flags_boolNames}no${_flags_name_} " 409 | 410 | # Append flag names to defined names for later validation checks. 411 | __flags_definedNames="${__flags_definedNames}${_flags_usName_} " 412 | [ "${_flags_type_}" -eq "${__FLAGS_TYPE_BOOLEAN}" ] && \ 413 | __flags_definedNames="${__flags_definedNames}no${_flags_usName_} " 414 | fi 415 | 416 | flags_return=${_flags_return_} 417 | unset _flags_default_ _flags_help_ _flags_name_ _flags_return_ \ 418 | _flags_short_ _flags_type_ _flags_usName_ 419 | [ ${flags_return} -eq ${FLAGS_ERROR} ] && _flags_error "${flags_error}" 420 | return ${flags_return} 421 | } 422 | 423 | # Underscore a flag name by replacing dashes with underscores. 424 | # 425 | # Args: 426 | # unnamed: string: log flag name 427 | # Output: 428 | # string: underscored name 429 | _flags_underscoreName() { 430 | echo "$1" |tr '-' '_' 431 | } 432 | 433 | # Return valid getopt options using currently defined list of long options. 434 | # 435 | # This function builds a proper getopt option string for short (and long) 436 | # options, using the current list of long options for reference. 437 | # 438 | # Args: 439 | # _flags_optStr: integer: option string type (__FLAGS_OPTSTR_*) 440 | # Output: 441 | # string: generated option string for getopt 442 | # Returns: 443 | # boolean: success of operation (always returns True) 444 | _flags_genOptStr() { 445 | _flags_optStrType_=$1 446 | 447 | _flags_opts_='' 448 | 449 | for _flags_name_ in ${__flags_longNames}; do 450 | _flags_usName_="`_flags_underscoreName "${_flags_name_}"`" 451 | _flags_type_="`_flags_getFlagInfo "${_flags_usName_}" "${__FLAGS_INFO_TYPE}"`" 452 | [ $? -eq ${FLAGS_TRUE} ] || _flags_fatal 'call to _flags_type_ failed' 453 | case ${_flags_optStrType_} in 454 | ${__FLAGS_OPTSTR_SHORT}) 455 | _flags_shortName_="`_flags_getFlagInfo \ 456 | "${_flags_usName_}" "${__FLAGS_INFO_SHORT}"`" 457 | if [ "${_flags_shortName_}" != "${__FLAGS_NULL}" ]; then 458 | _flags_opts_="${_flags_opts_}${_flags_shortName_}" 459 | # getopt needs a trailing ':' to indicate a required argument. 460 | [ "${_flags_type_}" -ne "${__FLAGS_TYPE_BOOLEAN}" ] && \ 461 | _flags_opts_="${_flags_opts_}:" 462 | fi 463 | ;; 464 | 465 | ${__FLAGS_OPTSTR_LONG}) 466 | _flags_opts_="${_flags_opts_:+${_flags_opts_},}${_flags_name_}" 467 | # getopt needs a trailing ':' to indicate a required argument 468 | [ "${_flags_type_}" -ne "${__FLAGS_TYPE_BOOLEAN}" ] && \ 469 | _flags_opts_="${_flags_opts_}:" 470 | ;; 471 | esac 472 | done 473 | 474 | echo "${_flags_opts_}" 475 | unset _flags_name_ _flags_opts_ _flags_optStrType_ _flags_shortName_ \ 476 | _flags_type_ _flags_usName_ 477 | return ${FLAGS_TRUE} 478 | } 479 | 480 | # Returns flag details based on a flag name and flag info. 481 | # 482 | # Args: 483 | # string: underscored flag name 484 | # string: flag info (see the _flags_define function for valid info types) 485 | # Output: 486 | # string: value of dereferenced flag variable 487 | # Returns: 488 | # integer: one of FLAGS_{TRUE|FALSE|ERROR} 489 | _flags_getFlagInfo() { 490 | # Note: adding gFI to variable names to prevent naming conflicts with calling 491 | # functions 492 | _flags_gFI_usName_=$1 493 | _flags_gFI_info_=$2 494 | 495 | # Example: given argument usName (underscored flag name) of 'my_flag', and 496 | # argument info of 'help', set the _flags_infoValue_ variable to the value of 497 | # ${__flags_my_flag_help}, and see if it is non-empty. 498 | _flags_infoVar_="__flags_${_flags_gFI_usName_}_${_flags_gFI_info_}" 499 | _flags_strToEval_="_flags_infoValue_=\"\${${_flags_infoVar_}:-}\"" 500 | eval "${_flags_strToEval_}" 501 | if [ -n "${_flags_infoValue_}" ]; then 502 | # Special value '§' indicates no help string provided. 503 | [ "${_flags_gFI_info_}" = ${__FLAGS_INFO_HELP} \ 504 | -a "${_flags_infoValue_}" = '§' ] && _flags_infoValue_='' 505 | flags_return=${FLAGS_TRUE} 506 | else 507 | # See if the _flags_gFI_usName_ variable is a string as strings can be 508 | # empty... 509 | # Note: the DRY principle would say to have this function call itself for 510 | # the next three lines, but doing so results in an infinite loop as an 511 | # invalid _flags_name_ will also not have the associated _type variable. 512 | # Because it doesn't (it will evaluate to an empty string) the logic will 513 | # try to find the _type variable of the _type variable, and so on. Not so 514 | # good ;-) 515 | # 516 | # Example cont.: set the _flags_typeValue_ variable to the value of 517 | # ${__flags_my_flag_type}, and see if it equals '4'. 518 | _flags_typeVar_="__flags_${_flags_gFI_usName_}_${__FLAGS_INFO_TYPE}" 519 | _flags_strToEval_="_flags_typeValue_=\"\${${_flags_typeVar_}:-}\"" 520 | eval "${_flags_strToEval_}" 521 | # shellcheck disable=SC2154 522 | if [ "${_flags_typeValue_}" = "${__FLAGS_TYPE_STRING}" ]; then 523 | flags_return=${FLAGS_TRUE} 524 | else 525 | flags_return=${FLAGS_ERROR} 526 | flags_error="missing flag info variable (${_flags_infoVar_})" 527 | fi 528 | fi 529 | 530 | echo "${_flags_infoValue_}" 531 | unset _flags_gFI_usName_ _flags_gfI_info_ _flags_infoValue_ _flags_infoVar_ \ 532 | _flags_strToEval_ _flags_typeValue_ _flags_typeVar_ 533 | [ ${flags_return} -eq ${FLAGS_ERROR} ] && _flags_error "${flags_error}" 534 | return ${flags_return} 535 | } 536 | 537 | # Check for presence of item in a list. 538 | # 539 | # Passed a string (e.g. 'abc'), this function will determine if the string is 540 | # present in the list of strings (e.g. ' foo bar abc '). 541 | # 542 | # Args: 543 | # _flags_str_: string: string to search for in a list of strings 544 | # unnamed: list: list of strings 545 | # Returns: 546 | # boolean: true if item is in the list 547 | _flags_itemInList() { 548 | _flags_str_=$1 549 | shift 550 | 551 | case " ${*:-} " in 552 | *\ ${_flags_str_}\ *) flags_return=${FLAGS_TRUE} ;; 553 | *) flags_return=${FLAGS_FALSE} ;; 554 | esac 555 | 556 | unset _flags_str_ 557 | return ${flags_return} 558 | } 559 | 560 | # Returns the width of the current screen. 561 | # 562 | # Output: 563 | # integer: width in columns of the current screen. 564 | _flags_columns() { 565 | if [ -z "${__flags_columns}" ]; then 566 | if eval stty size >/dev/null 2>&1; then 567 | # stty size worked :-) 568 | # shellcheck disable=SC2046 569 | set -- `stty size` 570 | __flags_columns="${2:-}" 571 | fi 572 | fi 573 | if [ -z "${__flags_columns}" ]; then 574 | if eval tput cols >/dev/null 2>&1; then 575 | # shellcheck disable=SC2046 576 | set -- `tput cols` 577 | __flags_columns="${1:-}" 578 | fi 579 | fi 580 | echo "${__flags_columns:-80}" 581 | } 582 | 583 | # Validate a boolean. 584 | # 585 | # Args: 586 | # _flags__bool: boolean: value to validate 587 | # Returns: 588 | # bool: true if the value is a valid boolean 589 | _flags_validBool() { 590 | _flags_bool_=$1 591 | 592 | flags_return=${FLAGS_TRUE} 593 | case "${_flags_bool_}" in 594 | true|t|0) ;; 595 | false|f|1) ;; 596 | *) flags_return=${FLAGS_FALSE} ;; 597 | esac 598 | 599 | unset _flags_bool_ 600 | return ${flags_return} 601 | } 602 | 603 | # Validate a float. 604 | # 605 | # Args: 606 | # _flags_float_: float: value to validate 607 | # Returns: 608 | # bool: true if the value is a valid integer 609 | _flags_validFloat() { 610 | flags_return=${FLAGS_FALSE} 611 | [ -n "$1" ] || return ${flags_return} 612 | _flags_float_=$1 613 | 614 | if _flags_validInt "${_flags_float_}"; then 615 | flags_return=${FLAGS_TRUE} 616 | elif _flags_useBuiltin; then 617 | _flags_float_whole_=${_flags_float_%.*} 618 | _flags_float_fraction_=${_flags_float_#*.} 619 | if _flags_validInt "${_flags_float_whole_:-0}" -a \ 620 | _flags_validInt "${_flags_float_fraction_}"; then 621 | flags_return=${FLAGS_TRUE} 622 | fi 623 | unset _flags_float_whole_ _flags_float_fraction_ 624 | else 625 | flags_return=${FLAGS_TRUE} 626 | case ${_flags_float_} in 627 | -*) # Negative floats. 628 | _flags_test_=`${FLAGS_EXPR_CMD} "${_flags_float_}" :\ 629 | '\(-[0-9]*\.[0-9]*\)'` 630 | ;; 631 | *) # Positive floats. 632 | _flags_test_=`${FLAGS_EXPR_CMD} "${_flags_float_}" :\ 633 | '\([0-9]*\.[0-9]*\)'` 634 | ;; 635 | esac 636 | [ "${_flags_test_}" != "${_flags_float_}" ] && flags_return=${FLAGS_FALSE} 637 | unset _flags_test_ 638 | fi 639 | 640 | unset _flags_float_ _flags_float_whole_ _flags_float_fraction_ 641 | return ${flags_return} 642 | } 643 | 644 | # Validate an integer. 645 | # 646 | # Args: 647 | # _flags_int_: integer: value to validate 648 | # Returns: 649 | # bool: true if the value is a valid integer 650 | _flags_validInt() { 651 | flags_return=${FLAGS_FALSE} 652 | [ -n "$1" ] || return ${flags_return} 653 | _flags_int_=$1 654 | 655 | case ${_flags_int_} in 656 | -*.*) ;; # Ignore negative floats (we'll invalidate them later). 657 | -*) # Strip possible leading negative sign. 658 | if _flags_useBuiltin; then 659 | _flags_int_=${_flags_int_#-} 660 | else 661 | _flags_int_=`${FLAGS_EXPR_CMD} "${_flags_int_}" : '-\([0-9][0-9]*\)'` 662 | fi 663 | ;; 664 | esac 665 | 666 | case ${_flags_int_} in 667 | *[!0-9]*) flags_return=${FLAGS_FALSE} ;; 668 | *) flags_return=${FLAGS_TRUE} ;; 669 | esac 670 | 671 | unset _flags_int_ 672 | return ${flags_return} 673 | } 674 | 675 | # Parse command-line options using the standard getopt. 676 | # 677 | # Note: the flag options are passed around in the global __flags_opts so that 678 | # the formatting is not lost due to shell parsing and such. 679 | # 680 | # Args: 681 | # @: varies: command-line options to parse 682 | # Returns: 683 | # integer: a FLAGS success condition 684 | _flags_getoptStandard() { 685 | flags_return=${FLAGS_TRUE} 686 | _flags_shortOpts_=`_flags_genOptStr ${__FLAGS_OPTSTR_SHORT}` 687 | 688 | # Check for spaces in passed options. 689 | for _flags_opt_ in "$@"; do 690 | # Note: the silliness with the x's is purely for ksh93 on Ubuntu 6.06. 691 | _flags_match_=`echo "x${_flags_opt_}x" |sed 's/ //g'` 692 | if [ "${_flags_match_}" != "x${_flags_opt_}x" ]; then 693 | flags_error='the available getopt does not support spaces in options' 694 | flags_return=${FLAGS_ERROR} 695 | break 696 | fi 697 | done 698 | 699 | if [ ${flags_return} -eq ${FLAGS_TRUE} ]; then 700 | __flags_opts=`getopt "${_flags_shortOpts_}" "$@" 2>&1` 701 | _flags_rtrn_=$? 702 | if [ ${_flags_rtrn_} -ne ${FLAGS_TRUE} ]; then 703 | _flags_warn "${__flags_opts}" 704 | flags_error='unable to parse provided options with getopt.' 705 | flags_return=${FLAGS_ERROR} 706 | fi 707 | fi 708 | 709 | unset _flags_match_ _flags_opt_ _flags_rtrn_ _flags_shortOpts_ 710 | return ${flags_return} 711 | } 712 | 713 | # Parse command-line options using the enhanced getopt. 714 | # 715 | # Note: the flag options are passed around in the global __flags_opts so that 716 | # the formatting is not lost due to shell parsing and such. 717 | # 718 | # Args: 719 | # @: varies: command-line options to parse 720 | # Returns: 721 | # integer: a FLAGS success condition 722 | _flags_getoptEnhanced() { 723 | flags_return=${FLAGS_TRUE} 724 | _flags_shortOpts_=`_flags_genOptStr ${__FLAGS_OPTSTR_SHORT}` 725 | _flags_boolOpts_=`echo "${__flags_boolNames}" \ 726 | |sed 's/^ *//;s/ *$//;s/ /,/g'` 727 | _flags_longOpts_=`_flags_genOptStr ${__FLAGS_OPTSTR_LONG}` 728 | 729 | __flags_opts=`${FLAGS_GETOPT_CMD} \ 730 | -o "${_flags_shortOpts_}" \ 731 | -l "${_flags_longOpts_},${_flags_boolOpts_}" \ 732 | -- "$@" 2>&1` 733 | _flags_rtrn_=$? 734 | if [ ${_flags_rtrn_} -ne ${FLAGS_TRUE} ]; then 735 | _flags_warn "${__flags_opts}" 736 | flags_error='unable to parse provided options with getopt.' 737 | flags_return=${FLAGS_ERROR} 738 | fi 739 | 740 | unset _flags_boolOpts_ _flags_longOpts_ _flags_rtrn_ _flags_shortOpts_ 741 | return ${flags_return} 742 | } 743 | 744 | # Dynamically parse a getopt result and set appropriate variables. 745 | # 746 | # This function does the actual conversion of getopt output and runs it through 747 | # the standard case structure for parsing. The case structure is actually quite 748 | # dynamic to support any number of flags. 749 | # 750 | # Args: 751 | # argc: int: original command-line argument count 752 | # @: varies: output from getopt parsing 753 | # Returns: 754 | # integer: a FLAGS success condition 755 | _flags_parseGetopt() { 756 | _flags_argc_=$1 757 | shift 758 | 759 | flags_return=${FLAGS_TRUE} 760 | 761 | if [ "${__FLAGS_GETOPT_VERS}" -ne "${__FLAGS_GETOPT_VERS_ENH}" ]; then 762 | # The @$ must be unquoted as it needs to be re-split. 763 | # shellcheck disable=SC2068 764 | set -- $@ 765 | else 766 | # Note the quotes around the `$@' -- they are essential! 767 | eval set -- "$@" 768 | fi 769 | 770 | # Provide user with the number of arguments to shift by later. 771 | # NOTE: the FLAGS_ARGC variable is obsolete as of 1.0.3 because it does not 772 | # properly give user access to non-flag arguments mixed in between flag 773 | # arguments. Its usage was replaced by FLAGS_ARGV, and it is being kept only 774 | # for backwards compatibility reasons. 775 | FLAGS_ARGC=`_flags_math "$# - 1 - ${_flags_argc_}"` 776 | export FLAGS_ARGC 777 | 778 | # Handle options. note options with values must do an additional shift. 779 | while true; do 780 | _flags_opt_=$1 781 | _flags_arg_=${2:-} 782 | _flags_type_=${__FLAGS_TYPE_NONE} 783 | _flags_name_='' 784 | 785 | # Determine long flag name. 786 | case "${_flags_opt_}" in 787 | --) shift; break ;; # Discontinue option parsing. 788 | 789 | --*) # Long option. 790 | if _flags_useBuiltin; then 791 | _flags_opt_=${_flags_opt_#*--} 792 | else 793 | _flags_opt_=`${FLAGS_EXPR_CMD} "${_flags_opt_}" : '--\(.*\)'` 794 | fi 795 | _flags_len_=${__FLAGS_LEN_LONG} 796 | if _flags_itemInList "${_flags_opt_}" "${__flags_longNames}"; then 797 | _flags_name_=${_flags_opt_} 798 | else 799 | # Check for negated long boolean version. 800 | if _flags_itemInList "${_flags_opt_}" "${__flags_boolNames}"; then 801 | if _flags_useBuiltin; then 802 | _flags_name_=${_flags_opt_#*no} 803 | else 804 | _flags_name_=`${FLAGS_EXPR_CMD} "${_flags_opt_}" : 'no\(.*\)'` 805 | fi 806 | _flags_type_=${__FLAGS_TYPE_BOOLEAN} 807 | _flags_arg_=${__FLAGS_NULL} 808 | fi 809 | fi 810 | ;; 811 | 812 | -*) # Short option. 813 | if _flags_useBuiltin; then 814 | _flags_opt_=${_flags_opt_#*-} 815 | else 816 | _flags_opt_=`${FLAGS_EXPR_CMD} "${_flags_opt_}" : '-\(.*\)'` 817 | fi 818 | _flags_len_=${__FLAGS_LEN_SHORT} 819 | if _flags_itemInList "${_flags_opt_}" "${__flags_shortNames}"; then 820 | # Yes. Match short name to long name. Note purposeful off-by-one 821 | # (too high) with awk calculations. 822 | _flags_pos_=`echo "${__flags_shortNames}" \ 823 | |awk 'BEGIN{RS=" ";rn=0}$0==e{rn=NR}END{print rn}' \ 824 | e="${_flags_opt_}"` 825 | _flags_name_=`echo "${__flags_longNames}" \ 826 | |awk 'BEGIN{RS=" "}rn==NR{print $0}' rn="${_flags_pos_}"` 827 | fi 828 | ;; 829 | esac 830 | 831 | # Die if the flag was unrecognized. 832 | if [ -z "${_flags_name_}" ]; then 833 | flags_error="unrecognized option (${_flags_opt_})" 834 | flags_return=${FLAGS_ERROR} 835 | break 836 | fi 837 | 838 | # Set new flag value. 839 | _flags_usName_=`_flags_underscoreName "${_flags_name_}"` 840 | [ ${_flags_type_} -eq ${__FLAGS_TYPE_NONE} ] && \ 841 | _flags_type_=`_flags_getFlagInfo \ 842 | "${_flags_usName_}" ${__FLAGS_INFO_TYPE}` 843 | case ${_flags_type_} in 844 | ${__FLAGS_TYPE_BOOLEAN}) 845 | if [ ${_flags_len_} -eq ${__FLAGS_LEN_LONG} ]; then 846 | if [ "${_flags_arg_}" != "${__FLAGS_NULL}" ]; then 847 | eval "FLAGS_${_flags_usName_}=${FLAGS_TRUE}" 848 | else 849 | eval "FLAGS_${_flags_usName_}=${FLAGS_FALSE}" 850 | fi 851 | else 852 | _flags_strToEval_="_flags_val_=\ 853 | \${__flags_${_flags_usName_}_${__FLAGS_INFO_DEFAULT}}" 854 | eval "${_flags_strToEval_}" 855 | # shellcheck disable=SC2154 856 | if [ "${_flags_val_}" -eq ${FLAGS_FALSE} ]; then 857 | eval "FLAGS_${_flags_usName_}=${FLAGS_TRUE}" 858 | else 859 | eval "FLAGS_${_flags_usName_}=${FLAGS_FALSE}" 860 | fi 861 | fi 862 | ;; 863 | 864 | ${__FLAGS_TYPE_FLOAT}) 865 | if _flags_validFloat "${_flags_arg_}"; then 866 | eval "FLAGS_${_flags_usName_}='${_flags_arg_}'" 867 | else 868 | flags_error="invalid float value (${_flags_arg_})" 869 | flags_return=${FLAGS_ERROR} 870 | break 871 | fi 872 | ;; 873 | 874 | ${__FLAGS_TYPE_INTEGER}) 875 | if _flags_validInt "${_flags_arg_}"; then 876 | eval "FLAGS_${_flags_usName_}='${_flags_arg_}'" 877 | else 878 | flags_error="invalid integer value (${_flags_arg_})" 879 | flags_return=${FLAGS_ERROR} 880 | break 881 | fi 882 | ;; 883 | 884 | ${__FLAGS_TYPE_STRING}) 885 | eval "FLAGS_${_flags_usName_}='${_flags_arg_}'" 886 | ;; 887 | esac 888 | 889 | # Handle special case help flag. 890 | if [ "${_flags_usName_}" = 'help' ]; then 891 | # shellcheck disable=SC2154 892 | if [ "${FLAGS_help}" -eq ${FLAGS_TRUE} ]; then 893 | flags_help 894 | flags_error='help requested' 895 | flags_return=${FLAGS_FALSE} 896 | break 897 | fi 898 | fi 899 | 900 | # Shift the option and non-boolean arguments out. 901 | shift 902 | [ "${_flags_type_}" != ${__FLAGS_TYPE_BOOLEAN} ] && shift 903 | done 904 | 905 | # Give user back non-flag arguments. 906 | FLAGS_ARGV='' 907 | while [ $# -gt 0 ]; do 908 | FLAGS_ARGV="${FLAGS_ARGV:+${FLAGS_ARGV} }'$1'" 909 | shift 910 | done 911 | 912 | unset _flags_arg_ _flags_len_ _flags_name_ _flags_opt_ _flags_pos_ \ 913 | _flags_strToEval_ _flags_type_ _flags_usName_ _flags_val_ 914 | return ${flags_return} 915 | } 916 | 917 | # Perform some path using built-ins. 918 | # 919 | # Args: 920 | # $@: string: math expression to evaluate 921 | # Output: 922 | # integer: the result 923 | # Returns: 924 | # bool: success of math evaluation 925 | _flags_math() { 926 | if [ $# -eq 0 ]; then 927 | flags_return=${FLAGS_FALSE} 928 | elif _flags_useBuiltin; then 929 | # Variable assignment is needed as workaround for Solaris Bourne shell, 930 | # which cannot parse a bare $((expression)). 931 | # shellcheck disable=SC2016 932 | _flags_expr_='$(($@))' 933 | eval echo ${_flags_expr_} 934 | flags_return=$? 935 | unset _flags_expr_ 936 | else 937 | eval expr "$@" 938 | flags_return=$? 939 | fi 940 | 941 | return ${flags_return} 942 | } 943 | 944 | # Cross-platform strlen() implementation. 945 | # 946 | # Args: 947 | # _flags_str: string: to determine length of 948 | # Output: 949 | # integer: length of string 950 | # Returns: 951 | # bool: success of strlen evaluation 952 | _flags_strlen() { 953 | _flags_str_=${1:-} 954 | 955 | if [ -z "${_flags_str_}" ]; then 956 | flags_output=0 957 | elif _flags_useBuiltin; then 958 | flags_output=${#_flags_str_} 959 | else 960 | flags_output=`${FLAGS_EXPR_CMD} "${_flags_str_}" : '.*'` 961 | fi 962 | flags_return=$? 963 | 964 | unset _flags_str_ 965 | echo "${flags_output}" 966 | return ${flags_return} 967 | } 968 | 969 | # Use built-in helper function to enable unit testing. 970 | # 971 | # Args: 972 | # None 973 | # Returns: 974 | # bool: true if built-ins should be used 975 | _flags_useBuiltin() { return ${__FLAGS_USE_BUILTIN}; } 976 | 977 | #------------------------------------------------------------------------------ 978 | # public functions 979 | # 980 | # A basic boolean flag. Boolean flags do not take any arguments, and their 981 | # value is either 1 (false) or 0 (true). For long flags, the false value is 982 | # specified on the command line by prepending the word 'no'. With short flags, 983 | # the presence of the flag toggles the current value between true and false. 984 | # Specifying a short boolean flag twice on the command results in returning the 985 | # value back to the default value. 986 | # 987 | # A default value is required for boolean flags. 988 | # 989 | # For example, lets say a Boolean flag was created whose long name was 'update' 990 | # and whose short name was 'x', and the default value was 'false'. This flag 991 | # could be explicitly set to 'true' with '--update' or by '-x', and it could be 992 | # explicitly set to 'false' with '--noupdate'. 993 | DEFINE_boolean() { _flags_define ${__FLAGS_TYPE_BOOLEAN} "$@"; } 994 | 995 | # Other basic flags. 996 | DEFINE_float() { _flags_define ${__FLAGS_TYPE_FLOAT} "$@"; } 997 | DEFINE_integer() { _flags_define ${__FLAGS_TYPE_INTEGER} "$@"; } 998 | DEFINE_string() { _flags_define ${__FLAGS_TYPE_STRING} "$@"; } 999 | 1000 | # Parse the flags. 1001 | # 1002 | # Args: 1003 | # unnamed: list: command-line flags to parse 1004 | # Returns: 1005 | # integer: success of operation, or error 1006 | FLAGS() { 1007 | # Define a standard 'help' flag if one isn't already defined. 1008 | [ -z "${__flags_help_type:-}" ] && \ 1009 | DEFINE_boolean 'help' false 'show this help' 'h' 1010 | 1011 | # Parse options. 1012 | if [ $# -gt 0 ]; then 1013 | if [ "${__FLAGS_GETOPT_VERS}" -ne "${__FLAGS_GETOPT_VERS_ENH}" ]; then 1014 | _flags_getoptStandard "$@" 1015 | else 1016 | _flags_getoptEnhanced "$@" 1017 | fi 1018 | flags_return=$? 1019 | else 1020 | # Nothing passed; won't bother running getopt. 1021 | __flags_opts='--' 1022 | flags_return=${FLAGS_TRUE} 1023 | fi 1024 | 1025 | if [ ${flags_return} -eq ${FLAGS_TRUE} ]; then 1026 | _flags_parseGetopt $# "${__flags_opts}" 1027 | flags_return=$? 1028 | fi 1029 | 1030 | [ ${flags_return} -eq ${FLAGS_ERROR} ] && _flags_fatal "${flags_error}" 1031 | return ${flags_return} 1032 | } 1033 | 1034 | # This is a helper function for determining the 'getopt' version for platforms 1035 | # where the detection isn't working. It simply outputs debug information that 1036 | # can be included in a bug report. 1037 | # 1038 | # Args: 1039 | # none 1040 | # Output: 1041 | # debug info that can be included in a bug report 1042 | # Returns: 1043 | # nothing 1044 | flags_getoptInfo() { 1045 | # Platform info. 1046 | _flags_debug "uname -a: `uname -a`" 1047 | _flags_debug "PATH: ${PATH}" 1048 | 1049 | # Shell info. 1050 | if [ -n "${BASH_VERSION:-}" ]; then 1051 | _flags_debug 'shell: bash' 1052 | _flags_debug "BASH_VERSION: ${BASH_VERSION}" 1053 | elif [ -n "${ZSH_VERSION:-}" ]; then 1054 | _flags_debug 'shell: zsh' 1055 | _flags_debug "ZSH_VERSION: ${ZSH_VERSION}" 1056 | fi 1057 | 1058 | # getopt info. 1059 | ${FLAGS_GETOPT_CMD} >/dev/null 1060 | _flags_getoptReturn=$? 1061 | _flags_debug "getopt return: ${_flags_getoptReturn}" 1062 | _flags_debug "getopt --version: `${FLAGS_GETOPT_CMD} --version 2>&1`" 1063 | 1064 | unset _flags_getoptReturn 1065 | } 1066 | 1067 | # Returns whether the detected getopt version is the enhanced version. 1068 | # 1069 | # Args: 1070 | # none 1071 | # Output: 1072 | # none 1073 | # Returns: 1074 | # bool: true if getopt is the enhanced version 1075 | flags_getoptIsEnh() { 1076 | test "${__FLAGS_GETOPT_VERS}" -eq "${__FLAGS_GETOPT_VERS_ENH}" 1077 | } 1078 | 1079 | # Returns whether the detected getopt version is the standard version. 1080 | # 1081 | # Args: 1082 | # none 1083 | # Returns: 1084 | # bool: true if getopt is the standard version 1085 | flags_getoptIsStd() { 1086 | test "${__FLAGS_GETOPT_VERS}" -eq "${__FLAGS_GETOPT_VERS_STD}" 1087 | } 1088 | 1089 | # This is effectively a 'usage()' function. It prints usage information and 1090 | # exits the program with ${FLAGS_FALSE} if it is ever found in the command line 1091 | # arguments. Note this function can be overridden so other apps can define 1092 | # their own --help flag, replacing this one, if they want. 1093 | # 1094 | # Args: 1095 | # none 1096 | # Returns: 1097 | # integer: success of operation (always returns true) 1098 | flags_help() { 1099 | if [ -n "${FLAGS_HELP:-}" ]; then 1100 | echo "${FLAGS_HELP}" >&2 1101 | else 1102 | echo "USAGE: ${FLAGS_PARENT:-$0} [flags] args" >&2 1103 | fi 1104 | if [ -n "${__flags_longNames}" ]; then 1105 | echo 'flags:' >&2 1106 | for flags_name_ in ${__flags_longNames}; do 1107 | flags_flagStr_='' 1108 | flags_boolStr_='' 1109 | flags_usName_=`_flags_underscoreName "${flags_name_}"` 1110 | 1111 | flags_default_=`_flags_getFlagInfo \ 1112 | "${flags_usName_}" ${__FLAGS_INFO_DEFAULT}` 1113 | flags_help_=`_flags_getFlagInfo \ 1114 | "${flags_usName_}" ${__FLAGS_INFO_HELP}` 1115 | flags_short_=`_flags_getFlagInfo \ 1116 | "${flags_usName_}" ${__FLAGS_INFO_SHORT}` 1117 | flags_type_=`_flags_getFlagInfo \ 1118 | "${flags_usName_}" ${__FLAGS_INFO_TYPE}` 1119 | 1120 | [ "${flags_short_}" != "${__FLAGS_NULL}" ] && \ 1121 | flags_flagStr_="-${flags_short_}" 1122 | 1123 | if [ "${__FLAGS_GETOPT_VERS}" -eq "${__FLAGS_GETOPT_VERS_ENH}" ]; then 1124 | [ "${flags_short_}" != "${__FLAGS_NULL}" ] && \ 1125 | flags_flagStr_="${flags_flagStr_}," 1126 | # Add [no] to long boolean flag names, except the 'help' flag. 1127 | [ "${flags_type_}" -eq ${__FLAGS_TYPE_BOOLEAN} \ 1128 | -a "${flags_usName_}" != 'help' ] && \ 1129 | flags_boolStr_='[no]' 1130 | flags_flagStr_="${flags_flagStr_}--${flags_boolStr_}${flags_name_}:" 1131 | fi 1132 | 1133 | case ${flags_type_} in 1134 | ${__FLAGS_TYPE_BOOLEAN}) 1135 | if [ "${flags_default_}" -eq ${FLAGS_TRUE} ]; then 1136 | flags_defaultStr_='true' 1137 | else 1138 | flags_defaultStr_='false' 1139 | fi 1140 | ;; 1141 | ${__FLAGS_TYPE_FLOAT}|${__FLAGS_TYPE_INTEGER}) 1142 | flags_defaultStr_=${flags_default_} ;; 1143 | ${__FLAGS_TYPE_STRING}) flags_defaultStr_="'${flags_default_}'" ;; 1144 | esac 1145 | flags_defaultStr_="(default: ${flags_defaultStr_})" 1146 | 1147 | flags_helpStr_=" ${flags_flagStr_} ${flags_help_:+${flags_help_} }${flags_defaultStr_}" 1148 | _flags_strlen "${flags_helpStr_}" >/dev/null 1149 | flags_helpStrLen_=${flags_output} 1150 | flags_columns_=`_flags_columns` 1151 | 1152 | if [ "${flags_helpStrLen_}" -lt "${flags_columns_}" ]; then 1153 | echo "${flags_helpStr_}" >&2 1154 | else 1155 | echo " ${flags_flagStr_} ${flags_help_}" >&2 1156 | # Note: the silliness with the x's is purely for ksh93 on Ubuntu 6.06 1157 | # because it doesn't like empty strings when used in this manner. 1158 | flags_emptyStr_="`echo \"x${flags_flagStr_}x\" \ 1159 | |awk '{printf "%"length($0)-2"s", ""}'`" 1160 | flags_helpStr_=" ${flags_emptyStr_} ${flags_defaultStr_}" 1161 | _flags_strlen "${flags_helpStr_}" >/dev/null 1162 | flags_helpStrLen_=${flags_output} 1163 | 1164 | if [ "${__FLAGS_GETOPT_VERS}" -eq "${__FLAGS_GETOPT_VERS_STD}" \ 1165 | -o "${flags_helpStrLen_}" -lt "${flags_columns_}" ]; then 1166 | # Indented to match help string. 1167 | echo "${flags_helpStr_}" >&2 1168 | else 1169 | # Indented four from left to allow for longer defaults as long flag 1170 | # names might be used too, making things too long. 1171 | echo " ${flags_defaultStr_}" >&2 1172 | fi 1173 | fi 1174 | done 1175 | fi 1176 | 1177 | unset flags_boolStr_ flags_default_ flags_defaultStr_ flags_emptyStr_ \ 1178 | flags_flagStr_ flags_help_ flags_helpStr flags_helpStrLen flags_name_ \ 1179 | flags_columns_ flags_short_ flags_type_ flags_usName_ 1180 | return ${FLAGS_TRUE} 1181 | } 1182 | 1183 | # Reset shflags back to an uninitialized state. 1184 | # 1185 | # Args: 1186 | # none 1187 | # Returns: 1188 | # nothing 1189 | flags_reset() { 1190 | for flags_name_ in ${__flags_longNames}; do 1191 | flags_usName_=`_flags_underscoreName "${flags_name_}"` 1192 | flags_strToEval_="unset FLAGS_${flags_usName_}" 1193 | for flags_type_ in \ 1194 | ${__FLAGS_INFO_DEFAULT} \ 1195 | ${__FLAGS_INFO_HELP} \ 1196 | ${__FLAGS_INFO_SHORT} \ 1197 | ${__FLAGS_INFO_TYPE} 1198 | do 1199 | flags_strToEval_=\ 1200 | "${flags_strToEval_} __flags_${flags_usName_}_${flags_type_}" 1201 | done 1202 | eval "${flags_strToEval_}" 1203 | done 1204 | 1205 | # Reset internal variables. 1206 | __flags_boolNames=' ' 1207 | __flags_longNames=' ' 1208 | __flags_shortNames=' ' 1209 | __flags_definedNames=' ' 1210 | 1211 | # Reset logging level back to default. 1212 | flags_setLoggingLevel ${__FLAGS_LEVEL_DEFAULT} 1213 | 1214 | unset flags_name_ flags_type_ flags_strToEval_ flags_usName_ 1215 | } 1216 | 1217 | # 1218 | # Initialization 1219 | # 1220 | 1221 | # Set the default logging level. 1222 | flags_setLoggingLevel ${__FLAGS_LEVEL_DEFAULT} 1223 | -------------------------------------------------------------------------------- /lib/versions: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # vim:et:ft=sh:sts=2:sw=2 3 | # 4 | # Versions determines the versions of all installed shells. 5 | # 6 | # Copyright 2008-2020 Kate Ward. All Rights Reserved. 7 | # Released under the Apache 2.0 License. 8 | # 9 | # Author: kate.ward@forestent.com (Kate Ward) 10 | # https://github.com/kward/shlib 11 | # 12 | # This library provides reusable functions that determine actual names and 13 | # versions of installed shells and the OS. The library can also be run as a 14 | # script if set executable. 15 | # 16 | # Disable checks that aren't fully portable (POSIX != portable). 17 | # shellcheck disable=SC2006 18 | 19 | ARGV0=`basename "$0"` 20 | LSB_RELEASE='/etc/lsb-release' 21 | VERSIONS_SHELLS='ash /bin/bash /bin/dash /bin/ksh /bin/mksh /bin/pdksh /bin/zsh /usr/xpg4/bin/sh /bin/sh /sbin/sh' 22 | 23 | true; TRUE=$? 24 | false; FALSE=$? 25 | ERROR=2 26 | 27 | UNAME_R=`uname -r` 28 | UNAME_S=`uname -s` 29 | 30 | __versions_haveStrings=${ERROR} 31 | 32 | versions_osName() { 33 | os_name_='unrecognized' 34 | os_system_=${UNAME_S} 35 | os_release_=${UNAME_R} 36 | case ${os_system_} in 37 | CYGWIN_NT-*) os_name_='Cygwin' ;; 38 | Darwin) 39 | os_name_=`/usr/bin/sw_vers -productName` 40 | os_version_=`versions_osVersion` 41 | case ${os_version_} in 42 | 10.4|10.4.[0-9]*) os_name_='Mac OS X Tiger' ;; 43 | 10.5|10.5.[0-9]*) os_name_='Mac OS X Leopard' ;; 44 | 10.6|10.6.[0-9]*) os_name_='Mac OS X Snow Leopard' ;; 45 | 10.7|10.7.[0-9]*) os_name_='Mac OS X Lion' ;; 46 | 10.8|10.8.[0-9]*) os_name_='Mac OS X Mountain Lion' ;; 47 | 10.9|10.9.[0-9]*) os_name_='Mac OS X Mavericks' ;; 48 | 10.10|10.10.[0-9]*) os_name_='Mac OS X Yosemite' ;; 49 | 10.11|10.11.[0-9]*) os_name_='Mac OS X El Capitan' ;; 50 | 10.12|10.12.[0-9]*) os_name_='macOS Sierra' ;; 51 | 10.13|10.13.[0-9]*) os_name_='macOS High Sierra' ;; 52 | 10.14|10.14.[0-9]*) os_name_='macOS Mojave' ;; 53 | 10.15|10.15.[0-9]*) os_name_='macOS Catalina' ;; 54 | 11.*) os_name_='macOS Big Sur' ;; 55 | 12.*) os_name_='macOS Monterey' ;; 56 | *) os_name_='macOS' ;; 57 | esac 58 | ;; 59 | FreeBSD) os_name_='FreeBSD' ;; 60 | Linux) os_name_='Linux' ;; 61 | SunOS) 62 | os_name_='SunOS' 63 | if [ -r '/etc/release' ]; then 64 | if grep 'OpenSolaris' /etc/release >/dev/null; then 65 | os_name_='OpenSolaris' 66 | else 67 | os_name_='Solaris' 68 | fi 69 | fi 70 | ;; 71 | esac 72 | 73 | echo ${os_name_} 74 | unset os_name_ os_system_ os_release_ os_version_ 75 | } 76 | 77 | versions_osVersion() { 78 | os_version_='unrecognized' 79 | os_system_=${UNAME_S} 80 | os_release_=${UNAME_R} 81 | case ${os_system_} in 82 | CYGWIN_NT-*) 83 | os_version_=`expr "${os_release_}" : '\([0-9]*\.[0-9]\.[0-9]*\).*'` 84 | ;; 85 | Darwin) 86 | os_version_=`/usr/bin/sw_vers -productVersion` 87 | ;; 88 | FreeBSD) 89 | os_version_=`expr "${os_release_}" : '\([0-9]*\.[0-9]*\)-.*'` 90 | ;; 91 | Linux) 92 | if [ -r '/etc/os-release' ]; then 93 | os_version_=`awk -F= '$1~/PRETTY_NAME/{print $2}' /etc/os-release \ 94 | |sed 's/"//g'` 95 | elif [ -r '/etc/redhat-release' ]; then 96 | os_version_=`cat /etc/redhat-release` 97 | elif [ -r '/etc/SuSE-release' ]; then 98 | os_version_=`head -n 1 /etc/SuSE-release` 99 | elif [ -r "${LSB_RELEASE}" ]; then 100 | if grep -q 'DISTRIB_ID=Ubuntu' "${LSB_RELEASE}"; then 101 | # shellcheck disable=SC2002 102 | os_version_=`cat "${LSB_RELEASE}" \ 103 | |awk -F= '$1~/DISTRIB_DESCRIPTION/{print $2}' \ 104 | |sed 's/"//g;s/ /-/g'` 105 | fi 106 | fi 107 | ;; 108 | SunOS) 109 | if [ -r '/etc/release' ]; then 110 | if grep 'OpenSolaris' /etc/release >/dev/null; then # OpenSolaris 111 | os_version_=`grep 'OpenSolaris' /etc/release |awk '{print $2"("$3")"}'` 112 | else # Solaris 113 | major_=`echo "${os_release_}" |sed 's/[0-9]*\.\([0-9]*\)/\1/'` 114 | minor_=`grep Solaris /etc/release |sed 's/[^u]*\(u[0-9]*\).*/\1/'` 115 | os_version_="${major_}${minor_}" 116 | fi 117 | fi 118 | ;; 119 | esac 120 | 121 | echo "${os_version_}" 122 | unset os_release_ os_system_ os_version_ major_ minor_ 123 | } 124 | 125 | versions_shellVersion() { 126 | shell_=$1 127 | 128 | shell_present_=${FALSE} 129 | case "${shell_}" in 130 | ash) [ -x '/bin/busybox' ] && shell_present_=${TRUE} ;; 131 | *) [ -x "${shell_}" ] && shell_present_=${TRUE} ;; 132 | esac 133 | if [ ${shell_present_} -eq ${FALSE} ]; then 134 | echo 'not installed' 135 | return ${FALSE} 136 | fi 137 | 138 | version_='' 139 | case ${shell_} in 140 | # SunOS shells. 141 | /sbin/sh) ;; 142 | /usr/xpg4/bin/sh) version_=`versions_shell_xpg4 "${shell_}"` ;; 143 | 144 | # Generic shell. 145 | */sh) 146 | # This could be one of any number of shells. Try until one fits. 147 | version_='' 148 | [ -z "${version_}" ] && version_=`versions_shell_bash "${shell_}"` 149 | # dash cannot be self determined yet 150 | [ -z "${version_}" ] && version_=`versions_shell_ksh "${shell_}"` 151 | # pdksh is covered in versions_shell_ksh() 152 | [ -z "${version_}" ] && version_=`versions_shell_xpg4 "${shell_}"` 153 | [ -z "${version_}" ] && version_=`versions_shell_zsh "${shell_}"` 154 | ;; 155 | 156 | # Specific shells. 157 | ash) version_=`versions_shell_ash "${shell_}"` ;; 158 | # bash - Bourne Again SHell (https://www.gnu.org/software/bash/) 159 | */bash) version_=`versions_shell_bash "${shell_}"` ;; 160 | */dash) version_=`versions_shell_dash` ;; 161 | # ksh - KornShell (http://www.kornshell.com/) 162 | */ksh) version_=`versions_shell_ksh "${shell_}"` ;; 163 | # mksh - MirBSD Korn Shell (http://www.mirbsd.org/mksh.htm) 164 | */mksh) version_=`versions_shell_ksh "${shell_}"` ;; 165 | # pdksh - Public Domain Korn Shell (http://web.cs.mun.ca/~michael/pdksh/) 166 | */pdksh) version_=`versions_shell_pdksh "${shell_}"` ;; 167 | # zsh (https://www.zsh.org/) 168 | */zsh) version_=`versions_shell_zsh "${shell_}"` ;; 169 | 170 | # Unrecognized shell. 171 | *) version_='invalid' 172 | esac 173 | 174 | echo "${version_:-unknown}" 175 | unset shell_ version_ 176 | } 177 | 178 | # The ash shell is included in BusyBox. 179 | versions_shell_ash() { 180 | busybox --help |head -1 |sed 's/BusyBox v\([0-9.]*\) .*/\1/' 181 | } 182 | 183 | versions_shell_bash() { 184 | $1 --version : 2>&1 |grep 'GNU bash' |sed 's/.*version \([^ ]*\).*/\1/' 185 | } 186 | 187 | # Assuming Ubuntu Linux until somebody comes up with a better test. The 188 | # following test will return an empty string if dash is not installed. 189 | versions_shell_dash() { 190 | eval dpkg >/dev/null 2>&1 191 | [ $? -eq 127 ] && return # Return if dpkg not found. 192 | 193 | dpkg -l |grep ' dash ' |awk '{print $3}' 194 | } 195 | 196 | versions_shell_ksh() { 197 | versions_shell_=$1 198 | versions_version_='' 199 | 200 | # Try a few different ways to figure out the version. 201 | versions_version_=`${versions_shell_} --version : 2>&1` 202 | # shellcheck disable=SC2181 203 | if [ $? -eq 0 ]; then 204 | versions_version_=`echo "${versions_version_}" \ 205 | |sed 's/.*\([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]\).*/\1/'` 206 | else 207 | versions_version_='' 208 | fi 209 | if [ -z "${versions_version_}" ]; then 210 | # shellcheck disable=SC2016 211 | versions_version_=`${versions_shell_} -c 'echo ${KSH_VERSION}'` 212 | fi 213 | if [ -z "${versions_version_}" ]; then 214 | _versions_have_strings 215 | versions_version_=`strings "${versions_shell_}" 2>&1 \ 216 | |grep Version \ 217 | |sed 's/^.*Version \(.*\)$/\1/;s/ s+ \$$//;s/ /-/g'` 218 | fi 219 | if [ -z "${versions_version_}" ]; then 220 | versions_version_=`versions_shell_pdksh "${versions_shell_}"` 221 | fi 222 | 223 | echo "${versions_version_}" 224 | unset versions_shell_ versions_version_ 225 | } 226 | 227 | # mksh - MirBSD Korn Shell (http://www.mirbsd.org/mksh.htm) 228 | # mksh is a successor to pdksh (Public Domain Korn Shell). 229 | versions_shell_mksh() { 230 | versions_shell_ksh 231 | } 232 | 233 | # pdksh - Public Domain Korn Shell 234 | # pdksh is an obsolete shell, which was replaced by mksh (among others). 235 | versions_shell_pdksh() { 236 | _versions_have_strings 237 | strings "$1" 2>&1 \ 238 | |grep 'PD KSH' \ 239 | |sed -e 's/.*PD KSH \(.*\)/\1/;s/ /-/g' 240 | } 241 | 242 | versions_shell_xpg4() { 243 | _versions_have_strings 244 | strings "$1" 2>&1 \ 245 | |grep 'Version' \ 246 | |sed -e 's/^@(#)Version //' 247 | } 248 | 249 | versions_shell_zsh() { 250 | versions_shell_=$1 251 | 252 | # Try a few different ways to figure out the version. 253 | # shellcheck disable=SC2016 254 | versions_version_=`echo 'echo ${ZSH_VERSION}' |${versions_shell_}` 255 | if [ -z "${versions_version_}" ]; then 256 | versions_version_=`${versions_shell_} --version : 2>&1` 257 | # shellcheck disable=SC2181 258 | if [ $? -eq 0 ]; then 259 | versions_version_=`echo "${versions_version_}" |awk '{print $2}'` 260 | else 261 | versions_version_='' 262 | fi 263 | fi 264 | 265 | echo "${versions_version_}" 266 | unset versions_shell_ versions_version_ 267 | } 268 | 269 | # Determine if the 'strings' binary installed. 270 | _versions_have_strings() { 271 | [ ${__versions_haveStrings} -ne ${ERROR} ] && return 272 | if eval strings /dev/null >/dev/null 2>&1; then 273 | __versions_haveStrings=${TRUE} 274 | return 275 | fi 276 | 277 | echo 'WARN: strings not installed. try installing binutils?' >&2 278 | __versions_haveStrings=${FALSE} 279 | } 280 | 281 | versions_main() { 282 | # Treat unset variables as an error. 283 | set -u 284 | 285 | os_name=`versions_osName` 286 | os_version=`versions_osVersion` 287 | echo "os: ${os_name} version: ${os_version}" 288 | 289 | for shell in ${VERSIONS_SHELLS}; do 290 | shell_version=`versions_shellVersion "${shell}"` 291 | echo "shell: ${shell} version: ${shell_version}" 292 | done 293 | } 294 | 295 | if [ "${ARGV0}" = 'versions' ]; then 296 | versions_main "$@" 297 | fi 298 | -------------------------------------------------------------------------------- /shunit2_args_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # vim:et:ft=sh:sts=2:sw=2 3 | # 4 | # shunit2 unit test for running subset(s) of tests based upon command line args. 5 | # 6 | # Copyright 2008-2021 Kate Ward. All Rights Reserved. 7 | # Released under the Apache 2.0 license. 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # https://github.com/kward/shunit2 11 | # 12 | # Also shows how non-default tests or a arbitrary subset of tests can be run. 13 | # 14 | # Disable source following. 15 | # shellcheck disable=SC1090,SC1091 16 | 17 | # Load test helpers. 18 | . ./shunit2_test_helpers 19 | 20 | CUSTOM_TEST_RAN='' 21 | 22 | # This test does not normally run because it does not begin "test*". Will be 23 | # run by setting the arguments to the script to include the name of this test. 24 | custom_test() { 25 | # Arbitrary assert. 26 | assertTrue 0 27 | # The true intent is to set this variable, which will be tested below. 28 | CUSTOM_TEST_RAN='yup, we ran' 29 | } 30 | 31 | # Verify that `customTest()` ran. 32 | testCustomTestRan() { 33 | assertNotNull "'custom_test()' did not run" "${CUSTOM_TEST_RAN}" 34 | } 35 | 36 | # Fail if this test runs, which is shouldn't if arguments are set correctly. 37 | testShouldFail() { 38 | fail 'testShouldFail should not be run if argument parsing works' 39 | } 40 | 41 | oneTimeSetUp() { 42 | th_oneTimeSetUp 43 | } 44 | 45 | # If zero/one argument(s) are provided, this test is being run in it's 46 | # entirety, and therefore we want to set the arguments to the script to 47 | # (simulate and) test the processing of command-line specified tests. If we 48 | # don't, then the "test_will_fail" test will run (by default) and the overall 49 | # test will fail. 50 | # 51 | # However, if two or more arguments are provided, then assume this test script 52 | # is being run by hand to experiment with command-line test specification, and 53 | # then don't override the user provided arguments. 54 | if [ "$#" -le 1 ]; then 55 | # We set the arguments in a POSIX way, inasmuch as we can; 56 | # helpful tip: 57 | # https://unix.stackexchange.com/questions/258512/how-to-remove-a-positional-parameter-from 58 | set -- '--' 'custom_test' 'testCustomTestRan' 59 | fi 60 | 61 | # Load and run shunit2. 62 | # shellcheck disable=SC2034 63 | [ -n "${ZSH_VERSION:-}" ] && SHUNIT_PARENT=$0 64 | . "${TH_SHUNIT}" 65 | -------------------------------------------------------------------------------- /shunit2_asserts_test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # vim:et:ft=sh:sts=2:sw=2 3 | # 4 | # shunit2 unit test for assert functions. 5 | # 6 | # Copyright 2008-2021 Kate Ward. All Rights Reserved. 7 | # Released under the Apache 2.0 license. 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Author: kate.ward@forestent.com (Kate Ward) 11 | # https://github.com/kward/shunit2 12 | # 13 | # In this file, all assert calls under test must be wrapped in () so they do not 14 | # influence the metrics of the test itself. 15 | # 16 | # Disable source following. 17 | # shellcheck disable=SC1090,SC1091 18 | 19 | # These variables will be overridden by the test helpers. 20 | stdoutF="${TMPDIR:-/tmp}/STDOUT" 21 | stderrF="${TMPDIR:-/tmp}/STDERR" 22 | 23 | # Load test helpers. 24 | . ./shunit2_test_helpers 25 | 26 | commonEqualsSame() { 27 | fn=$1 28 | 29 | # These should succeed. 30 | 31 | desc='equal' 32 | if (${fn} 'x' 'x' >"${stdoutF}" 2>"${stderrF}"); then 33 | th_assertTrueWithNoOutput "${desc}" $? "${stdoutF}" "${stderrF}" 34 | else 35 | fail "${desc}: unexpected failure" 36 | _showTestOutput 37 | fi 38 | 39 | desc='equal_with_message' 40 | if (${fn} 'some message' 'x' 'x' >"${stdoutF}" 2>"${stderrF}"); then 41 | th_assertTrueWithNoOutput "${desc}" $? "${stdoutF}" "${stderrF}" 42 | else 43 | fail "${desc}: unexpected failure" 44 | _showTestOutput 45 | fi 46 | 47 | desc='equal_with_spaces' 48 | if (${fn} 'abc def' 'abc def' >"${stdoutF}" 2>"${stderrF}"); then 49 | th_assertTrueWithNoOutput "${desc}" $? "${stdoutF}" "${stderrF}" 50 | else 51 | fail "${desc}: unexpected failure" 52 | _showTestOutput 53 | fi 54 | 55 | desc='equal_null_values' 56 | if (${fn} '' '' >"${stdoutF}" 2>"${stderrF}"); then 57 | th_assertTrueWithNoOutput "${desc}" $? "${stdoutF}" "${stderrF}" 58 | else 59 | fail "${desc}: unexpected failure" 60 | _showTestOutput 61 | fi 62 | 63 | # These should fail. 64 | 65 | desc='not_equal' 66 | if (${fn} 'x' 'y' >"${stdoutF}" 2>"${stderrF}"); then 67 | fail "${desc}: expected a failure" 68 | _showTestOutput 69 | else 70 | th_assertFalseWithOutput "${desc}" $? "${stdoutF}" "${stderrF}" 71 | fi 72 | } 73 | 74 | commonNotEqualsSame() { 75 | fn=$1 76 | 77 | # These should succeed. 78 | 79 | desc='not_same' 80 | if (${fn} 'x' 'y' >"${stdoutF}" 2>"${stderrF}"); then 81 | th_assertTrueWithNoOutput "${desc}" $? "${stdoutF}" "${stderrF}" 82 | else 83 | fail "${desc}: unexpected failure" 84 | _showTestOutput 85 | fi 86 | 87 | desc='not_same_with_message' 88 | if (${fn} 'some message' 'x' 'y' >"${stdoutF}" 2>"${stderrF}"); then 89 | th_assertTrueWithNoOutput "${desc}" $? "${stdoutF}" "${stderrF}" 90 | else 91 | fail "${desc}: unexpected failure" 92 | _showTestOutput 93 | fi 94 | 95 | # These should fail. 96 | 97 | desc='same' 98 | if (${fn} 'x' 'x' >"${stdoutF}" 2>"${stderrF}"); then 99 | fail "${desc}: expected a failure" 100 | _showTestOutput 101 | else 102 | th_assertFalseWithOutput "${desc}" $? "${stdoutF}" "${stderrF}" 103 | fi 104 | 105 | desc='unequal_null_values' 106 | if (${fn} '' '' >"${stdoutF}" 2>"${stderrF}"); then 107 | fail "${desc}: expected a failure" 108 | _showTestOutput 109 | else 110 | th_assertFalseWithOutput "${desc}" $? "${stdoutF}" "${stderrF}" 111 | fi 112 | } 113 | 114 | testAssertEquals() { commonEqualsSame 'assertEquals'; } 115 | testAssertNotEquals() { commonNotEqualsSame 'assertNotEquals'; } 116 | testAssertSame() { commonEqualsSame 'assertSame'; } 117 | testAssertNotSame() { commonNotEqualsSame 'assertNotSame'; } 118 | 119 | testAssertContains() { 120 | # Content is present. 121 | while read -r desc container content; do 122 | if (assertContains "${container}" "${content}" >"${stdoutF}" 2>"${stderrF}"); then 123 | th_assertTrueWithNoOutput "${desc}" $? "${stdoutF}" "${stderrF}" 124 | else 125 | fail "${desc}: unexpected failure" 126 | _showTestOutput 127 | fi 128 | done <"${stdoutF}" 2>"${stderrF}"); then 137 | fail "${desc}: unexpected failure" 138 | _showTestOutput 139 | else 140 | th_assertFalseWithOutput "${desc}" $? "${stdoutF}" "${stderrF}" 141 | fi 142 | done <"${stdoutF}" 2>"${stderrF}"); then 151 | th_assertTrueWithNoOutput "${desc}" $? "${stdoutF}" "${stderrF}" 152 | else 153 | fail "${desc}: unexpected failure" 154 | _showTestOutput 155 | fi 156 | 157 | desc="contains_with_message" 158 | if (assertContains 'some message' 'abcdef' 'abc' >"${stdoutF}" 2>"${stderrF}"); then 159 | th_assertTrueWithNoOutput "${desc}" $? "${stdoutF}" "${stderrF}" 160 | else 161 | fail "${desc}: unexpected failure" 162 | _showTestOutput 163 | fi 164 | } 165 | 166 | testAssertNotContains() { 167 | # Content not present. 168 | while read -r desc container content; do 169 | if (assertNotContains "${container}" "${content}" >"${stdoutF}" 2>"${stderrF}"); then 170 | th_assertTrueWithNoOutput "${desc}" $? "${stdoutF}" "${stderrF}" 171 | else 172 | fail "${desc}: unexpected failure" 173 | _showTestOutput 174 | fi 175 | done <"${stdoutF}" 2>"${stderrF}"); then 185 | fail "${desc}: expected a failure" 186 | _showTestOutput 187 | else 188 | th_assertFalseWithOutput "${desc}" $? "${stdoutF}" "${stderrF}" 189 | fi 190 | done <"${stdoutF}" 2>"${stderrF}"); then 196 | th_assertTrueWithNoOutput "${desc}" $? "${stdoutF}" "${stderrF}" 197 | else 198 | fail "${desc}: unexpected failure" 199 | _showTestOutput 200 | fi 201 | } 202 | 203 | testAssertNull() { 204 | while read -r desc value; do 205 | if (assertNull "${value}" >"${stdoutF}" 2>"${stderrF}"); then 206 | fail "${desc}: unexpected failure" 207 | _showTestOutput 208 | else 209 | th_assertFalseWithOutput "${desc}" $? "${stdoutF}" "${stderrF}" 210 | fi 211 | done <<'EOF' 212 | x_alone x 213 | x_double_quote_a x"a 214 | x_single_quote_a x'a 215 | x_dollar_a x$a 216 | x_backtick_a x`a 217 | EOF 218 | 219 | desc='null_without_message' 220 | if (assertNull '' >"${stdoutF}" 2>"${stderrF}"); then 221 | th_assertTrueWithNoOutput "${desc}" $? "${stdoutF}" "${stderrF}" 222 | else 223 | fail "${desc}: unexpected failure" 224 | _showTestOutput 225 | fi 226 | 227 | desc='null_with_message' 228 | if (assertNull 'some message' '' >"${stdoutF}" 2>"${stderrF}"); then 229 | th_assertTrueWithNoOutput "${desc}" $? "${stdoutF}" "${stderrF}" 230 | else 231 | fail "${desc}: unexpected failure" 232 | _showTestOutput 233 | fi 234 | 235 | desc='x_is_not_null' 236 | if (assertNull 'x' >"${stdoutF}" 2>"${stderrF}"); then 237 | fail "${desc}: expected a failure" 238 | _showTestOutput 239 | else 240 | th_assertFalseWithOutput "${desc}" $? "${stdoutF}" "${stderrF}" 241 | fi 242 | } 243 | 244 | testAssertNotNull() { 245 | while read -r desc value; do 246 | if (assertNotNull "${value}" >"${stdoutF}" 2>"${stderrF}"); then 247 | th_assertTrueWithNoOutput "${desc}" $? "${stdoutF}" "${stderrF}" 248 | else 249 | fail "${desc}: unexpected failure" 250 | _showTestOutput 251 | fi 252 | done <<'EOF' 253 | x_alone x 254 | x_double_quote_b x"b 255 | x_single_quote_b x'b 256 | x_dollar_b x$b 257 | x_backtick_b x`b 258 | EOF 259 | 260 | desc='not_null_with_message' 261 | if (assertNotNull 'some message' 'x' >"${stdoutF}" 2>"${stderrF}"); then 262 | th_assertTrueWithNoOutput "${desc}" $? "${stdoutF}" "${stderrF}" 263 | else 264 | fail "${desc}: unexpected failure" 265 | _showTestOutput 266 | fi 267 | 268 | desc="double_ticks_are_null" 269 | if (assertNotNull '' >"${stdoutF}" 2>"${stderrF}"); then 270 | fail "${desc}: expected a failure" 271 | _showTestOutput 272 | else 273 | th_assertFalseWithOutput "${desc}" $? "${stdoutF}" "${stderrF}" 274 | fi 275 | } 276 | 277 | testAssertTrue() { 278 | # True values. 279 | while read -r desc value; do 280 | if (assertTrue "${value}" >"${stdoutF}" 2>"${stderrF}"); then 281 | th_assertTrueWithNoOutput "${desc}" $? "${stdoutF}" "${stderrF}" 282 | else 283 | fail "${desc}: unexpected failure" 284 | _showTestOutput 285 | fi 286 | done <<'EOF' 287 | zero 0 288 | zero_eq_zero [ 0 -eq 0 ] 289 | EOF 290 | 291 | # Not true values. 292 | while read -r desc value; do 293 | if (assertTrue "${value}" >"${stdoutF}" 2>"${stderrF}"); then 294 | fail "${desc}: expected a failure" 295 | _showTestOutput 296 | else 297 | th_assertFalseWithOutput "${desc}" $? "${stdoutF}" "${stderrF}" 298 | fi 299 | done <"${stdoutF}" 2>"${stderrF}"); then 307 | th_assertTrueWithNoOutput "${desc}" $? "${stdoutF}" "${stderrF}" 308 | else 309 | fail "${desc}: unexpected failure" 310 | _showTestOutput 311 | fi 312 | } 313 | 314 | testAssertFalse() { 315 | # False values. 316 | while read -r desc value; do 317 | if (assertFalse "${value}" >"${stdoutF}" 2>"${stderrF}"); then 318 | th_assertTrueWithNoOutput "${desc}" $? "${stdoutF}" "${stderrF}" 319 | else 320 | fail "${desc}: unexpected failure" 321 | _showTestOutput 322 | fi 323 | done <"${stdoutF}" 2>"${stderrF}"); then 332 | fail "${desc}: expected a failure" 333 | _showTestOutput 334 | else 335 | th_assertFalseWithOutput "${desc}" $? "${stdoutF}" "${stderrF}" 336 | fi 337 | done <<'EOF' 338 | zero 0 339 | zero_eq_zero [ 0 -eq 0 ] 340 | EOF 341 | 342 | desc='false_with_message' 343 | if (assertFalse 'some message' 1 >"${stdoutF}" 2>"${stderrF}"); then 344 | th_assertTrueWithNoOutput "${desc}" $? "${stdoutF}" "${stderrF}" 345 | else 346 | fail "${desc}: unexpected failure" 347 | _showTestOutput 348 | fi 349 | } 350 | 351 | FUNCTIONS=' 352 | assertEquals assertNotEquals 353 | assertSame assertNotSame 354 | assertContains assertNotContains 355 | assertNull assertNotNull 356 | assertTrue assertFalse 357 | ' 358 | 359 | testTooFewArguments() { 360 | for fn in ${FUNCTIONS}; do 361 | # These functions support zero arguments. 362 | case "${fn}" in 363 | assertNull) continue ;; 364 | assertNotNull) continue ;; 365 | esac 366 | 367 | desc="${fn}" 368 | if (${fn} >"${stdoutF}" 2>"${stderrF}"); then 369 | fail "${desc}: expected a failure" 370 | _showTestOutput 371 | else 372 | got=$? want=${SHUNIT_ERROR} 373 | assertEquals "${desc}: incorrect return code" "${got}" "${want}" 374 | th_assertFalseWithError "${desc}" "${got}" "${stdoutF}" "${stderrF}" 375 | fi 376 | done 377 | } 378 | 379 | testTooManyArguments() { 380 | for fn in ${FUNCTIONS}; do 381 | desc="${fn}" 382 | if (${fn} arg1 arg2 arg3 arg4 >"${stdoutF}" 2>"${stderrF}"); then 383 | fail "${desc}: expected a failure" 384 | _showTestOutput 385 | else 386 | got=$? want=${SHUNIT_ERROR} 387 | assertEquals "${desc}: incorrect return code" "${got}" "${want}" 388 | th_assertFalseWithError "${desc}" "${got}" "${stdoutF}" "${stderrF}" 389 | fi 390 | done 391 | } 392 | 393 | oneTimeSetUp() { 394 | th_oneTimeSetUp 395 | } 396 | 397 | # showTestOutput for the most recently run test. 398 | _showTestOutput() { th_showOutput "${SHUNIT_FALSE}" "${stdoutF}" "${stderrF}"; } 399 | 400 | # Load and run shunit2. 401 | # shellcheck disable=SC2034 402 | [ -n "${ZSH_VERSION:-}" ] && SHUNIT_PARENT=$0 403 | . "${TH_SHUNIT}" 404 | -------------------------------------------------------------------------------- /shunit2_failures_test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # vim:et:ft=sh:sts=2:sw=2 3 | # 4 | # shUnit2 unit test for failure functions. These functions do not test values. 5 | # 6 | # Copyright 2008-2021 Kate Ward. All Rights Reserved. 7 | # Released under the Apache 2.0 license. 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Author: kate.ward@forestent.com (Kate Ward) 11 | # https://github.com/kward/shunit2 12 | # 13 | # Disable source following. 14 | # shellcheck disable=SC1090,SC1091 15 | 16 | # These variables will be overridden by the test helpers. 17 | stdoutF="${TMPDIR:-/tmp}/STDOUT" 18 | stderrF="${TMPDIR:-/tmp}/STDERR" 19 | 20 | # Load test helpers. 21 | . ./shunit2_test_helpers 22 | 23 | testFail() { 24 | # Test without a message. 25 | desc='fail_without_message' 26 | if ( fail >"${stdoutF}" 2>"${stderrF}" ); then 27 | fail "${desc}: expected a failure" 28 | th_showOutput 29 | else 30 | th_assertFalseWithOutput "${desc}" $? "${stdoutF}" "${stderrF}" 31 | fi 32 | 33 | # Test with a message. 34 | desc='fail_with_message' 35 | if ( fail 'some message' >"${stdoutF}" 2>"${stderrF}" ); then 36 | fail "${desc}: expected a failure" 37 | th_showOutput 38 | else 39 | th_assertFalseWithOutput "${desc}" $? "${stdoutF}" "${stderrF}" 40 | fi 41 | } 42 | 43 | # FN_TESTS hold all the functions to be tested. 44 | # shellcheck disable=SC2006 45 | FN_TESTS=` 46 | # fn num_args pattern 47 | cat <"${stdoutF}" 2>"${stderrF}" ); then 67 | fail "${desc}: expected a failure" 68 | th_showOutput 69 | else 70 | th_assertFalseWithOutput "${desc}" $? "${stdoutF}" "${stderrF}" 71 | fi 72 | 73 | # Test with a message. 74 | arg1='' arg2='' 75 | case ${num_args} in 76 | 1) ;; 77 | 2) arg1='arg1' ;; 78 | 3) arg1='arg1' arg2='arg2' ;; 79 | esac 80 | 81 | desc="${fn}_with_message" 82 | if ( ${fn} 'some message' ${arg1} ${arg2} >"${stdoutF}" 2>"${stderrF}" ); then 83 | fail "${desc}: expected a failure" 84 | th_showOutput 85 | else 86 | th_assertFalseWithOutput "${desc}" $? "${stdoutF}" "${stderrF}" 87 | if ! grep -- "${pattern}" "${stdoutF}" >/dev/null; then 88 | fail "${desc}: incorrect message to STDOUT" 89 | th_showOutput 90 | fi 91 | fi 92 | done 93 | } 94 | 95 | testTooFewArguments() { 96 | echo "${FN_TESTS}" \ 97 | |while read -r fn num_args pattern; do 98 | # Skip functions that support a single message argument. 99 | if [ "${num_args}" -eq 1 ]; then 100 | continue 101 | fi 102 | 103 | desc="${fn}" 104 | if (${fn} >"${stdoutF}" 2>"${stderrF}"); then 105 | fail "${desc}: expected a failure" 106 | _showTestOutput 107 | else 108 | got=$? want=${SHUNIT_ERROR} 109 | assertEquals "${desc}: incorrect return code" "${got}" "${want}" 110 | th_assertFalseWithError "${desc}" "${got}" "${stdoutF}" "${stderrF}" 111 | fi 112 | done 113 | } 114 | 115 | testTooManyArguments() { 116 | echo "${FN_TESTS}" \ 117 | |while read -r fn num_args pattern; do 118 | desc="${fn}" 119 | if (${fn} arg1 arg2 arg3 arg4 >"${stdoutF}" 2>"${stderrF}"); then 120 | fail "${desc}: expected a failure" 121 | _showTestOutput 122 | else 123 | got=$? want=${SHUNIT_ERROR} 124 | assertEquals "${desc}: incorrect return code" "${got}" "${want}" 125 | th_assertFalseWithError "${desc}" "${got}" "${stdoutF}" "${stderrF}" 126 | fi 127 | done 128 | } 129 | 130 | oneTimeSetUp() { 131 | th_oneTimeSetUp 132 | } 133 | 134 | # Load and run shUnit2. 135 | # shellcheck disable=SC2034 136 | [ -n "${ZSH_VERSION:-}" ] && SHUNIT_PARENT=$0 137 | . "${TH_SHUNIT}" 138 | -------------------------------------------------------------------------------- /shunit2_general_test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # vim:et:ft=sh:sts=2:sw=2 3 | # 4 | # shUnit2 unit tests for general commands. 5 | # 6 | # Copyright 2008-2021 Kate Ward. All Rights Reserved. 7 | # Released under the Apache 2.0 license. 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Author: kate.ward@forestent.com (Kate Ward) 11 | # https://github.com/kward/shunit2 12 | # 13 | # Disable source following. 14 | # shellcheck disable=SC1090,SC1091 15 | 16 | # These variables will be overridden by the test helpers. 17 | stdoutF="${TMPDIR:-/tmp}/STDOUT" 18 | stderrF="${TMPDIR:-/tmp}/STDERR" 19 | 20 | # Load test helpers. 21 | . ./shunit2_test_helpers 22 | 23 | testSkipping() { 24 | # We shouldn't be skipping to start. 25 | if isSkipping; then 26 | th_error 'skipping *should not be* enabled' 27 | return 28 | fi 29 | 30 | startSkipping 31 | was_skipping_started=${SHUNIT_FALSE} 32 | if isSkipping; then was_skipping_started=${SHUNIT_TRUE}; fi 33 | 34 | endSkipping 35 | was_skipping_ended=${SHUNIT_FALSE} 36 | if isSkipping; then was_skipping_ended=${SHUNIT_TRUE}; fi 37 | 38 | assertEquals "skipping wasn't started" "${was_skipping_started}" "${SHUNIT_TRUE}" 39 | assertNotEquals "skipping wasn't ended" "${was_skipping_ended}" "${SHUNIT_TRUE}" 40 | return 0 41 | } 42 | 43 | testStartSkippingWithMessage() { 44 | unittestF="${SHUNIT_TMPDIR}/unittest" 45 | sed 's/^#//' >"${unittestF}" <<\EOF 46 | ## Start skipping with a message. 47 | #testSkipping() { 48 | # startSkipping 'SKIP-a-Dee-Doo-Dah' 49 | #} 50 | #SHUNIT_COLOR='none' 51 | #. ${TH_SHUNIT} 52 | EOF 53 | # Ignoring errors with `|| :` as we only care about `FAILED` in the output. 54 | ( exec "${SHELL:-sh}" "${unittestF}" >"${stdoutF}" 2>"${stderrF}" ) || : 55 | if ! grep '\[skipping\] SKIP-a-Dee-Doo-Dah' "${stderrF}" >/dev/null; then 56 | fail 'skipping message was not generated' 57 | fi 58 | return 0 59 | } 60 | 61 | testStartSkippingWithoutMessage() { 62 | unittestF="${SHUNIT_TMPDIR}/unittest" 63 | sed 's/^#//' >"${unittestF}" <<\EOF 64 | ## Start skipping with a message. 65 | #testSkipping() { 66 | # startSkipping 67 | #} 68 | #SHUNIT_COLOR='none' 69 | #. ${TH_SHUNIT} 70 | EOF 71 | # Ignoring errors with `|| :` as we only care about `FAILED` in the output. 72 | ( exec "${SHELL:-sh}" "${unittestF}" >"${stdoutF}" 2>"${stderrF}" ) || : 73 | if grep '\[skipping\]' "${stderrF}" >/dev/null; then 74 | fail 'skipping message was unexpectedly generated' 75 | fi 76 | return 0 77 | } 78 | 79 | setUp() { 80 | for f in "${stdoutF}" "${stderrF}"; do 81 | cp /dev/null "${f}" 82 | done 83 | 84 | # Reconfigure coloring as some tests override default behavior. 85 | _shunit_configureColor "${SHUNIT_COLOR_DEFAULT}" 86 | 87 | # shellcheck disable=SC2034,SC2153 88 | SHUNIT_CMD_TPUT=${__SHUNIT_CMD_TPUT} 89 | } 90 | 91 | oneTimeSetUp() { 92 | SHUNIT_COLOR_DEFAULT="${SHUNIT_COLOR}" 93 | th_oneTimeSetUp 94 | } 95 | 96 | # Load and run shUnit2. 97 | # shellcheck disable=SC2034 98 | [ -n "${ZSH_VERSION:-}" ] && SHUNIT_PARENT=$0 99 | . "${TH_SHUNIT}" 100 | -------------------------------------------------------------------------------- /shunit2_macros_test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # vim:et:ft=sh:sts=2:sw=2 3 | # 4 | # shunit2 unit test for macros. 5 | # 6 | # Copyright 2008-2021 Kate Ward. All Rights Reserved. 7 | # Released under the Apache 2.0 license. 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Author: kate.ward@forestent.com (Kate Ward) 11 | # https://github.com/kward/shunit2 12 | # 13 | # Disable source following. 14 | # shellcheck disable=SC1090,SC1091 15 | 16 | # These variables will be overridden by the test helpers. 17 | stdoutF="${TMPDIR:-/tmp}/STDOUT" 18 | stderrF="${TMPDIR:-/tmp}/STDERR" 19 | 20 | # Load test helpers. 21 | . ./shunit2_test_helpers 22 | 23 | testAssertEquals() { 24 | isLinenoWorking || startSkipping 25 | 26 | ( ${_ASSERT_EQUALS_} 'x' 'y' >"${stdoutF}" 2>"${stderrF}" ) 27 | if ! wasAssertGenerated; then 28 | fail '_ASSERT_EQUALS_ failed to produce an ASSERT message' 29 | showTestOutput 30 | fi 31 | 32 | ( ${_ASSERT_EQUALS_} '"some msg"' 'x' 'y' >"${stdoutF}" 2>"${stderrF}" ) 33 | if ! wasAssertGenerated; then 34 | fail '_ASSERT_EQUALS_ (with a message) failed to produce an ASSERT message' 35 | showTestOutput 36 | fi 37 | } 38 | 39 | testAssertNotEquals() { 40 | isLinenoWorking || startSkipping 41 | 42 | ( ${_ASSERT_NOT_EQUALS_} 'x' 'x' >"${stdoutF}" 2>"${stderrF}" ) 43 | if ! wasAssertGenerated; then 44 | fail '_ASSERT_NOT_EQUALS_ failed to produce an ASSERT message' 45 | showTestOutput 46 | fi 47 | 48 | ( ${_ASSERT_NOT_EQUALS_} '"some msg"' 'x' 'x' >"${stdoutF}" 2>"${stderrF}" ) 49 | if ! wasAssertGenerated; then 50 | fail '_ASSERT_NOT_EQUALS_ (with a message) failed to produce an ASSERT message' 51 | showTestOutput 52 | fi 53 | } 54 | 55 | testSame() { 56 | isLinenoWorking || startSkipping 57 | 58 | ( ${_ASSERT_SAME_} 'x' 'y' >"${stdoutF}" 2>"${stderrF}" ) 59 | if ! wasAssertGenerated; then 60 | fail '_ASSERT_SAME_ failed to produce an ASSERT message' 61 | showTestOutput 62 | fi 63 | 64 | ( ${_ASSERT_SAME_} '"some msg"' 'x' 'y' >"${stdoutF}" 2>"${stderrF}" ) 65 | if ! wasAssertGenerated; then 66 | fail '_ASSERT_SAME_ (with a message) failed to produce an ASSERT message' 67 | showTestOutput 68 | fi 69 | } 70 | 71 | testNotSame() { 72 | isLinenoWorking || startSkipping 73 | 74 | ( ${_ASSERT_NOT_SAME_} 'x' 'x' >"${stdoutF}" 2>"${stderrF}" ) 75 | if ! wasAssertGenerated; then 76 | fail '_ASSERT_NOT_SAME_ failed to produce an ASSERT message' 77 | showTestOutput 78 | fi 79 | 80 | ( ${_ASSERT_NOT_SAME_} '"some msg"' 'x' 'x' >"${stdoutF}" 2>"${stderrF}" ) 81 | if ! wasAssertGenerated; then 82 | fail '_ASSERT_NOT_SAME_ (with a message) failed to produce an ASSERT message' 83 | showTestOutput 84 | fi 85 | } 86 | 87 | testNull() { 88 | isLinenoWorking || startSkipping 89 | 90 | ( ${_ASSERT_NULL_} 'x' >"${stdoutF}" 2>"${stderrF}" ) 91 | if ! wasAssertGenerated; then 92 | fail '_ASSERT_NULL_ failed to produce an ASSERT message' 93 | showTestOutput 94 | fi 95 | 96 | ( ${_ASSERT_NULL_} '"some msg"' 'x' >"${stdoutF}" 2>"${stderrF}" ) 97 | if ! wasAssertGenerated; then 98 | fail '_ASSERT_NULL_ (with a message) failed to produce an ASSERT message' 99 | showTestOutput 100 | fi 101 | } 102 | 103 | testNotNull() { 104 | isLinenoWorking || startSkipping 105 | 106 | ( ${_ASSERT_NOT_NULL_} '' >"${stdoutF}" 2>"${stderrF}" ) 107 | if ! wasAssertGenerated; then 108 | fail '_ASSERT_NOT_NULL_ failed to produce an ASSERT message' 109 | showTestOutput 110 | fi 111 | 112 | ( ${_ASSERT_NOT_NULL_} '"some msg"' '""' >"${stdoutF}" 2>"${stderrF}" ) 113 | if ! wasAssertGenerated; then 114 | fail '_ASSERT_NOT_NULL_ (with a message) failed to produce an ASSERT message' 115 | showTestOutput 116 | fi 117 | } 118 | 119 | testAssertTrue() { 120 | isLinenoWorking || startSkipping 121 | 122 | ( ${_ASSERT_TRUE_} "${SHUNIT_FALSE}" >"${stdoutF}" 2>"${stderrF}" ) 123 | if ! wasAssertGenerated; then 124 | fail '_ASSERT_TRUE_ failed to produce an ASSERT message' 125 | showTestOutput 126 | fi 127 | 128 | ( ${_ASSERT_TRUE_} '"some msg"' "${SHUNIT_FALSE}" >"${stdoutF}" 2>"${stderrF}" ) 129 | if ! wasAssertGenerated; then 130 | fail '_ASSERT_TRUE_ (with a message) failed to produce an ASSERT message' 131 | showTestOutput 132 | fi 133 | } 134 | 135 | testAssertFalse() { 136 | isLinenoWorking || startSkipping 137 | 138 | ( ${_ASSERT_FALSE_} "${SHUNIT_TRUE}" >"${stdoutF}" 2>"${stderrF}" ) 139 | if ! wasAssertGenerated; then 140 | fail '_ASSERT_FALSE_ failed to produce an ASSERT message' 141 | showTestOutput 142 | fi 143 | 144 | ( ${_ASSERT_FALSE_} '"some msg"' "${SHUNIT_TRUE}" >"${stdoutF}" 2>"${stderrF}" ) 145 | if ! wasAssertGenerated; then 146 | fail '_ASSERT_FALSE_ (with a message) failed to produce an ASSERT message' 147 | showTestOutput 148 | fi 149 | } 150 | 151 | testFail() { 152 | isLinenoWorking || startSkipping 153 | 154 | ( ${_FAIL_} >"${stdoutF}" 2>"${stderrF}" ) 155 | if ! wasAssertGenerated; then 156 | fail '_FAIL_ failed to produce an ASSERT message' 157 | showTestOutput 158 | fi 159 | 160 | ( ${_FAIL_} '"some msg"' >"${stdoutF}" 2>"${stderrF}" ) 161 | if ! wasAssertGenerated; then 162 | fail '_FAIL_ (with a message) failed to produce an ASSERT message' 163 | showTestOutput 164 | fi 165 | } 166 | 167 | testFailNotEquals() { 168 | isLinenoWorking || startSkipping 169 | 170 | ( ${_FAIL_NOT_EQUALS_} 'x' 'y' >"${stdoutF}" 2>"${stderrF}" ) 171 | if ! wasAssertGenerated; then 172 | fail '_FAIL_NOT_EQUALS_ failed to produce an ASSERT message' 173 | showTestOutput 174 | fi 175 | 176 | ( ${_FAIL_NOT_EQUALS_} '"some msg"' 'x' 'y' >"${stdoutF}" 2>"${stderrF}" ) 177 | if ! wasAssertGenerated; then 178 | fail '_FAIL_NOT_EQUALS_ (with a message) failed to produce an ASSERT message' 179 | showTestOutput 180 | fi 181 | } 182 | 183 | testFailSame() { 184 | isLinenoWorking || startSkipping 185 | 186 | ( ${_FAIL_SAME_} 'x' 'x' >"${stdoutF}" 2>"${stderrF}" ) 187 | if ! wasAssertGenerated; then 188 | fail '_FAIL_SAME_ failed to produce an ASSERT message' 189 | showTestOutput 190 | fi 191 | 192 | ( ${_FAIL_SAME_} '"some msg"' 'x' 'x' >"${stdoutF}" 2>"${stderrF}" ) 193 | if ! wasAssertGenerated; then 194 | fail '_FAIL_SAME_ (with a message) failed to produce an ASSERT message' 195 | showTestOutput 196 | fi 197 | } 198 | 199 | testFailNotSame() { 200 | isLinenoWorking || startSkipping 201 | 202 | ( ${_FAIL_NOT_SAME_} 'x' 'y' >"${stdoutF}" 2>"${stderrF}" ) 203 | if ! wasAssertGenerated; then 204 | fail '_FAIL_NOT_SAME_ failed to produce an ASSERT message' 205 | showTestOutput 206 | fi 207 | 208 | ( ${_FAIL_NOT_SAME_} '"some msg"' 'x' 'y' >"${stdoutF}" 2>"${stderrF}" ) 209 | if ! wasAssertGenerated; then 210 | fail '_FAIL_NOT_SAME_ (with a message) failed to produce an ASSERT message' 211 | showTestOutput 212 | fi 213 | } 214 | 215 | oneTimeSetUp() { 216 | th_oneTimeSetUp 217 | 218 | if ! isLinenoWorking; then 219 | # shellcheck disable=SC2016 220 | th_warn '${LINENO} is not working for this shell. Tests will be skipped.' 221 | fi 222 | } 223 | 224 | # isLinenoWorking returns true if the `$LINENO` shell variable works properly. 225 | isLinenoWorking() { 226 | # shellcheck disable=SC2016 227 | ln='eval echo "${LINENO:-}"' 228 | case ${ln} in 229 | [0-9]*) return "${SHUNIT_TRUE}" ;; 230 | -[0-9]*) return "${SHUNIT_FALSE}" ;; # The dash shell produces negative values. 231 | esac 232 | return "${SHUNIT_FALSE}" 233 | } 234 | 235 | # showTestOutput for the most recently run test. 236 | showTestOutput() { th_showOutput "${SHUNIT_FALSE}" "${stdoutF}" "${stderrF}"; } 237 | 238 | # wasAssertGenerated returns true if an ASSERT was generated to STDOUT. 239 | wasAssertGenerated() { grep '^ASSERT:\[[0-9]*\] *' "${stdoutF}" >/dev/null; } 240 | 241 | # Disable output coloring as it breaks the tests. 242 | SHUNIT_COLOR='none'; export SHUNIT_COLOR 243 | 244 | # Load and run shUnit2. 245 | # shellcheck disable=SC2034 246 | [ -n "${ZSH_VERSION:-}" ] && SHUNIT_PARENT="$0" 247 | . "${TH_SHUNIT}" 248 | -------------------------------------------------------------------------------- /shunit2_misc_test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # vim:et:ft=sh:sts=2:sw=2 3 | # 4 | # shUnit2 unit tests of miscellaneous things 5 | # 6 | # Copyright 2008-2023 Kate Ward. All Rights Reserved. 7 | # Released under the Apache 2.0 license. 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Author: kate.ward@forestent.com (Kate Ward) 11 | # https://github.com/kward/shunit2 12 | # 13 | # Allow usage of legacy backticked `...` notation instead of $(...). 14 | # shellcheck disable=SC2006 15 | # Disable source following. 16 | # shellcheck disable=SC1090,SC1091 17 | 18 | # These variables will be overridden by the test helpers. 19 | stdoutF="${TMPDIR:-/tmp}/STDOUT" 20 | stderrF="${TMPDIR:-/tmp}/STDERR" 21 | 22 | # Load test helpers. 23 | . ./shunit2_test_helpers 24 | 25 | # Note: the test script is prefixed with '#' chars so that shUnit2 does not 26 | # incorrectly interpret the embedded functions as real functions. 27 | testUnboundVariable() { 28 | unittestF="${SHUNIT_TMPDIR}/unittest" 29 | sed 's/^#//' >"${unittestF}" <"${stdoutF}" 2>"${stderrF}" ); then 43 | fail 'expected a non-zero exit value' 44 | fi 45 | if ! grep '^ASSERT:unknown failure' "${stdoutF}" >/dev/null; then 46 | fail 'assert message was not generated' 47 | fi 48 | if ! grep '^Ran [0-9]* test' "${stdoutF}" >/dev/null; then 49 | fail 'test count message was not generated' 50 | fi 51 | if ! grep '^FAILED' "${stdoutF}" >/dev/null; then 52 | fail 'failure message was not generated' 53 | fi 54 | } 55 | 56 | # assertEquals repeats message argument. 57 | # https://github.com/kward/shunit2/issues/7 58 | testIssue7() { 59 | # Disable coloring so 'ASSERT:' lines can be matched correctly. 60 | _shunit_configureColor 'none' 61 | 62 | # Ignoring errors with `|| :` as we only care about the message in this test. 63 | ( assertEquals 'Some message.' 1 2 >"${stdoutF}" 2>"${stderrF}" ) || : 64 | diff "${stdoutF}" - >/dev/null < but was:<2> 66 | EOF 67 | rtrn=$? 68 | assertEquals "${SHUNIT_TRUE}" "${rtrn}" 69 | [ "${rtrn}" -eq "${SHUNIT_TRUE}" ] || cat "${stderrF}" >&2 70 | } 71 | 72 | # Support prefixes on test output. 73 | # https://github.com/kward/shunit2/issues/29 74 | testIssue29() { 75 | unittestF="${SHUNIT_TMPDIR}/unittest" 76 | sed 's/^#//' >"${unittestF}" <"${stdoutF}" 2>"${stderrF}" ) 84 | grep '^--- test_assert' "${stdoutF}" >/dev/null 85 | rtrn=$? 86 | assertEquals "${SHUNIT_TRUE}" "${rtrn}" 87 | [ "${rtrn}" -eq "${SHUNIT_TRUE}" ] || cat "${stdoutF}" >&2 88 | } 89 | 90 | # Test that certain external commands sometimes "stubbed" by users are escaped. 91 | testIssue54() { 92 | for c in mkdir rm cat chmod sed; do 93 | if grep "^[^#]*${c} " "${TH_SHUNIT}" | grep -qv "command ${c}"; then 94 | fail "external call to ${c} not protected somewhere" 95 | fi 96 | done 97 | # shellcheck disable=2016 98 | if grep '^[^#]*[^ ] *\[' "${TH_SHUNIT}" | grep -qv '${__SHUNIT_BUILTIN} \['; then 99 | fail 'call to [ not protected somewhere' 100 | fi 101 | # shellcheck disable=2016 102 | if grep '^[^#]* *\.' "${TH_SHUNIT}" | grep -qv '${__SHUNIT_BUILTIN} \.'; then 103 | fail 'call to . not protected somewhere' 104 | fi 105 | } 106 | 107 | # shUnit2 should not exit with 0 when it has syntax errors. 108 | # https://github.com/kward/shunit2/issues/69 109 | testIssue69() { 110 | unittestF="${SHUNIT_TMPDIR}/unittest" 111 | 112 | # Note: assertNull not tested as zero arguments == null, which is valid. 113 | for t in Equals NotEquals NotNull Same NotSame True False; do 114 | assert="assert${t}" 115 | sed 's/^#//' >"${unittestF}" <"${stdoutF}" 2>"${stderrF}" ) || : 123 | grep '^FAILED' "${stdoutF}" >/dev/null 124 | assertTrue "failure message for ${assert} was not generated" $? 125 | done 126 | } 127 | 128 | # Ensure that test fails if setup/teardown functions fail. 129 | testIssue77() { 130 | unittestF="${SHUNIT_TMPDIR}/unittest" 131 | for func in oneTimeSetUp setUp tearDown oneTimeTearDown; do 132 | sed 's/^#//' >"${unittestF}" <"${stdoutF}" 2>"${stderrF}" || : 141 | grep '^FAILED' "${stdoutF}" >/dev/null 142 | assertTrue "failure of ${func}() did not end test" $? 143 | done 144 | } 145 | 146 | # Ensure a test failure is recorded for code containing syntax errors. 147 | # https://github.com/kward/shunit2/issues/84 148 | testIssue84() { 149 | unittestF="${SHUNIT_TMPDIR}/unittest" 150 | sed 's/^#//' >"${unittestF}" <<\EOF 151 | ## Function with syntax error. 152 | #syntax_error() { ${!#3442} -334 a$@2[1]; } 153 | #test_syntax_error() { 154 | # syntax_error 155 | # assertTrue ${SHUNIT_TRUE} 156 | #} 157 | #SHUNIT_COLOR='none' 158 | #SHUNIT_TEST_PREFIX='--- ' 159 | #. ${TH_SHUNIT} 160 | EOF 161 | # Ignoring errors with `|| :` as we only care about `FAILED` in the output. 162 | ( exec "${SHELL:-sh}" "${unittestF}" >"${stdoutF}" 2>"${stderrF}" ) || : 163 | if ! grep '^FAILED' "${stdoutF}" >/dev/null; then 164 | fail 'failure message was not generated' 165 | fi 166 | } 167 | 168 | # Demonstrate that asserts are no longer executed in subshells. 169 | # https://github.com/kward/shunit2/issues/123 170 | # 171 | # NOTE: this test only works if the `${BASH_SUBSHELL}` variable is present. 172 | testIssue123() { 173 | if [ -z "${BASH_SUBSHELL:-}" ]; then 174 | # shellcheck disable=SC2016 175 | startSkipping 'The ${BASH_SUBSHELL} variable is unavailable in this shell.' 176 | fi 177 | # shellcheck disable=SC2016 178 | assertTrue 'not in subshell' '[[ ${BASH_SUBSHELL} -eq 0 ]]' 179 | } 180 | 181 | testPrepForSourcing() { 182 | assertEquals '/abc' "`_shunit_prepForSourcing '/abc'`" 183 | assertEquals './abc' "`_shunit_prepForSourcing './abc'`" 184 | assertEquals './abc' "`_shunit_prepForSourcing 'abc'`" 185 | } 186 | 187 | # Test the various ways of declaring functions. 188 | # 189 | # Prefixing (then stripping) with comment symbol so these functions aren't 190 | # treated as real functions by shUnit2. 191 | testExtractTestFunctions() { 192 | f="${SHUNIT_TMPDIR}/extract_test_functions" 193 | sed 's/^#//' <"${f}" 194 | ## Function on a single line. 195 | #testABC() { echo 'ABC'; } 196 | ## Multi-line function with '{' on next line. 197 | #test_def() 198 | # { 199 | # echo 'def' 200 | #} 201 | ## Multi-line function with '{' on first line. 202 | #testG3 () { 203 | # echo 'G3' 204 | #} 205 | ## Function with numerical values in name. 206 | #function test4() { echo '4'; } 207 | ## Leading space in front of function. 208 | # test5() { echo '5'; } 209 | ## Function with '_' chars in name. 210 | #some_test_function() { echo 'some func'; } 211 | ## Function that sets variables. 212 | #func_with_test_vars() { 213 | # testVariable=1234 214 | #} 215 | ## Function with keyword but no parenthesis 216 | #function test6 { echo '6'; } 217 | ## Function with keyword but no parenthesis, multi-line 218 | #function test7 { 219 | # echo '7'; 220 | #} 221 | ## Function with no parenthesis, '{' on next line 222 | #function test8 223 | #{ 224 | # echo '8' 225 | #} 226 | ## Function with hyphenated name 227 | #test-9() { 228 | # echo '9'; 229 | #} 230 | ## Function without parenthesis or keyword 231 | #test_foobar { echo 'hello world'; } 232 | ## Function with multiple function keywords 233 | #function function test_test_test() { echo 'lorem'; } 234 | EOF 235 | 236 | actual=`_shunit_extractTestFunctions "${f}"` 237 | assertEquals 'testABC test_def testG3 test4 test5 test6 test7 test8 test-9' "${actual}" 238 | } 239 | 240 | testColors() { 241 | while read -r cmd colors desc; do 242 | SHUNIT_CMD_TPUT=${cmd} 243 | want=${colors} got=`_shunit_colors` 244 | assertEquals "${desc}: incorrect number of colors;" \ 245 | "${got}" "${want}" 246 | done <<'EOF' 247 | missing_tput 16 missing tput command 248 | mock_tput 256 mock tput command 249 | EOF 250 | } 251 | 252 | testColorsWitoutTERM() { 253 | SHUNIT_CMD_TPUT='mock_tput' 254 | got=`TERM='' _shunit_colors` 255 | want=16 256 | assertEquals "${got}" "${want}" 257 | } 258 | 259 | mock_tput() { 260 | if [ -z "${TERM}" ]; then 261 | # shellcheck disable=SC2016 262 | echo 'tput: No value for $TERM and no -T specified' 263 | return 2 264 | fi 265 | if [ "$1" = 'colors' ]; then 266 | echo 256 267 | return 0 268 | fi 269 | return 1 270 | } 271 | 272 | # Note: the test script is prefixed with '#' chars so that shUnit2 does not 273 | # incorrectly interpret the embedded functions as real functions. 274 | testPipefail() { 275 | unittestF="${SHUNIT_TMPDIR}/unittest" 276 | sed 's/^#//' >"${unittestF}" <"${stdoutF}" 2>"${stderrF}" ); then 290 | fail 'expected a zero exit value' 291 | fi 292 | if ! grep '^Ran [0-9]* test' "${stdoutF}" >/dev/null; then 293 | fail 'test count message was not generated' 294 | fi 295 | } 296 | 297 | setUp() { 298 | for f in "${stdoutF}" "${stderrF}"; do 299 | cp /dev/null "${f}" 300 | done 301 | 302 | # Reconfigure coloring as some tests override default behavior. 303 | _shunit_configureColor "${SHUNIT_COLOR_DEFAULT}" 304 | 305 | # shellcheck disable=SC2034,SC2153 306 | SHUNIT_CMD_TPUT=${__SHUNIT_CMD_TPUT} 307 | } 308 | 309 | oneTimeSetUp() { 310 | SHUNIT_COLOR_DEFAULT="${SHUNIT_COLOR}" 311 | th_oneTimeSetUp 312 | } 313 | 314 | # Load and run shUnit2. 315 | # shellcheck disable=SC2034 316 | [ -n "${ZSH_VERSION:-}" ] && SHUNIT_PARENT=$0 317 | . "${TH_SHUNIT}" 318 | -------------------------------------------------------------------------------- /shunit2_shopt_test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # vim:et:ft=sh:sts=2:sw=2 3 | # 4 | # shUnit2 unit tests for `shopt` support. 5 | # 6 | # Copyright 2008-2021 Kate Ward. All Rights Reserved. 7 | # Released under the Apache 2.0 license. 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Author: kate.ward@forestent.com (Kate Ward) 11 | # https://github.com/kward/shunit2 12 | # 13 | # Disable source following. 14 | # shellcheck disable=SC1090,SC1091 15 | 16 | # Load test helpers. 17 | . ./shunit2_test_helpers 18 | 19 | # Call shopt from a variable so it can be mocked if it doesn't work. 20 | SHOPT_CMD='shopt' 21 | 22 | testNullglob() { 23 | isShoptWorking || startSkipping 24 | 25 | nullglob=$(${SHOPT_CMD} nullglob |cut -f2) 26 | 27 | # Test without nullglob. 28 | ${SHOPT_CMD} -u nullglob 29 | assertEquals 'test without nullglob' 0 0 30 | 31 | # Test with nullglob. 32 | ${SHOPT_CMD} -s nullglob 33 | assertEquals 'test with nullglob' 1 1 34 | 35 | # Reset nullglob. 36 | if [ "${nullglob}" = "on" ]; then 37 | ${SHOPT_CMD} -s nullglob 38 | else 39 | ${SHOPT_CMD} -u nullglob 40 | fi 41 | 42 | unset nullglob 43 | } 44 | 45 | oneTimeSetUp() { 46 | th_oneTimeSetUp 47 | 48 | if ! isShoptWorking; then 49 | SHOPT_CMD='mock_shopt' 50 | fi 51 | } 52 | 53 | # isShoptWorking returns true if the `shopt` shell command is available. 54 | # NOTE: `shopt` is not defined as part of the POSIX standard. 55 | isShoptWorking() { 56 | # shellcheck disable=SC2039,SC3044 57 | ( shopt >/dev/null 2>&1 ); 58 | } 59 | 60 | mock_shopt() { 61 | if [ $# -eq 0 ]; then 62 | echo "nullglob off" 63 | fi 64 | return 65 | } 66 | 67 | # Load and run shUnit2. 68 | # shellcheck disable=SC2034 69 | [ -n "${ZSH_VERSION:-}" ] && SHUNIT_PARENT="$0" 70 | . "${TH_SHUNIT}" 71 | -------------------------------------------------------------------------------- /shunit2_standalone_test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # vim:et:ft=sh:sts=2:sw=2 3 | # 4 | # shUnit2 unit test for standalone operation. 5 | # 6 | # Copyright 2008-2021 Kate Ward. All Rights Reserved. 7 | # Released under the Apache 2.0 license. 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Author: kate.ward@forestent.com (Kate Ward) 11 | # https://github.com/kward/shunit2 12 | # 13 | # This unit test is purely to test that calling shunit2 directly, while passing 14 | # the name of a unit test script, works. When run, this script determines if it 15 | # is running as a standalone program, and calls main() if it is. 16 | # 17 | # Disable source following. 18 | # shellcheck disable=SC1090,SC1091 19 | 20 | ARGV0=$(basename "$0") 21 | 22 | # Load test helpers. 23 | . ./shunit2_test_helpers 24 | 25 | testStandalone() { 26 | assertTrue "${SHUNIT_TRUE}" 27 | } 28 | 29 | main() { 30 | ${TH_SHUNIT} "${ARGV0}" 31 | } 32 | 33 | # Run main() if are running as a standalone script. 34 | if [ "${ARGV0}" = 'shunit2_standalone_test.sh' ]; then 35 | main "$@" 36 | fi 37 | -------------------------------------------------------------------------------- /shunit2_test_helpers: -------------------------------------------------------------------------------- 1 | # vim:et:ft=sh:sts=2:sw=2 2 | # 3 | # shUnit2 unit test common functions 4 | # 5 | # Copyright 2008-2021 Kate Ward. All Rights Reserved. 6 | # Released under the Apache 2.0 license. 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Author: kate.ward@forestent.com (Kate Ward) 10 | # https://github.com/kward/shunit2 11 | # 12 | ### ShellCheck (http://www.shellcheck.net/) 13 | # expr may be antiquated, but it is the only solution in some cases. 14 | # shellcheck disable=SC2003 15 | # $() are not fully portable (POSIX != portable). 16 | # shellcheck disable=SC2006 17 | 18 | # Exit immediately if a simple command exits with a non-zero status. 19 | set -e 20 | 21 | # Treat unset variables as an error when performing parameter expansion. 22 | set -u 23 | 24 | # Set shwordsplit for zsh. 25 | [ -n "${ZSH_VERSION:-}" ] && setopt shwordsplit 26 | 27 | # 28 | # Constants. 29 | # 30 | 31 | # Path to shUnit2 library. Can be overridden by setting SHUNIT_INC. 32 | TH_SHUNIT=${SHUNIT_INC:-./shunit2}; export TH_SHUNIT 33 | 34 | # Configure debugging. Set the DEBUG environment variable to any 35 | # non-empty value to enable debug output, or TRACE to enable trace 36 | # output. 37 | TRACE=${TRACE:+'th_trace '} 38 | [ -n "${TRACE}" ] && DEBUG=1 39 | [ -z "${TRACE}" ] && TRACE=':' 40 | 41 | DEBUG=${DEBUG:+'th_debug '} 42 | [ -z "${DEBUG}" ] && DEBUG=':' 43 | 44 | # 45 | # Variables. 46 | # 47 | 48 | th_RANDOM=0 49 | 50 | # 51 | # Functions. 52 | # 53 | 54 | # Logging functions. 55 | th_trace() { echo "test:TRACE $*" >&2; } 56 | th_debug() { echo "test:DEBUG $*" >&2; } 57 | th_info() { echo "test:INFO $*" >&2; } 58 | th_warn() { echo "test:WARN $*" >&2; } 59 | th_error() { echo "test:ERROR $*" >&2; } 60 | th_fatal() { echo "test:FATAL $*" >&2; } 61 | 62 | # Output subtest name. 63 | th_subtest() { echo " $*" >&2; } 64 | 65 | th_oneTimeSetUp() { 66 | # These files will be cleaned up automatically by shUnit2. 67 | stdoutF="${SHUNIT_TMPDIR}/stdout" 68 | stderrF="${SHUNIT_TMPDIR}/stderr" 69 | returnF="${SHUNIT_TMPDIR}/return" 70 | expectedF="${SHUNIT_TMPDIR}/expected" 71 | export stdoutF stderrF returnF expectedF 72 | } 73 | 74 | # Generate a random number. 75 | th_generateRandom() { 76 | tfgr_random=${th_RANDOM} 77 | 78 | while [ "${tfgr_random}" = "${th_RANDOM}" ]; do 79 | # shellcheck disable=SC2039 80 | if [ -n "${RANDOM:-}" ]; then 81 | # $RANDOM works 82 | # shellcheck disable=SC2039 83 | tfgr_random=${RANDOM}${RANDOM}${RANDOM}$$ 84 | elif [ -r '/dev/urandom' ]; then 85 | tfgr_random=`od -vAn -N4 -tu4 >> STDOUT' >&2 222 | cat "${_th_stdout_}" >&2 223 | echo '<<< STDOUT' >&2 224 | fi 225 | # shellcheck disable=SC2166 226 | if [ -n "${_th_stderr_}" -a -s "${_th_stderr_}" ]; then 227 | echo '>>> STDERR' >&2 228 | cat "${_th_stderr_}" >&2 229 | echo '<<< STDERR' >&2 230 | fi 231 | fi 232 | 233 | unset _th_return_ _th_stdout_ _th_stderr_ 234 | } 235 | 236 | # 237 | # Main. 238 | # 239 | 240 | ${TRACE} 'trace output enabled' 241 | ${DEBUG} 'debug output enabled' 242 | -------------------------------------------------------------------------------- /shunit2_tools_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # vim:et:ft=sh:sts=2:sw=2 3 | # 4 | # shunit2 unit test for tools testing. 5 | # 6 | # Copyright 2023 AxxonSoft. All Rights Reserved. 7 | # Released under the Apache 2.0 license. 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # https://github.com/kward/shunit2 11 | # 12 | # Disable source following. 13 | # shellcheck disable=SC1090,SC1091 14 | 15 | # Load test helpers. 16 | . ./shunit2_test_helpers 17 | 18 | # Run integer calculation checks. 19 | # Arguments: 20 | # funcName: string: name of function to call as a calculator. 21 | commonCalcInteger() { 22 | _common_function="$1" 23 | 24 | assertEquals "3" "$("${_common_function}" 1 + 2)" 25 | assertEquals "42" "$("${_common_function}" 0 + 42)" 26 | assertEquals "-42" "$("${_common_function}" 0 - 42)" 27 | assertEquals "78" "$("${_common_function}" 123 - 45)" 28 | assertEquals "0" "$("${_common_function}" 1 - 1)" 29 | } 30 | 31 | # Run float calculation checks. 32 | # Arguments: 33 | # funcName: string: name of function to call as a calculator. 34 | commonCalcFloat() { 35 | _common_function="$1" 36 | 37 | assertEquals "3" "$("${_common_function}" 1.0 + 2.0)" 38 | assertEquals "42" "$("${_common_function}" 0.000 + 42.0)" 39 | assertEquals "-42" "$("${_common_function}" 0 - 42.0)" 40 | assertEquals "78" "$("${_common_function}" 123 - 45.00)" 41 | assertEquals "0" "$("${_common_function}" 1.0 - 1.0)" 42 | assertEquals "0" "$("${_common_function}" 1.0 - 1.00)" 43 | 44 | assertEquals "4.6" "$("${_common_function}" 1.2 + 3.4)" 45 | assertEquals "5.1" "$("${_common_function}" 1.2 + 3.9)" 46 | assertEquals "-2.9005" "$("${_common_function}" 1 - 3.9005)" 47 | assertEquals "7.905" "$("${_common_function}" 11.005 - 3.1)" 48 | assertEquals "0.01" "$("${_common_function}" 0.085 - 0.075)" 49 | } 50 | 51 | testFloatFormat() { 52 | # Bad values. 53 | assertEquals '' "$(_shunit_float_format ..)" 54 | assertEquals '' "$(_shunit_float_format 0.1.2)" 55 | assertEquals '' "$(_shunit_float_format 0...)" 56 | assertEquals '' "$(_shunit_float_format 123.123.123)" 57 | assertEquals '' "$(_shunit_float_format 123.123.123.)" 58 | 59 | # Good values (unusual cases). 60 | assertEquals '0' "$(_shunit_float_format .)" 61 | 62 | # Good values (integer). 63 | assertEquals '1' "$(_shunit_float_format 1)" 64 | assertEquals '10' "$(_shunit_float_format 10)" 65 | assertEquals '2300' "$(_shunit_float_format 2300)" 66 | 67 | # Good values (float). 68 | assertEquals '1' "$(_shunit_float_format 1.)" 69 | assertEquals '10' "$(_shunit_float_format 10.)" 70 | assertEquals '2300' "$(_shunit_float_format 2300.)" 71 | assertEquals '1' "$(_shunit_float_format 1.0)" 72 | assertEquals '10' "$(_shunit_float_format 10.0)" 73 | assertEquals '2300' "$(_shunit_float_format 2300.000)" 74 | assertEquals '0' "$(_shunit_float_format .000)" 75 | assertEquals '1.2' "$(_shunit_float_format 1.2)" 76 | assertEquals '4.3' "$(_shunit_float_format 4.30)" 77 | assertEquals '0.3' "$(_shunit_float_format .30)" 78 | assertEquals '0.7' "$(_shunit_float_format .7)" 79 | assertEquals '1.08' "$(_shunit_float_format 1.080)" 80 | } 81 | 82 | testCalcDc() { 83 | if [ -z "${__SHUNIT_CMD_DC}" ]; then 84 | # shellcheck disable=SC2016 85 | startSkipping '`dc` not found' 86 | fi 87 | 88 | commonCalcInteger "_shunit_calc_dc" 89 | commonCalcFloat "_shunit_calc_dc" 90 | } 91 | 92 | testCalcBc() { 93 | if [ -z "${__SHUNIT_CMD_BC}" ]; then 94 | # shellcheck disable=SC2016 95 | startSkipping '`bc` not found' 96 | fi 97 | 98 | commonCalcInteger "_shunit_calc_bc" 99 | commonCalcFloat "_shunit_calc_bc" 100 | } 101 | 102 | testCalcExpr() { 103 | commonCalcInteger "_shunit_calc_expr" 104 | } 105 | 106 | oneTimeSetUp() { 107 | th_oneTimeSetUp 108 | } 109 | 110 | # Load and run shunit2. 111 | # shellcheck disable=SC2034 112 | [ -n "${ZSH_VERSION:-}" ] && SHUNIT_PARENT=$0 113 | . "${TH_SHUNIT}" 114 | -------------------------------------------------------------------------------- /shunit2_xml_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # vim:et:ft=sh:sts=2:sw=2 3 | # 4 | # shunit2 unit test for running subset(s) of tests based upon junit XML generator. 5 | # 6 | # Copyright 2023 AxxonSoft. All Rights Reserved. 7 | # Released under the Apache 2.0 license. 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # https://github.com/kward/shunit2 11 | # 12 | # Also shows how JUnit XML may be generated. 13 | # 14 | # Disable source following. 15 | # shellcheck disable=SC1090,SC1091 16 | 17 | # These variables will be overridden by the test helpers. 18 | stdoutF="" 19 | stderrF="" 20 | 21 | # Load test helpers. 22 | . ./shunit2_test_helpers 23 | 24 | # Run test and check XML output is correct. 25 | # Arguments: 26 | # isSuccess: bool: true if the test should succeed. Default is true. 27 | # $@: additional arguments to pass to shunit2 28 | commonRunAndCheck() { 29 | _common_test_should_succeed=true 30 | if [ $# -gt 0 ]; then 31 | case "$1" in 32 | --*) ;; 33 | *) _common_test_should_succeed="$1"; shift ;; 34 | esac 35 | fi 36 | 37 | _common_test_succeed=true 38 | ( exec "${SHELL:-sh}" "${unittestF}" -- "--output-junit-xml=${currentXmlF}" "$@" >"${stdoutF}" 2>"${stderrF}" ) || { 39 | _common_test_succeed=false 40 | } 41 | 42 | assertEquals "Test exit status" "${_common_test_should_succeed}" "${_common_test_succeed}" 43 | 44 | if ! grep '^Ran [0-9]* test' "${stdoutF}" >/dev/null; then 45 | fail 'test count message was not generated' 46 | th_showOutput 47 | fi 48 | 49 | # Patch time & timestamp attribute to the magic number constant. 50 | sed -i \ 51 | -e 's/time="[0-9]*\(.[0-9]*\)\?"/time="42.25"/g' \ 52 | -e 's/timestamp="[-0-9+T:]*"/timestamp="1983-10-27T03:36:45+0000"/g' \ 53 | "${currentXmlF}" 54 | 55 | if ! diff "${idealXmlF}" "${currentXmlF}" >/dev/null; then 56 | fail 'XML output is not equal' 57 | echo '>>> Ideal' >&2 58 | cat "${idealXmlF}" >&2 59 | echo '<<< Ideal' >&2 60 | echo '>>> Actual' >&2 61 | cat "${currentXmlF}" >&2 62 | echo '<<< Actual' >&2 63 | fi 64 | } 65 | 66 | ### 67 | # XML messages escaping logic 68 | ### 69 | testEscapeXmlDataAmp() { 70 | assertEquals "&" "$(_shunit_escapeXmlData "&")" 71 | } 72 | 73 | testEscapeXmlDataLess() { 74 | assertEquals "<" "$(_shunit_escapeXmlData "<")" 75 | } 76 | 77 | testEscapeXmlDataGreater() { 78 | assertEquals ">" "$(_shunit_escapeXmlData ">")" 79 | } 80 | 81 | testEscapeXmlDataQuote() { 82 | assertEquals """ "$(_shunit_escapeXmlData '"')" 83 | } 84 | 85 | testEscapeXmlDataApostrophe() { 86 | assertEquals "'" "$(_shunit_escapeXmlData "'")" 87 | } 88 | 89 | testEscapeXmlDataMultiple() { 90 | assertEquals "&<'>"" "$(_shunit_escapeXmlData "&<'>\"")" 91 | } 92 | 93 | testEscapeXmlDataInverseMultiple() { 94 | assertEquals "">'<&" "$(_shunit_escapeXmlData "\">'<&")" 95 | } 96 | 97 | ### 98 | # XML tests passing/erroring. 99 | ### 100 | testSingleSuccess() { 101 | sed 's/^#//' >"${unittestF}" <"${idealXmlF}" < 111 | 119 | 125 | 126 | 127 | EOF 128 | 129 | commonRunAndCheck 130 | } 131 | 132 | testFewSuccess() { 133 | sed 's/^#//' >"${unittestF}" <"${idealXmlF}" < 149 | 157 | 163 | 164 | 170 | 171 | 177 | 178 | 179 | EOF 180 | 181 | commonRunAndCheck 182 | } 183 | 184 | testMultipleAsserts() { 185 | sed 's/^#//' >"${unittestF}" <"${idealXmlF}" < 201 | 209 | 215 | 216 | 222 | 223 | 224 | EOF 225 | 226 | commonRunAndCheck 227 | } 228 | 229 | testFailures() { 230 | sed 's/^#//' >"${unittestF}" <"${idealXmlF}" < 249 | 257 | 263 | 267 | 271 | 272 | 278 | 279 | 285 | 289 | 290 | 291 | EOF 292 | 293 | commonRunAndCheck false 294 | } 295 | 296 | ### 297 | # Custom suite name cases. 298 | ### 299 | testCustomSuiteName() { 300 | sed 's/^#//' >"${unittestF}" <"${idealXmlF}" < 310 | 318 | 324 | 325 | 326 | EOF 327 | 328 | commonRunAndCheck --suite-name=mySuiteName 329 | } 330 | 331 | testCustomSuiteNameEscaping() { 332 | sed 's/^#//' >"${unittestF}" <"${idealXmlF}" < 342 | 350 | 356 | 357 | 358 | EOF 359 | 360 | commonRunAndCheck "--suite-name=Custom name with spaces & some special chars!" 361 | } 362 | 363 | oneTimeSetUp() { 364 | th_oneTimeSetUp 365 | unittestF="${SHUNIT_TMPDIR}/unittest" 366 | idealXmlF="${SHUNIT_TMPDIR}/ideal.xml" 367 | currentXmlF="${SHUNIT_TMPDIR}/current.xml" 368 | } 369 | 370 | # Load and run shunit2. 371 | # shellcheck disable=SC2034 372 | [ -n "${ZSH_VERSION:-}" ] && SHUNIT_PARENT=$0 373 | . "${TH_SHUNIT}" 374 | -------------------------------------------------------------------------------- /shunit2_xml_time_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # vim:et:ft=sh:sts=2:sw=2 3 | # 4 | # shunit2 unit test for running subset(s) of tests based upon junit XML time-specific fields. 5 | # 6 | # Copyright 2023 AxxonSoft. All Rights Reserved. 7 | # Released under the Apache 2.0 license. 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # https://github.com/kward/shunit2 11 | # 12 | # Disable source following. 13 | # shellcheck disable=SC1090,SC1091 14 | 15 | # These variables will be overridden by the test helpers. 16 | stdoutF="" 17 | stderrF="" 18 | 19 | # Load test helpers. 20 | . ./shunit2_test_helpers 21 | 22 | # Run test and check test finished correctly. 23 | commonRunAndCheck() { 24 | _common_test_should_succeed=true 25 | _common_test_succeed=true 26 | 27 | _common_testStartSeconds="$(${__SHUNIT_CMD_DATE_SECONDS} -u)" 28 | 29 | ( exec "${SHELL:-sh}" "${unittestF}" -- "--output-junit-xml=${currentXmlF}" >"${stdoutF}" 2>"${stderrF}" ) || { 30 | _common_test_succeed=false 31 | } 32 | 33 | _common_testEndSeconds="$(${__SHUNIT_CMD_DATE_SECONDS} -u)" 34 | _common_testDuration="$(${__SHUNIT_CMD_CALC} "${_common_testEndSeconds}" - "${_common_testStartSeconds}")" 35 | 36 | assertEquals "Test exit status" "${_common_test_should_succeed}" "${_common_test_succeed}" 37 | 38 | if ! grep '^Ran [0-9]* test' "${stdoutF}" >/dev/null; then 39 | fail 'test count message was not generated' 40 | th_showOutput 41 | fi 42 | } 43 | 44 | commonCompareTimes() { 45 | _common_testDurationPatched="${_common_testDuration}" 46 | 47 | # Busybox'es ash (date with 1 second precision) test with GNU sh (date with 1 nanosecond precision) conflicts with time precision. 48 | # That is why we need to add 1 second to the test duration in this case. 49 | if [ "$(date '+%N')" = '%N' ] && [ "$("${SHELL:-sh}" -c "date '+%N'")" != '%N' ]; then 50 | _common_testDurationPatched="$(${__SHUNIT_CMD_CALC} "${_common_testDurationPatched}" + 1)" 51 | fi 52 | 53 | _common_executionTimeChecker="$(awk -v externTestDuration="${_common_testDurationPatched}" ' 54 | BEGIN { 55 | sumStamps = 0 56 | isTotalGiven = 0 57 | } 58 | /^(.*[[:blank:]])?time="/ { 59 | # Extract the time value. 60 | sub(/.*time="/, "") 61 | sub(/"$/, "") 62 | sub(/" .*/, "") 63 | 64 | if (isTotalGiven == 1) { 65 | sumStamps += $0 66 | } else { 67 | isTotalGiven = 1 68 | total = $0 69 | } 70 | } 71 | END { 72 | error = 0 73 | if (isTotalGiven == 0) { 74 | print "No time=\"XXX\" given" 75 | error = 1 76 | } 77 | if (externTestDuration <= 0) { 78 | print "0 >= externTestDuration =", externTestDuration 79 | error = 1 80 | } 81 | if (total <= 0) { 82 | print "0 >= total =", total 83 | error = 1 84 | } 85 | if (sumStamps <= 0) { 86 | print "0 >= sumStamps =", sumStamps 87 | error = 1 88 | } 89 | if (sumStamps > total) { 90 | print "Sum is larger than total. Sum:", sumStamps, "Total:", total 91 | error = 1 92 | } 93 | if (externTestDuration < total) { 94 | print "externTestDuration is smaller than total. externTestDuration:", externTestDuration, "Total:", total 95 | error = 1 96 | } 97 | 98 | if (error == 0) { 99 | print "OK" 100 | } else { 101 | print "ERROR" 102 | } 103 | } 104 | ' "${currentXmlF}")" 105 | } 106 | 107 | testTimeStamp() { 108 | sed 's/^#//' >"${unittestF}" < /dev/null 2>&1; then 132 | # BusyBox date tool does not support the ISO 8601 input, but support the input format. 133 | _test_xmlStartSeconds="$(${__SHUNIT_CMD_DATE_SECONDS} -D "${_test_timestampFormat}" -d "${_test_timestamp}" -u)" 134 | else 135 | _test_xmlStartSeconds="$(${__SHUNIT_CMD_DATE_SECONDS} -d "${_test_timestamp}" -u)" 136 | fi 137 | 138 | # XML timestamp has no sub-second precision. 139 | _test_startComparison="$(${__SHUNIT_CMD_CALC} "${_test_xmlStartSeconds}" - "${_common_testStartSeconds%.*}")" 140 | _test_endComparison="$(${__SHUNIT_CMD_CALC} "${_common_testEndSeconds}" - "${_test_xmlStartSeconds}")" 141 | 142 | _test_startComparison="$(${__SHUNIT_CMD_CALC} "${_test_startComparison}" ">=" "0")" 143 | _test_endComparison="$(${__SHUNIT_CMD_CALC} "${_test_endComparison}" ">=" "0")" 144 | 145 | # shellcheck disable=SC2016 146 | assertTrue 'XML timestamp is so early' '[ "${_test_startComparison}" -gt 0 ]' 147 | # shellcheck disable=SC2016 148 | assertTrue 'XML timestamp is so late' '[ "${_test_endComparison}" -gt 0 ]' 149 | } 150 | 151 | testSingleTimePeriod() { 152 | sed 's/^#//' >"${unittestF}" <&2 167 | echo "<<"${unittestF}" <&2 196 | echo "<<"${unittestF}" <&2 227 | echo "<<= 5)}"; then 231 | fail "Test duration should be at least 5 seconds" 232 | fi 233 | 234 | if [ "$(grep -c 'time="2\(\.[0-9]\+\)\?"' "${currentXmlF}")" -ne 1 ]; then 235 | fail "Must have a test with 2 seconds duration" 236 | fi 237 | 238 | if [ "$(grep -c 'time="3\(\.[0-9]\+\)\?"' "${currentXmlF}")" -ne 1 ]; then 239 | fail "Must have a test with 3 seconds duration" 240 | fi 241 | } 242 | 243 | oneTimeSetUp() { 244 | th_oneTimeSetUp 245 | unittestF="${SHUNIT_TMPDIR}/unittest" 246 | currentXmlF="${SHUNIT_TMPDIR}/current.xml" 247 | } 248 | 249 | # Load and run shunit2. 250 | # shellcheck disable=SC2034 251 | [ -n "${ZSH_VERSION:-}" ] && SHUNIT_PARENT=$0 252 | . "${TH_SHUNIT}" 253 | -------------------------------------------------------------------------------- /test_runner: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # vim:et:ft=sh:sts=2:sw=2 3 | # 4 | # Unit test suite runner. 5 | # 6 | # Copyright 2008-2020 Kate Ward. All Rights Reserved. 7 | # Released under the Apache 2.0 license. 8 | # 9 | # Author: kate.ward@forestent.com (Kate Ward) 10 | # https://github.com/kward/shlib 11 | # 12 | # This script runs all the unit tests that can be found, and generates a nice 13 | # report of the tests. 14 | # 15 | ### Sample usage: 16 | # 17 | # Run all tests for all shells. 18 | # $ ./test_runner 19 | # 20 | # Run all tests for single shell. 21 | # $ ./test_runner -s /bin/bash 22 | # 23 | # Run single test for all shells. 24 | # $ ./test_runner -t shunit_asserts_test.sh 25 | # 26 | # Run single test for single shell. 27 | # $ ./test_runner -s /bin/bash -t shunit_asserts_test.sh 28 | # 29 | ### ShellCheck (http://www.shellcheck.net/) 30 | # Disable source following. 31 | # shellcheck disable=SC1090,SC1091 32 | # expr may be antiquated, but it is the only solution in some cases. 33 | # shellcheck disable=SC2003 34 | # $() are not fully portable (POSIX != portable). 35 | # shellcheck disable=SC2006 36 | 37 | # Return if test_runner already loaded. 38 | [ -z "${RUNNER_LOADED:-}" ] || return 0 39 | RUNNER_LOADED=0 40 | 41 | RUNNER_ARGV0=`basename "$0"` 42 | RUNNER_SHELLS='/bin/sh ash /bin/bash /bin/dash /bin/ksh /bin/mksh /bin/zsh' 43 | RUNNER_TEST_SUFFIX='_test.sh' 44 | true; RUNNER_TRUE=$? 45 | false; RUNNER_FALSE=$? 46 | 47 | runner_warn() { echo "runner:WARN $*" >&2; } 48 | runner_error() { echo "runner:ERROR $*" >&2; } 49 | runner_fatal() { echo "runner:FATAL $*" >&2; exit 1; } 50 | 51 | runner_usage() { 52 | echo "usage: ${RUNNER_ARGV0} [-e key=val ...] [-s shell(s)] [-t test(s)]" 53 | } 54 | 55 | _runner_tests() { echo ./*${RUNNER_TEST_SUFFIX} |sed 's#\./##g'; } 56 | _runner_testName() { 57 | # shellcheck disable=SC1117 58 | _runner_testName_=`expr "${1:-}" : "\(.*\)${RUNNER_TEST_SUFFIX}"` 59 | if [ -n "${_runner_testName_}" ]; then 60 | echo "${_runner_testName_}" 61 | else 62 | echo 'unknown' 63 | fi 64 | unset _runner_testName_ 65 | } 66 | 67 | main() { 68 | # Find and load versions library. 69 | for _runner_dir_ in . ${LIB_DIR:-lib}; do 70 | if [ -r "${_runner_dir_}/versions" ]; then 71 | _runner_lib_dir_="${_runner_dir_}" 72 | break 73 | fi 74 | done 75 | [ -n "${_runner_lib_dir_}" ] || runner_fatal 'Unable to find versions library.' 76 | . "${_runner_lib_dir_}/versions" || runner_fatal 'Unable to load versions library.' 77 | unset _runner_dir_ _runner_lib_dir_ 78 | 79 | # Process command line flags. 80 | env='' 81 | while getopts 'e:hs:t:' opt; do 82 | case ${opt} in 83 | e) # set an environment variable 84 | key=`expr "${OPTARG}" : '\([^=]*\)='` 85 | val=`expr "${OPTARG}" : '[^=]*=\(.*\)'` 86 | # shellcheck disable=SC2166 87 | if [ -z "${key}" -o -z "${val}" ]; then 88 | runner_usage 89 | exit 1 90 | fi 91 | eval "${key}='${val}'" 92 | eval "export ${key}" 93 | env="${env:+${env} }${key}" 94 | ;; 95 | h) runner_usage; exit 0 ;; # help output 96 | s) shells=${OPTARG} ;; # list of shells to run 97 | t) tests=${OPTARG} ;; # list of tests to run 98 | *) runner_usage; exit 1 ;; 99 | esac 100 | done 101 | shift "`expr ${OPTIND} - 1`" 102 | 103 | # Fill shells and/or tests. 104 | shells=${shells:-${RUNNER_SHELLS}} 105 | [ -z "${tests}" ] && tests=`_runner_tests` 106 | 107 | # Error checking. 108 | if [ -z "${tests}" ]; then 109 | runner_error 'no tests found to run; exiting' 110 | exit 1 111 | fi 112 | 113 | cat <&1; ) 177 | shell_passing=$? 178 | if [ "${shell_passing}" -ne "${RUNNER_TRUE}" ]; then 179 | runner_warn "${shell_bin} not passing" 180 | fi 181 | test "${runner_passing_}" -eq ${RUNNER_TRUE} -a ${shell_passing} -eq ${RUNNER_TRUE} 182 | runner_passing_=$? 183 | done 184 | done 185 | return ${runner_passing_} 186 | } 187 | 188 | # Execute main() if this is run in standalone mode (i.e. not from a unit test). 189 | if [ -z "${SHUNIT_VERSION}" ]; then 190 | main "$@" 191 | fi 192 | --------------------------------------------------------------------------------