├── .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 | [](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 |
--------------------------------------------------------------------------------