├── .github └── stale.yml ├── .gitignore ├── .whitesource ├── CODEOWNERS ├── License.txt ├── README.adoc ├── bin └── shellmock ├── build.sh ├── install.sh ├── sample-bats ├── sample.bats └── sample.sh └── test ├── shellmock.bats └── test.bash /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | 3 | # Number of days of inactivity before an Issue or Pull Request becomes stale 4 | daysUntilStale: 60 5 | 6 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. 7 | # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. 8 | daysUntilClose: 7 9 | 10 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable 11 | exemptLabels: 12 | - pinned 13 | - security 14 | - later 15 | 16 | # Label to use when marking as stale 17 | staleLabel: wontfix 18 | 19 | # Comment to post when marking as stale. Set to `false` to disable 20 | markComment: > 21 | This issue has been automatically marked as stale because it has not had 22 | recent activity. It will be closed if no further activity occurs. Thank you 23 | for your contributions. 24 | 25 | # Limit the number of actions per hour, from 1-30. Default is 30 26 | limitPerRun: 30 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /sample-bats/tmpstubs/ 2 | .idea 3 | *.out 4 | *.err -------------------------------------------------------------------------------- /.whitesource: -------------------------------------------------------------------------------- 1 | { 2 | "checkRunSettings": { 3 | "vulnerableCheckRunConclusionLevel": "failure" 4 | }, 5 | "issueSettings": { 6 | "minSeverityLevel": "LOW" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Owner: https://github.com/ckstettler 2 | * @CaptianQuirk 3 | * @sheosinha 4 | * @tyleroconnell 5 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 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.adoc: -------------------------------------------------------------------------------- 1 | # Due to changes in the priorities, this project is currently not being supported. The project is archived as of 9/17/21 and will be available in a read-only state. Please note, since archival, the project is not maintained or reviewed. # 2 | 3 | :toc: 4 | :toc-placement!: 5 | :toc-position: left 6 | :toclevels: 5 7 | :source-highlighter: highlight 8 | :imagesdir: images 9 | 10 | [.text-center] 11 | image::https://raw.githubusercontent.com/odb/official-bash-logo/master/assets/Logos/Identity/PNG/BASH_logo-transparent-bg-color.png[] 12 | 13 | = Bash Shell Mock 14 | 15 | toc::[] 16 | // use additional conditions to support other environments and extensions 17 | ifdef::env-github[:outfilesuffix: .adoc] 18 | 19 | == Overview 20 | 21 | **Shellmock** is a bash shell script mocking utility/framework. It was written to be a companion of the https://github.com/bats-core/bats-core[Bash Automated Testing System]. 22 | 23 | Typically, mocking frameworks return certain outputs when particular inputs are provided. When enabling mocks for scripts, **Shellmock** defines the "input" as the command line arguments to the script and it defines 24 | the "output" as the exit status and any standard output. In addition it will allow you to provide an alternate stubbed behavior. In most testing scenarios just knowing what was called and the command line args is sufficient, but some 25 | advanced test cases may require new behavior. 26 | 27 | Given the bash command "cp file1 file2", a stub might be defined to return status 0. The stub could also echo "cp file1 file2" to standard out. Using Bats **status**, **line** or **output** variables 28 | you can verify that "cp file1 file2" was written which would confirm that the script was called. The example below is a snippet from a **bats** script. It is assumed that **mycmd** is the script being tested and it calls "cp file1 file2" during its execution. 29 | 30 | ```bats 31 | run mycmd 32 | [ "${status}" = "0" ] 33 | [ "${line[0]}" = "cp file1 file2" ] 34 | ``` 35 | 36 | One approach for stubbing is to create bash scripts with the same names as the real scripts or programs and then override the **PATH** to the stubs and there by short circuiting the real path. Depending on the number of scripts to stub this can be taxing and the stubbing logic can become complex. This is where **Shellmock** helps. **Shellmock** lets you define the mocks inline within the **bats** tests. It creates and manages the stub scripts behind the scenes. The added benefit is that you can view the tests and the test data together making tests easier to manage. 37 | 38 | == Enabling Mocks in your Bats tests 39 | 40 | The first step is to source **shellmock** in the **setup()** function and call the **shellmock_cleanup** function from the bats **teardown** function. These two steps will make **shellmock** functions available 41 | to your testcases and perform appropriate cleanup in between tests. 42 | 43 | Adding the **skipIfNot** function in **setup()** will help with troubleshooting. This will allow 44 | users to define the **TEST_FUNCTION** environment variable to run a single test or a subset of tests and turn on the associated debug logs. 45 | 46 | The link:sample-bats/sample.sh[sample.sh] script provided in the **shellmock** repo provides an example of how to setup **shellmock** in a **bats** script. 47 | ```bash 48 | 49 | setup() 50 | { 51 | # Source the shellmock functions into the shell. 52 | . shellmock 53 | 54 | skipIfNot "$BATS_TEST_DESCRIPTION" 55 | 56 | shellmock_clean 57 | } 58 | 59 | teardown() 60 | { 61 | if [ -z "$TEST_FUNCTION" ]; then 62 | shellmock_clean 63 | fi 64 | } 65 | 66 | ``` 67 | 68 | == Environment Variables 69 | |=== 70 | | Name | Purpose 71 | | TEST_FUNCTION | Shellmock variable that is used to run single tests and control debugging output in shellmock. When 72 | set to the name of a test case then all tmp and debug output is 73 | preserved after the test completes. If you give each test a unique name then only that one test will be executed. You have to leverage the **skipIfNot** function in **setup** or in the 74 | test case to take advantage of the feature. 75 | | SHELLMOCK_V1_COMPATIBILITY | v1.2 introduced logic that handles matching 76 | arguments differently when they contain spaces. As a result old tests 77 | could fail to match. This flag allows the old tests to continue to pass. 78 | |=== 79 | 80 | == Adding Mocks in one of your tests 81 | 82 | In the link:sample-bats/sample.sh[sample.sh] script provided in the **shellmock** repo, the status code from **grep** is used to control the logic flow of the script. We can use Shellmock's **shellmock_expect** command to simulate various success and failures scenarios depending on the arguments passed into the **grep** command. 83 | 84 | **sample.sh** 85 | ```bash 86 | echo "sample line" > sample.out 87 | 88 | grep "sample line" sample.out > /dev/null 89 | if [ $? -ne 0 ]; then 90 | echo "sample not found" 91 | exit 1 92 | fi 93 | 94 | echo "sample found" 95 | ``` 96 | 97 | In the sections that follow we will create some test cases against **sample.sh**. 98 | 99 | === Success Scenario using exact matching 100 | This testcase is simply using bats and calling the real **grep** command. No mocking was involved. This was included to 101 | show that testcases can be a mixture of mocks and real commands. 102 | 103 | ```bash 104 | @test "sample.sh-success" { 105 | 106 | run ./sample.sh 107 | 108 | [ "$status" = "0" ] 109 | 110 | # Validate using lines array. 111 | [ "${lines[0]}" = "sample found" ] 112 | 113 | # Optionally since this is a single line you can use $output 114 | [ "$output" = "sample found" ] 115 | 116 | } 117 | ``` 118 | 119 | === Failure Scenario using exact matching 120 | In this failure scenario we are creating a stub that will return a status of 1 if the **grep** is called in one of the two ways below: 121 | 122 | ``` 123 | grep "sample line" sample.out. 124 | 125 | or 126 | 127 | grep 'sample line' sample.out 128 | 129 | NOTE: These will look the same in the stub's input args. 130 | ``` 131 | 132 | The testcase is using the default match type which is an exact match. 133 | 134 | ```bash 135 | @test "sample.sh-failure" { 136 | 137 | 138 | shellmock_expect grep --status 1 --match '"sample line" sample.out' 139 | 140 | shellmock_debug "starting the test" 141 | 142 | run ./sample.sh 143 | 144 | # Only significant when debugging is occurring it captures bats variables to output files 145 | # to make it easier to see what you are missing. 146 | shellmock_dump 147 | 148 | [ "$status" = "1" ] 149 | [ "$output" = "sample not found" ] 150 | 151 | # called to create the capture array to allow expect verifications. 152 | shellmock_verify 153 | [ "${capture[0]}" = 'grep-stub "sample line" sample.out' ] 154 | 155 | } 156 | ``` 157 | 158 | After the status and output of the script has been validated as needed, then the final piece is to verify that all of the expected mocks were called. The function **shellmock_verify** reads the **shellmock.out** file which contains a record 159 | of all mock invocations. The lines of the file are written to an array variable called **capture**. 160 | 161 | NOTE: Arguments that contain quotes in them were a challenge. The scripting cannot tell the difference between single or double quotes. 162 | Therefore when single quotes are specified in the matching then **shellmock** converts them to double quotes. The capture output will contain double quotes even if 163 | the original script was called with single quotes. 164 | 165 | The original version 1 did not make any distinction and this new feature was added in version 2. In v1 no quotes would appear in the verification output. It would appear like three arguments were passed instead of two. 166 | 167 | === Success Scenario leveraging a partial mock 168 | In this test scenario we are only matching one of the arguments: "sample line". Any filename could be passed and still match the mock. 169 | 170 | ```bash 171 | @test "sample.sh-success-partial-mock" { 172 | 173 | shellmock_expect grep --status 0 --type partial --match '"sample line"' 174 | 175 | run ./sample.sh 176 | 177 | shellmock_dump 178 | 179 | [ "$status" = "0" ] 180 | 181 | # Validate using lines array. 182 | [ "${lines[0]}" = "sample found" ] 183 | 184 | # Optionally since this is a single line you can use $output 185 | [ "$output" = "sample found" ] 186 | 187 | shellmock_verify 188 | [ "${#capture[@]}" = "1" ] 189 | [ "${capture[0]}" = 'grep-stub "sample line" sample.out' ] 190 | 191 | } 192 | ``` 193 | 194 | === Success Scenario demonstrating single vs double quotes 195 | This testcase is the same as the one above except that single quotes where 196 | used around the argument. 197 | 198 | ```bash 199 | @test "sample.sh-success-partial-mock-with-single-quotes" { 200 | 201 | shellmock_expect grep --status 0 --type partial --match "'sample line'" 202 | 203 | run ./sample.sh 204 | 205 | shellmock_dump 206 | 207 | [ "$status" = "0" ] 208 | 209 | # Validate using lines array. 210 | [ "${lines[0]}" = "sample found" ] 211 | 212 | # Optionally since this is a single line you can use $output 213 | [ "$output" = "sample found" ] 214 | 215 | shellmock_verify 216 | [ "${#capture[@]}" = "1" ] 217 | 218 | # Note that it is "sample line" in the capture output. 219 | [ "${capture[0]}" = 'grep-stub "sample line" sample.out' ] 220 | 221 | } 222 | ``` 223 | 224 | === Scenario with RegEx matching 225 | This scenario was easier to show just using grep directly from the bats file. 226 | I created two mocks for grep, one with file names that start with 's' and one with 227 | file names starting with 'b'. The two mocks return 0 and 1 respectively. 228 | 229 | ```bash 230 | @test "sample.sh-mock-with-regex" { 231 | 232 | shellmock_expect grep --status 0 --type regex --match '"sample line" s.*' 233 | shellmock_expect grep --status 1 --type regex --match '"sample line" b.*' 234 | 235 | # The first two patterns leverage the first mock. 236 | run grep "sample line" sample.out 237 | [ "$status" = "0" ] 238 | 239 | run grep "sample line" sample1.out 240 | [ "$status" = "0" ] 241 | 242 | # These two patterns leverage the second mock. 243 | run grep "sample line" bfile.out 244 | [ "$status" = "1" ] 245 | 246 | run grep "sample line" bats.out 247 | [ "$status" = "1" ] 248 | 249 | shellmock_dump 250 | 251 | shellmock_verify 252 | [ "${#capture[@]}" = "4" ] 253 | [ "${capture[0]}" = 'grep-stub "sample line" sample.out' ] 254 | [ "${capture[1]}" = 'grep-stub "sample line" sample1.out' ] 255 | [ "${capture[2]}" = 'grep-stub "sample line" bfile.out' ] 256 | [ "${capture[3]}" = 'grep-stub "sample line" bats.out' ] 257 | 258 | } 259 | ``` 260 | To see a demonstration of the sample tests running, you will first need to install **shellmock** as described later and then follow the steps below. 261 | 262 | ``` 263 | cd sample-bats 264 | bats sample.bats 265 | ``` 266 | 267 | You should expect to see output as follows: 268 | ``` 269 | ✓ sample.sh-success 270 | ✓ sample.sh-failure 271 | ✓ sample.sh-success-partial-mock 272 | ✓ sample.sh-success-partial-mock-with-single-quotes 273 | ✓ sample.sh-mock-with-regex 274 | 275 | 5 tests, 0 failures 276 | 277 | ``` 278 | 279 | The test bats files are another good source for examples as it contains examples of all of the **shellmock** features. 280 | 281 | == Shellmock Functions 282 | This section contains a list of the function provided by **shellmock** also with example usages. 283 | 284 | === skipIfNot 285 | 286 | **skipIfNot** is a very useful function that would be a great addition to **bats** itself. There is currently a PR against **bats** for this ability. For now I have included this function in **shellmock**. This function will allow you to target particular tests while excluding others. 287 | To use it you must define an environment variable called **TEST_FUNCTION**. 288 | 289 | **TEST_FUNCTION** may contain one or more test names delimited by a pipe. In the example below only tests "sample.sh failure" and "sample.sh success" would be executed. All others would be skipped. 290 | 291 | ```bash 292 | $export TEST_FUNCTION="sample.sh-failure|sample.sh-success" 293 | ``` 294 | 295 | The next step is to instrument the tests with **skipIfNot**. **skipIfNot** requires one parameter which is the test name. The recommended approach is to add **skipIfNot** to the **setup** function and leverage the **BATS_TEST_DESCRIPTION** variable. Alternatively, you 296 | can instrument each function with **skipIfNot** and pass in any alias for the test name you like. 297 | 298 | ```bash 299 | setup() 300 | { 301 | # Source the shellmock functions into the shell. 302 | . ../bin/shellmock 303 | 304 | skipIfNot "$BATS_TEST_DESCRIPTION" 305 | 306 | shellmock_clean 307 | } 308 | 309 | @test "sample.sh-failure" { 310 | 311 | . 312 | . 313 | . 314 | 315 | } 316 | ``` 317 | 318 | 319 | === shellmock_clean 320 | 321 | **shellmock_clean** cleans up various temp files used by **shellmock**: 322 | 323 | - the **tmpstubs** directory - that is used to store stub data and scripts 324 | - **shellmock.out** - lists every stub call made 325 | - **shellmock.err** - lists errors encountered the stubs (ie not match found) 326 | 327 | This command should be placed in the **setup** and **teardown** functions. To aid in troubleshooting, I typically recommend only calling it if **TEST_FUNCTION** is not set. This keeps stubs scripts and data from being deleted and allows you to 328 | investigate issues easier. 329 | 330 | A useful practice is to place the cleanup in an if statement and ignore cleanup if the 331 | TEST_FUNCTION variable is set or some other debug variable. 332 | This allows you to have debugging access to the shellmock temp files 333 | for troubleshooting tests. 334 | 335 | === shellmock_debug 336 | 337 | **shellmock_debug** provides a means to capture output statement that might 338 | help troubleshoot testing issues. 339 | 340 | It can be used in the shellmock script or in your bats scripts if useful. 341 | 342 | The output is captured in shellmock-debut.out and will only be available if 343 | TEST_FUNCTION is set. 344 | 345 | === shellmock_dump 346 | 347 | **shellmock_dump** can prove quite useful to troubleshoot testing issues. It 348 | will dump the contains of the **bats** **$lines** variable which basically equates to 349 | any standard out that has been generated by the script under test. 350 | 351 | The output is captured in shellmock-debug.out and will only be available if 352 | **TEST_FUNCTION** is set. 353 | 354 | === shellmock_verify 355 | 356 | **shellmock_verify** converts all **shellmock.out** lines into a variable array called **capture**. This allows testers to verify which stubs were called and in what order. 357 | 358 | ```bash 359 | @test "sample.sh-failure" { 360 | . 361 | . 362 | . 363 | shellmock_verify 364 | [ "${capture[0]}" = "some-stub arg1 arg2" ] 365 | [ "${capture[1]}" = "some-stub2 arg1 arg2" ] 366 | } 367 | ``` 368 | 369 | === shellmock_expect 370 | 371 | **shellmock_expect** allows you specify the command to be mocked and how the function should be mocked. The behavior can be in terms of status code, output to echo or a custom 372 | behavior that you provide. 373 | 374 | ```bash 375 | usage: shellmock_expect [cmd] [--type partial | exact | regex ] [--status #] --match [arg1 arg2 arg3...] [--exec cmdstring ] [--source cmdstring] [--output texttoecho] 376 | ``` 377 | [cols="35%,50%,10%"] 378 | |=== 379 | |**Item**|**Description**|**Required?** 380 | |cmd|unix command to mock|Yes. 381 | |-t,--type|Type of argument list matching: **partial**, **exact** **regex**|No. Defaults to **exact** 382 | |-T,--stdin-match-type|Type of stdin matching: **partial**, **exact**, or **regex** | **exact** 383 | |-m,--match,--match-args|Arguments passed to cmd that indicate a match to mock.|No. 384 | |-M,--match-stdin|stdin data that is expected to be considered a match.|No. 385 | |-e,--exec|Command string to execute for custom behavior.|No. 386 | |-S,--source|Command string to source.|No. 387 | |-o,--output|Text string to echo if there is a match.|No. 388 | |-s,--status|status code to return|No. Defaults to 0 389 | |=== 390 | 391 | Matching can be defined based on the argument list or the stdin data stream. When both **--match** and **--match-stdin** are provided in an expectation then 392 | it becomes an AND of the two conditions. 393 | 394 | **shellmock_expect** supports returning a single or multiple responses for a given match criteria. The responses will be returned in the order defined. Once all response are seen the last response will be returned indefinitely. 395 | 396 | ==== examples 397 | 398 | These examples assume that the "grep string1 file1" is the unix command being mocked to be used in other scripts under test. For 399 | simplicity of understanding, I am calling the **grep** command directly from bats to show what the behavior would look like. 400 | 401 | ===== Basic mock with success status 402 | This example mocks **grep** to return a 0 status when the input is "string1 file1". 403 | In order to verify that the function was called you would need to use **shellmock_verify** and do a comparison. 404 | 405 | ```bash 406 | shellmock_expect grep --match "string1 file2" 407 | 408 | run grep string1 file2 409 | [ "$status" = "0" ] 410 | 411 | shellmock_verify 412 | [ "${capture[@]} = 1 ] 413 | [ "${capture[0]} = "grep-stub string1 file2" ] 414 | 415 | ``` 416 | 417 | ===== Basic mock with failed status 418 | 419 | This scenario show a status of 1 being returned for the same inputs. 420 | 421 | ```bash 422 | shellmock_expect grep --status 1 --match "string1 file2" 423 | 424 | run grep string1 file2 425 | [ "$status" = "1" ] 426 | 427 | shellmock_verify 428 | [ "${capture[@]} = 1 ] 429 | [ "${capture[0]} = "grep-stub string1 file2" ] 430 | 431 | ``` 432 | 433 | ===== Mock with partial mock 434 | 435 | If the **grep** command is run it will return a status 0 if arg1 is "string1" regardless of the rest of the args. Use **shellmock_verify** verify each invocation if desired. 436 | 437 | ```bash 438 | shellmock_expect grep --status 0 --type partial --match string1 439 | 440 | run grep string1 file2 441 | [ "$status" = "0" ] 442 | 443 | run grep string1 file3 444 | [ "$status" = "0" ] 445 | 446 | shellmock_verify 447 | [ "${capture[@]} = 2 ] 448 | [ "${capture[0]} = "grep-stub string1 file2" ] 449 | [ "${capture[1]} = "grep-stub string1 file3" ] 450 | 451 | ``` 452 | 453 | ===== Mock with whitespace in the parameters 454 | 455 | If the **grep** command is run by the script under test it will return a status 0 if arg1 is "string1" regardless of the rest of the args. In order 456 | to verify that the function was called you would need to use **shellmock_verify** and do a comparison. 457 | 458 | If the --match argument were "'string1 string2' file", where the double quotes and single quotes are 459 | swapped, then shellmock treats the string as if it were '"string1 string2" file'. 460 | 461 | ```bash 462 | shellmock_expect grep --status 0 --type partial --match '"string1 string2"' 463 | 464 | run grep "string1 string2" file2 465 | [ "$status" = "0" ] 466 | 467 | run grep "string1 string2" file3 468 | [ "$status" = "0" ] 469 | 470 | shellmock_verify 471 | [ "${capture[@]} = 2 ] 472 | [ "${capture[0]} = 'grep-stub "string1 string2" file2' ] 473 | [ "${capture[1]} = 'grep-stub "string1 string2" file3' ] 474 | 475 | ``` 476 | 477 | ===== Mock with regex 478 | This example shows the use of regex match type. 479 | 480 | The regular expression is evaluated by the *AWK* command. Refer to *AWK* documentation for details. Any *AWK* 481 | special characters will need to be escaped in the match criteria. 482 | 483 | ```bash 484 | shellmock_expect grep --status 0 --type regex --match "s.* f.*" 485 | 486 | run grep string1 file2 487 | [ "$status" = "0" ] 488 | 489 | run grep string1 file3 490 | [ "$status" = "0" ] 491 | 492 | shellmock_verify 493 | [ "${capture[0]} = "grep-stub string1 file2" ] 494 | [ "${capture[1]} = "grep-stub string1 file3" ] 495 | 496 | ``` 497 | 498 | ===== Mock with custom script 499 | 500 | If the **grep** command is run by a script under test it will return a status 0 if arg1 is "string1" and arg2 is "file1". It will also write "mycustom string1 file1" to stdout. The use of the {} 501 | in the --exec script will cause any arguments passed to the mocked script to be expanded in place of the braces as seen below. 502 | 503 | For this example you can verify the **status**, the **output**/**line**, and the **capture** variables. 504 | 505 | ```bash 506 | shellmock_expect grep --status 0 --type partial --match "string1" --exec "echo mycustom {}" 507 | 508 | run grep string1 file1 509 | 510 | shellmock_dump 511 | [ "$status" = "0" ] 512 | [ "${lines[0]}" = "mycustom string1 file1" ] 513 | 514 | run grep string1 file2 515 | shellmock_dump 516 | [ "$status" = "0" ] 517 | [ "${lines[0]}" = "mycustom string1 file2" ] 518 | 519 | shellmock_verify 520 | [ "${#capture[@]}" = "2" ] 521 | [ "${capture[0]}" = 'grep-stub string1 file1' ] 522 | [ "${capture[1]}" = 'grep-stub string1 file2' ] 523 | 524 | ``` 525 | 526 | This example shows the use of **echo** as the script, however, it could also be any user defined script that you 527 | want in place of the mocked command. The {} braces are a way to forward arguments from the mock script into your script. 528 | 529 | == Installing Bash Shell Mock from source 530 | 531 | Check out a copy of the **shellmock** repository. Then, either add the **shellmock** 532 | `bin` directory to your `$PATH`, or run the provided `install.sh` 533 | command with the location to the prefix in which you want to install 534 | **Shellmock**. For example, to install Bats into `/usr/local`, 535 | 536 | $ git clone [repository_url] 537 | $ cd bash_shell_mock 538 | $ ./install.sh /usr/local 539 | 540 | Note that you may need to run `install.sh` with `sudo` if you do not 541 | have permission to write to the installation prefix. 542 | 543 | == Debugging Tests 544 | 545 | If the **shellmock_clean** function is short circuited then the temp files will remain. 546 | 547 | shellmock.out contains all of the mock commands that have been run and is used by the 548 | **shellmock_verify** command. 549 | 550 | If you following the sample and set TEST_FUNCTION then the tmpstubs directory will remain and not be cleaned up. Inside that 551 | directory you will find err out and debug files. 552 | 553 | For each file there will be two .tmp data files: 554 | 555 | - shellmock.out - shows which mocks were executed and their parameters 556 | - shellmock.err - shows the results of the matches 557 | - shellmock-debug.out - shows the results of what would have been sent to standard out array $lines which bats also allows you to match on. 558 | - *.playback.capture.tmp - shows defines each of the expectations. There will be on of these files for every mocked script. 559 | - *.playback.state.tmp - keeps track of multiple responses for the same mock 560 | 561 | == Limitations 562 | 563 | The **Shellmock** mocking approach does have impact on how write your scripts. The key to using any mocking in unix scripts is that the scripts must be reached via the PATH variable and you can not use 564 | full or relative pathing to the script. **Shellmock** uses the PATH variable to short circuit calling the "real" script or program. 565 | 566 | == Dependencies 567 | This project requires some additional dependencies: 568 | 569 | - https://github.com/koalaman/shellcheck/wiki[shellcheck] - linting tool required for shellmock development. 570 | - https://github.com/bats-core/bats-core[Bash Automated Testing System] - required to use shellmock for testing. 571 | 572 | == Linting Shellcheck 573 | Shellcheck has been integrated into the build process to provide the posix shell linting capabilities. 574 | 575 | There is one globally disabled shellcheck rule related to the use of $?. It was quite rampant and it seemed good enough for now to ignore this one. 576 | ``` 577 | #!/usr/bin/env bash 578 | #shellcheck disable=SC2181 579 | ``` 580 | 581 | Others are disabled as required such as the use of single quotes in awk commands that wrap the awk scripts. In those cases it was necessary to ignore SC2016 since we expect awk $ variables NOT to be expanded by the shell. 582 | 583 | Shellcheck allows file, function and line item exclusions. In this project we favor line item exclusions. To add a line item exception place the exclusion directly about the line of code. 584 | 585 | ``` 586 | #shellcheck disable=SC2016 587 | AWK_STDIN_SCRIPT='BEGIN{FS="@@"}{if ($4=="E" && ($5... 588 | ``` 589 | 590 | It may make sense, however, to exclude at a higher level if for some reason there is a high number of expected failures as is the case in the *shellmock_expect()* function. That function generates a shell script itself so shellcheck has a field day in that one. As a result we excluded two items within the function. 591 | 592 | ``` 593 | #shellcheck disable=SC2016,SC2129 594 | shellmock_expect() 595 | ``` 596 | 597 | == Looking up Error Details 598 | https://github.com/koalaman/shellcheck/wiki/Checks[Shellcheck Errors] describes how to lookup a particular error. There is a page created for each one. You access the page by appending the error code that was reported by shellcheck. 599 | ``` 600 | https://github.com/koalaman/shellcheck/wiki/Checks/[error] 601 | ``` 602 | 603 | At the time of this writing they also provided a link in the page to take you 604 | to an enumerated list of all errors. 605 | 606 | == Contributors 607 | We welcome Your interest in Capital One’s Open Source Projects (the “Project”). Any Contributor to the Project must accept and sign an Agreement indicating agreement to the license terms below. Except for the license granted in this Agreement to Capital One and to recipients of software distributed by Capital One, You reserve all right, title, and interest in and to Your Contributions; this Agreement does not impact Your rights to use Your own Contributions for any other purpose. 608 | 609 | https://docs.google.com/forms/d/19LpBBjykHPox18vrZvBbZUcK6gQTj7qv1O5hCduAZFU/viewform[Sign the Individual Agreement] 610 | 611 | https://docs.google.com/forms/d/e/1FAIpQLSeAbobIPLCVZD_ccgtMWBDAcN68oqbAJBQyDTSAQ1AkYuCp_g/viewform?usp=send_form[Sign the Corporate Agreement] 612 | 613 | == Code of Conduct 614 | This project adheres to the https://developer.capitalone.com/resources/code-of-conduct[Open Code of Conduct]. By participating, you are expected to honor this code. 615 | 616 | -------------------------------------------------------------------------------- /bin/shellmock: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | #shellcheck disable=SC2181 3 | #-------------------------------------------------------------------------------- 4 | # SPDX-Copyright: Copyright (c) Capital One Services, LLC 5 | # SPDX-License-Identifier: Apache-2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | #--------------------------------------------------------------------------------- 19 | 20 | #--------------------------------------------------------------------------------- 21 | # File: shellmock 22 | # Purpose: 23 | # This script provides mocking features to test the various bash scripts. 24 | # They are made available in your current shell by sourcing this script. 25 | # i.e. source shellmock 26 | #--------------------------------------------------------------------------------- 27 | 28 | #--------------------------------------- 29 | # Helper function to do targeted testing 30 | #--------------------------------------- 31 | skipIfNot() 32 | { 33 | local doskip 34 | if [ -n "$TEST_FUNCTION" ]; then 35 | doskip=$(echo "$TEST_FUNCTION|" | awk 'BEGIN{RS="|"}{ if ($0=="'"$1"'") print "true";}') 36 | if [ "$doskip" != "true" ]; then 37 | skip 38 | fi 39 | fi 40 | } 41 | 42 | #---------------------------------------------------------------------------- 43 | # This function creates a single string containing all $*. In the process 44 | # it checks to see if an arg contains a space and if so then it places 45 | # double quotes around the argument. 46 | # 47 | # Note: Even if single quotes were originally used the string and 48 | # any matching in the ${capture{@]} arrary will have double quotes around 49 | # the arguments. 50 | #---------------------------------------------------------------------------- 51 | shellmock_normalize_args() { 52 | shellmock_debug "shellmock_normalize_args: before *$**" 53 | 54 | #----------------------------------------------------------------------- 55 | # Shellcheck warnings have been disable below. I think for good reason. 56 | # The function is all about not losing knowledge about which strings 57 | # are quoted in the argument list. In the future I may revisit this. 58 | # For now I just know I spent alot of time getting it to work and 59 | # dont want to break it now. 60 | #----------------------------------------------------------------------- 61 | local re="[[:space:]]+" 62 | args="" 63 | for arg in "${@}";do 64 | if [[ $arg =~ $re ]]; then 65 | shellmock_debug "shellmock_normalize_args: found space: $arg" 66 | # shellcheck disable=SC2089 67 | args="$args \"$arg\"" 68 | shellmock_debug "shellmock_normalize_args: args: $args" 69 | else 70 | args="$args $arg" 71 | fi 72 | shellmock_debug "shellmock_normalize_args: args: $args" 73 | done 74 | # shellcheck disable=SC2016 75 | args=$(echo "$args"|$AWK '{$1=$1;print}') 76 | shellmock_debug "shellmock_normalize_args: after *$args*" 77 | echo "$args" 78 | 79 | } 80 | #--------------------------------------------------------------------- 81 | # The variables are being pas$SED to sed and / are important to sed 82 | # so before we send to sed and write to the detour.properties we will 83 | # use sed to replace any / with \/ then the later sed will succede. 84 | #--------------------------------------------------------------------- 85 | shellmock_escape_special_chars() 86 | { 87 | shellmock_debug "shellmock_escape_special_chars: args: $*" 88 | $ECHO "$*" | $SED -e 's/\//\\\//g' -e 's/\[/\\\[/g' -e 's/\]/\\\]/g' 89 | } 90 | #--------------------------------------------------------------------- 91 | # The variables are being pas$SED to sed and / are important to sed 92 | # so before we send to sed and write to the detour.properties we will 93 | # use sed to replace any / with \/ then the later sed will succede. 94 | #--------------------------------------------------------------------- 95 | shellmock_escape_escapes() 96 | { 97 | shellmock_debug "shellmock_escape_escapes: args: $*" 98 | $ECHO "$*" | $SED -e 's/\\/\\\\/g' 99 | } 100 | #-------------------------------------------------------------------------------------------------------------------------------------- 101 | # This funciton is used to mock bash scripts. It maps inputs to outputs and if a given script is 102 | # expecting varying results then they are played back in the order the expects were given. 103 | # 104 | # inputs are assumed to be the function name plus command line arguments. 105 | # outputs are the given string provided. 106 | # 107 | # usage: shellmock [command] --source [command to source] --exec [command to exec] --match [args to match] --output [output to write] 108 | # --source -- specifies the script to soure if the args match 109 | # --exec -- specifies the script to execute if the args match 110 | # --match -- arguments to command that should be used to match the record 111 | # --output -- output that should be written to standard out if the args match 112 | # --type -- type of match partial or exact 113 | # 114 | # NOTE: --source --exec and --output should be mutually exclusive. We should never use more than one at time in the the same expect 115 | #-------------------------------------------------------------------------------------------------------------------------------------- 116 | 117 | #------------------------------------------------------------------- 118 | # This function puts \ in front of " so that it can be passed to awk 119 | #------------------------------------------------------------------- 120 | shellmock_escape_quotes() 121 | { 122 | POSIXLY_CORRECT=1 $ECHO "$*" | $SED -e 's/"/\\"/g' 123 | } 124 | 125 | #------------------------------------ 126 | # Use awk to dete$RMine the match list 127 | #------------------------------------ 128 | mock_capture_match() 129 | { 130 | local MATCH 131 | MATCH=$(shellmock_escape_quotes "$1") 132 | 133 | local IN_MATCH 134 | IN_MATCH=$(shellmock_escape_quotes "$2") 135 | 136 | shellmock_debug "mock_capture_match: cmd: *$cmd* MATCH: *$MATCH* IN_MATCH: *$IN_MATCH*" 137 | 138 | #shellcheck disable=SC2016 139 | local AWK_ARG_SCRIPT='BEGIN{FS="@@"}{if ($5=="E" && ($1 == "'"$MATCH"'")) print; if ($5=="P" && index("'"$MATCH"'",$1)) print; if ($5=="X" && match("'"$MATCH"'", $1)) print}' 140 | shellmock_debug "mock_capture_match: awk arg matcher script: $AWK_ARG_SCRIPT" 141 | 142 | #shellcheck disable=SC2016 143 | local AWK_STDIN_SCRIPT='BEGIN{FS="@@"}{if ($6=="E" && ($7 == "'"$IN_MATCH"'")) print; if ($6=="P" && index("'"$IN_MATCH"'",$7)) print; if ($6=="X" && match("'"$IN_MATCH"'", $7)) print}' 144 | shellmock_debug "mock_capture_match: awk stdin matcher script: $AWK_STDIN_SCRIPT" 145 | 146 | $CAT "$BATS_TEST_DIRNAME/tmpstubs/$cmd.playback.capture.tmp" | $AWK "$AWK_ARG_SCRIPT" | $AWK "$AWK_STDIN_SCRIPT" 147 | } 148 | 149 | #------------------------------------ 150 | # Use awk to determine the match list 151 | #------------------------------------ 152 | mock_state_match() 153 | { 154 | local MATCH 155 | MATCH=$(shellmock_escape_quotes "$1") 156 | local IN_MATCH 157 | IN_MATCH=$(shellmock_escape_quotes "$2") 158 | shellmock_debug "mock_state_match: cmd: $cmd MATCH: *$MATCH* IN_MATCH: *$IN_MATCH*" 159 | local AWK_ARG_SCRIPT 160 | #shellcheck disable=SC2016 161 | AWK_ARG_SCRIPT='BEGIN{FS="@@"}{if ($3=="E" && ($1 == "'"$MATCH"'")) print; if ($3=="P" && index("'"$MATCH"'",$1)) print;if ($3=="X" && match("'"$MATCH"'", $1)) print}' 162 | shellmock_debug "mock_state_match: awk arg match cmd: $AWK_ARG_SCRIPT" 163 | 164 | local AWK_STDIN_SCRIPT 165 | #shellcheck disable=SC2016 166 | AWK_STDIN_SCRIPT='BEGIN{FS="@@"}{if ($4=="E" && ($5 == "'"$IN_MATCH"'")) print $2; if ($4=="P" && index("'"$IN_MATCH"'",$5)) print $2;if ($4=="X" && match("'"$IN_MATCH"'", $5)) print $2}' 167 | shellmock_debug "mock_state_match: awk stdin match cmd: $AWK_STDIN_SCRIPT" 168 | 169 | local rec 170 | rec=$($CAT "$BATS_TEST_DIRNAME/tmpstubs/$cmd.playback.state.tmp" | $AWK "$AWK_ARG_SCRIPT" | $AWK "$AWK_STDIN_SCRIPT" | $TAIL -1) 171 | shellmock_debug "mock_state_match: rec: *$rec*" 172 | $ECHO "$rec" 173 | } 174 | 175 | #------------------------------------------------------------------------ 176 | # Create the mock stub and write mock expections and actions to tmp files 177 | #------------------------------------------------------------------------ 178 | #shellcheck disable=SC2016,SC2129 179 | shellmock_expect() 180 | { 181 | #--------------------------------------- 182 | # The first arg is the command basename. 183 | #--------------------------------------- 184 | local cmd 185 | cmd=$1 186 | shift 187 | 188 | local FORWARD="" 189 | local MATCH="" 190 | local OUTPUT="" 191 | local STATUS=0 192 | local MTYPE="E" 193 | local IN_MTYPE="E" 194 | 195 | 196 | #-------------------------------------------------------------- 197 | # read the switches so we know what to do 198 | # --exec -- forward to another command 199 | # -m,--match,--match-args -- arg list to the base command for matching 200 | # -M,--match-stdin -- stdin contents for arg matching 201 | # --output -- standard out that should be echoed 202 | # --status -- exit status to return 203 | # -t,--type,--args-match-type -- exact or partial of arg list 204 | # -T,--stdin-match-type -- exact or partial match of stdin 205 | #-------------------------------------------------------------- 206 | while [[ $# -gt 1 ]] 207 | do 208 | local key="$1" 209 | case $key in 210 | -S|--source) 211 | SOURCE="$2" 212 | shift # past argument 213 | ;; 214 | -e|--exec) 215 | FORWARD="$2" 216 | shift # past argument 217 | ;; 218 | -t|--type|--args-match-type) 219 | if [ "$2" = "partial" ];then 220 | MTYPE="P" 221 | elif [ "$2" = "exact" ]; then 222 | MTYPE="E" 223 | elif [ "$2" = "regex" ]; then 224 | MTYPE="X" 225 | else 226 | shellmock_capture_err "mock_expect type $2 not valid should be exact or partial" 227 | return 1 228 | fi 229 | shift # past argument 230 | ;; 231 | -T|--stdin-match-type) 232 | if [ "$2" = "partial" ];then 233 | IN_MTYPE="P" 234 | elif [ "$2" = "exact" ]; then 235 | IN_MTYPE="E" 236 | elif [ "$2" = "regex" ]; then 237 | IN_MTYPE="X" 238 | else 239 | shellmock_capture_err "mock_expect type $2 not valid should be exact or partial" 240 | return 1 241 | fi 242 | shift # past argument 243 | ;; 244 | -m|--match|--match-args) 245 | MATCH="$2" 246 | shift # past argument 247 | ;; 248 | -M|--match-stdin) 249 | MATCH_IN="$2" 250 | shift # past argument 251 | ;; 252 | -o|--output) 253 | #--------------------------------------------------------- 254 | # Preserve any newlines in the string by replacing with %% 255 | # but also remove the trailing %% that awk puts there. 256 | #--------------------------------------------------------- 257 | OUTPUT=$($ECHO "$2" | $AWK '$1=$1' ORS='%%' | $SED 's/%%$//g') 258 | shift # past argument 259 | ;; 260 | -s|--status) 261 | STATUS="$2" 262 | shift # past argument 263 | ;; 264 | *) 265 | # unknown option 266 | return 1 267 | ;; 268 | esac 269 | shift # past argument or value 270 | done 271 | 272 | shellmock_debug "shellmock_expect: FORWARD=$FORWARD" 273 | shellmock_debug "shellmock_expect: MATCH=$MATCH" 274 | shellmock_debug "shellmock_expect: OUTPUT=$OUTPUT" 275 | shellmock_debug "shellmock_expect: STATUS=$STATUS" 276 | shellmock_debug "shellmock_expect: MTYPE=$MTYPE" 277 | shellmock_debug "shellmock_expect: MATCH_IN=$MATCH_IN" 278 | shellmock_debug "shellmock_expect: IN_MTYPE=$IN_MTYPE" 279 | 280 | #----------------------------------------------------------- 281 | # If the command has not been stubbed then generate the stub 282 | #----------------------------------------------------------- 283 | if [ ! -f "$BATS_TEST_DIRNAME/tmpstubs/$cmd" ]; then 284 | 285 | $MKDIR -p "$BATS_TEST_DIRNAME/tmpstubs" 286 | $TOUCH "$BATS_TEST_DIRNAME/tmpstubs/$cmd" 287 | $ECHO "#!/usr/bin/env bash" >> "$BATS_TEST_DIRNAME/tmpstubs/$cmd" 288 | $ECHO "export BATS_TEST_DIRNAME=\"$BATS_TEST_DIRNAME\"" >> "$BATS_TEST_DIRNAME/tmpstubs/$cmd" 289 | $ECHO ". shellmock" >> "$BATS_TEST_DIRNAME/tmpstubs/$cmd" 290 | $ECHO 'shellmock_debug shellmock_stub: $0-stub: args: "$*"' >> "$BATS_TEST_DIRNAME/tmpstubs/$cmd" 291 | $ECHO "if [ -p /dev/stdin ]; then" >> "$BATS_TEST_DIRNAME/tmpstubs/$cmd" 292 | $ECHO " let cnt=0" >> "$BATS_TEST_DIRNAME/tmpstubs/$cmd" 293 | $ECHO " while IFS= read line; do" >> "$BATS_TEST_DIRNAME/tmpstubs/$cmd" 294 | $ECHO ' stdin[$cnt]=$line' >> "$BATS_TEST_DIRNAME/tmpstubs/$cmd" 295 | $ECHO ' let cnt=$cnt+1' >> "$BATS_TEST_DIRNAME/tmpstubs/$cmd" 296 | $ECHO " done" >> "$BATS_TEST_DIRNAME/tmpstubs/$cmd" 297 | $ECHO ' if [ $cnt -gt 0 ]; then stdin[$cnt]=" | "; fi' >> "$BATS_TEST_DIRNAME/tmpstubs/$cmd" 298 | $ECHO 'else' >> "$BATS_TEST_DIRNAME/tmpstubs/$cmd" 299 | $ECHO ' shellmock_debug shellmock_stub: $0-stub: no stdin' >> "$BATS_TEST_DIRNAME/tmpstubs/$cmd" 300 | $ECHO "fi" >> "$BATS_TEST_DIRNAME/tmpstubs/$cmd" 301 | $ECHO 'shellmock_debug shellmock_stub: $0-stub: stdin: "${stdin[@]}"' >> "$BATS_TEST_DIRNAME/tmpstubs/$cmd" 302 | if [ -z "$SHELLMOCK_V1_COMPATIBILITY" ]; then 303 | $ECHO 'shellmock_capture_cmd "${stdin[@]}"'"${cmd}"'-stub "$(shellmock_normalize_args "$@")"' >> "$BATS_TEST_DIRNAME/tmpstubs/$cmd" 304 | $ECHO "shellmock_replay $cmd "'"`shellmock_normalize_args "$@"`" "${stdin[@]}"' >> "$BATS_TEST_DIRNAME/tmpstubs/$cmd" 305 | else 306 | $ECHO 'shellmock_capture_cmd '"${cmd}"'-stub "$*"' >> "$BATS_TEST_DIRNAME/tmpstubs/$cmd" 307 | $ECHO "shellmock_replay $cmd "'"$*"' >> "$BATS_TEST_DIRNAME/tmpstubs/$cmd" 308 | fi 309 | $ECHO 'status=$?' >> "$BATS_TEST_DIRNAME/tmpstubs/$cmd" 310 | $ECHO 'if [ $status -ne 0 ]; then' >> "$BATS_TEST_DIRNAME/tmpstubs/$cmd" 311 | $ECHO ' shellmock_capture_err $0 failed ' >> "$BATS_TEST_DIRNAME/tmpstubs/$cmd" 312 | $ECHO ' exit $status' >> "$BATS_TEST_DIRNAME/tmpstubs/$cmd" 313 | $ECHO 'fi' >> "$BATS_TEST_DIRNAME/tmpstubs/$cmd" 314 | $CHMOD 755 "$BATS_TEST_DIRNAME/tmpstubs/$cmd" 315 | fi 316 | 317 | #--------------------------------------------------------------- 318 | # There are two record formats one for forwards and one for 319 | # matching inputs and outputs 320 | # forward implies executing an alternative command vs mocking 321 | # 322 | #--------------------------------------------------------------- 323 | if [ "$MTYPE" != "X" ] && [ -z "$SHELLMOCK_V1_COMPATIBILITY" ]; then 324 | MATCH_NORM=$(eval shellmock_normalize_args "$MATCH") 325 | else 326 | MATCH_NORM=$MATCH 327 | fi 328 | 329 | # Field definitions for the capture file 330 | # $1 - arg match criteria 331 | # $2 - type of expectation (forward, source, or output) 332 | # $3 - data related to the expectation type: script to foward to, the script to source, or the output to display 333 | # $4 - status value to return 334 | # $5 - type of argument matcher 335 | # $6 - type of stdin matcher 336 | # $7 - stdin match criteria 337 | 338 | shellmock_debug "shellmock_expect: normalized arg match string *$MATCH* as *$MATCH_NORM*" 339 | if [ "$FORWARD" != "" ]; then 340 | $ECHO "$MATCH_NORM@@FORWARD@@$FORWARD@@0@@$MTYPE@@$IN_MTYPE@@$MATCH_IN" >> "$BATS_TEST_DIRNAME/tmpstubs/$cmd.playback.capture.tmp" 341 | elif [ "$SOURCE" != "" ]; then 342 | $ECHO "$MATCH_NORM@@SOURCE@@$SOURCE@@0@@$MTYPE@@$IN_MTYPE@@$MATCH_IN" >> "$BATS_TEST_DIRNAME/tmpstubs/$cmd.playback.capture.tmp" 343 | else 344 | $ECHO "$MATCH_NORM@@OUTPUT@@$OUTPUT@@$STATUS@@$MTYPE@@$IN_MTYPE@@$MATCH_IN" >> "$BATS_TEST_DIRNAME/tmpstubs/$cmd.playback.capture.tmp" 345 | fi 346 | 347 | # Field definitions for the state file: 348 | # $1 - argument match criteria 349 | # $2 - which occurrence is the active when there are multiple responses to playback 350 | # $3 - argument match type 351 | # $4 - stdin match type 352 | # $5 - stdin match criteria 353 | $ECHO "$MATCH_NORM@@1@@$MTYPE@@$IN_MTYPE@@$MATCH_IN" >> "$BATS_TEST_DIRNAME/tmpstubs/$cmd.playback.state.tmp" 354 | 355 | } 356 | 357 | 358 | #---------------------------------------- 359 | # This funciton is used by the mock stubs 360 | # usage: shellmock_replay [cmd] 361 | #---------------------------------------- 362 | shellmock_replay() 363 | { 364 | local cmd="$1" 365 | local match="$2" 366 | local in_match="$3" 367 | 368 | shellmock_debug "shellmock_replay: cmd: $cmd match: *$match* in_match: *$in_match*" 369 | 370 | 371 | local rec 372 | typeset -i rec 373 | 374 | local count 375 | typeset -i count 376 | 377 | #------------------------------------------------------------------------------------- 378 | # Get the record index. If there are multiple matches then they are returned in order 379 | #------------------------------------------------------------------------------------- 380 | rec=$(mock_state_match "$match" "$in_match") 381 | if [ "$rec" = "0" ]; then 382 | shellmock_capture_err "No record match found stdin:*$in_match* cmd:$cmd args:*$match*" 383 | return 99 384 | fi 385 | 386 | shellmock_debug "shellmock_replay: matched rec: $rec" 387 | count=$(mock_capture_match "$match" "$in_match"| $WC -l) 388 | entry=$(mock_capture_match "$match" "$in_match"| $HEAD -"${rec}" | $TAIL -1) 389 | 390 | shellmock_debug "shellmock_replay: count: $count entry: $entry" 391 | #------------------------------- 392 | # If no entry is found then fail 393 | #------------------------------- 394 | if [ -z "$entry" ]; then 395 | shellmock_capture_err "No match found for stdin: *$in_match* cmd: *$cmd* - args: *$match*" 396 | exit 99 397 | fi 398 | local action 399 | local output 400 | local status 401 | local mtype 402 | local in_mtype 403 | 404 | #shellcheck disable=SC2016 405 | action=$($ECHO "$entry" | $AWK 'BEGIN{FS="@@"}{print $2}') 406 | 407 | #shellcheck disable=SC2016 408 | output=$($ECHO "$entry" | $AWK 'BEGIN{FS="@@"}{print $3}') 409 | 410 | #shellcheck disable=SC2016 411 | status=$($ECHO "$entry" | $AWK 'BEGIN{FS="@@"}{print $4}') 412 | 413 | #shellcheck disable=SC2016 414 | mtype=$($ECHO "$entry" | $AWK 'BEGIN{FS="@@"}{print $5}') 415 | 416 | #shellcheck disable=SC2016 417 | in_mtype=$($ECHO "$entry" | $AWK 'BEGIN{FS="@@"}{print $6}') 418 | 419 | shellmock_debug "shellmock_replay: action: $action" 420 | shellmock_debug "shellmock_replay: output: $output" 421 | shellmock_debug "shellmock_replay: status: $status" 422 | shellmock_debug "shellmock_replay: mtype: $mtype" 423 | shellmock_debug "shellmock_replay: in_mtype: $in_mtype" 424 | 425 | #-------------------------------------------------------------------------------------- 426 | # If there are multiple responses for a given match then keep track of a response index 427 | #-------------------------------------------------------------------------------------- 428 | if [ "$count" -gt 1 ]; then 429 | shellmock_debug "shelmock_replay: multiple matches: $count" 430 | $CP "$BATS_TEST_DIRNAME/tmpstubs/$1.playback.state.tmp" "$BATS_TEST_DIRNAME/tmpstubs/$1.playback.state.bak" 431 | # This script updates index for the next mock when there is more than one response value. 432 | #shellcheck disable=SC2016 433 | $CAT "$BATS_TEST_DIRNAME/tmpstubs/$1.playback.state.bak" | $AWK 'BEGIN{FS="@@"}{ if ((($3=="E" && $1=="'"$match"'")||($3=="P"&& index("'"$match"'",$1))||($3=="X" && match("'"$match"'",$1))) && (($4=="E" && $5=="'"$in_match"'")||($4=="P"&& index("'"$in_match"'",$5))||($4=="X" && match("'"$in_match"'",$5)))) printf("%s@@%d@@%s@@%s@@%s\n",$1,$2+1,$3,$4,$5) ; else printf("%s@@%d@@%s@@%s@@%s\n",$1,$2,$3,$4,$5) }' > "$BATS_TEST_DIRNAME/tmpstubs/$1.playback.state.tmp" 434 | fi 435 | 436 | #-------------------------------------------------------------- 437 | # If this is a command forwarding request then call the command 438 | #-------------------------------------------------------------- 439 | if [ "$action" = "SOURCE" ]; then 440 | shellmock_debug "shellmock_replay: perform: SOURCE *. $output*" 441 | # shellcheck disable=SC1090 442 | . "$output" 443 | return $? 444 | 445 | elif [ "$action" = "FORWARD" ]; then 446 | local tmpcmd 447 | $ECHO "$output" | $GREP "{}" > /dev/null 448 | 449 | # SUBSTITION Feature 450 | # If {} is present that means pass the match pattern into the exec script. 451 | if [ $? -eq 0 ]; then 452 | local tmpmatch 453 | tmpmatch=$(shellmock_escape_special_chars "$match") 454 | tmpcmd=$($ECHO "$output" | $SED "s/{}/$tmpmatch/g") 455 | else 456 | tmpcmd=$output 457 | fi 458 | shellmock_debug "shellmock_replay: perform: FORWARD *$tmpcmd*" 459 | eval "$tmpcmd" 460 | return $? 461 | 462 | #---------------------------- 463 | # Otherwise return the output 464 | #---------------------------- 465 | else 466 | shellmock_debug "shellmock_replay: perform: OUTPUT *$output* STATUS: $status" 467 | #shellcheck disable=SC2016 468 | $ECHO "$output" | $AWK 'BEGIN{FS="%%"}{ for (i=1;i<=NF;i++) {print $i}}' 469 | return "$status" 470 | fi 471 | } 472 | 473 | #------------------------------- 474 | # Records that script was called 475 | #------------------------------- 476 | shellmock_capture_cmd() 477 | { 478 | local cmd 479 | cmd=$(echo "$@" | awk '{$1=$1};1') 480 | # trim leading and trailing spaces from the command 481 | shellmock_debug "shellmock_capture_cmd: captured: *$cmd*" 482 | $ECHO "${cmd}" >> "$CAPTURE_FILE" 483 | } 484 | 485 | #------------------------- 486 | # Write errors to err file 487 | #------------------------- 488 | shellmock_capture_err() 489 | { 490 | $ECHO "$*" >> "$shellmock_capture_err" 491 | } 492 | 493 | #---------------------------------------------------------------------- 494 | # This utility function captures user output and writes to a debug file 495 | #---------------------------------------------------------------------- 496 | shellmock_dump() 497 | { 498 | if [ -n "$TEST_FUNCTION" ]; then 499 | POSIXLY_CORRECT=1 $ECHO "DUMP-START: stdout" >> "$shellmock_capture_debug" 500 | #shellcheck disable=SC2154 501 | for idx in ${!lines[*]} 502 | do 503 | POSIXLY_CORRECT=1 $ECHO "${lines[$idx]}" >> "$shellmock_capture_debug" 504 | done 505 | POSIXLY_CORRECT=1 $ECHO "DUMP-END: stdout" >> "$shellmock_capture_debug" 506 | fi 507 | } 508 | 509 | #---------------------------------------------------------------------- 510 | # This utility function captures user output and writes to a debug file 511 | #---------------------------------------------------------------------- 512 | shellmock_debug() 513 | { 514 | if [ -n "$TEST_FUNCTION" ]; then 515 | POSIXLY_CORRECT=1 $ECHO "$@" >> "$shellmock_capture_debug" 516 | fi 517 | } 518 | 519 | #---------------------------------- 520 | # Clean up an previous capture file 521 | #---------------------------------- 522 | shellmock_clean() 523 | { 524 | $RM -f "$CAPTURE_FILE" 525 | $RM -f "$shellmock_capture_err" 526 | $RM -f "$shellmock_capture_debug" 527 | if [ -d "$BATS_TEST_DIRNAME/tmpstubs" ]; then 528 | $RM -rf "$BATS_TEST_DIRNAME/tmpstubs" 529 | fi 530 | } 531 | 532 | #--------------------------------------------------- 533 | # Read the capture file into an array called capture 534 | #--------------------------------------------------- 535 | shellmock_verify() 536 | { 537 | index=0 538 | while read -r line ; do 539 | capture[$index]="$line" 540 | index=$index+1 541 | done < "$CAPTURE_FILE" 542 | 543 | export capture 544 | return 0 545 | } 546 | 547 | #------------------------------------------------------------------------------------------------------- 548 | # In case users need to mock lower level commands then make sure that shellmock knows the exact location of 549 | # key commands it needs. 550 | #------------------------------------------------------------------------------------------------------- 551 | if [ -z "$ECHO" ]; then 552 | ECHO=$(command -v echo) 553 | export ECHO 554 | fi 555 | if [ -z "$CP" ]; then 556 | CP=$(command -v cp) 557 | export CP 558 | fi 559 | if [ -z "$CAT" ]; then 560 | CAT=$(command -v cat) 561 | export CAT 562 | fi 563 | if [ -z "$RM" ]; then 564 | RM=$(command -v rm) 565 | export RM 566 | fi 567 | if [ -z "$AWK" ]; then 568 | AWK=$(command -v awk) 569 | export AWK 570 | fi 571 | if [ -z "$GREP" ]; then 572 | GREP=$(command -v grep) 573 | export GREP 574 | fi 575 | if [ -z "$MKDIR" ]; then 576 | MKDIR=$(command -v mkdir) 577 | export MKDIR 578 | fi 579 | if [ -z "$TOUCH" ]; then 580 | TOUCH=$(command -v touch) 581 | export TOUCH 582 | fi 583 | if [ -z "$CHMOD" ]; then 584 | CHMOD=$(command -v chmod) 585 | export CHMOD 586 | fi 587 | if [ -z "$SED" ]; then 588 | SED=$(command -v sed) 589 | export SED 590 | fi 591 | if [ -z "$HEAD" ]; then 592 | HEAD=$(command -v head) 593 | export HEAD 594 | fi 595 | if [ -z "$TAIL" ]; then 596 | TAIL=$(command -v tail) 597 | export TAIL 598 | fi 599 | if [ -z "$WC" ]; then 600 | WC=$(command -v wc) 601 | export WC 602 | fi 603 | 604 | export BATS_TEST_DIRNAME 605 | export CAPTURE_FILE=$BATS_TEST_DIRNAME/shellmock.out 606 | export shellmock_capture_err=$BATS_TEST_DIRNAME/shellmock.err 607 | export shellmock_capture_debug=$BATS_TEST_DIRNAME/shellmock-debug.out 608 | export PATH=$BATS_TEST_DIRNAME/tmpstubs:$PATH 609 | 610 | if [ -n "$SHELLMOCK_V1_COMPATIBILITY" ]; then 611 | shellmock_debug "shellmock: init: Running in V1 compatibility mode." 612 | fi 613 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | #-------------------------------------------------------------------------------- 3 | # SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | # SPDX-License-Identifier: Apache-2.0 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | #--------------------------------------------------------------------------------- 18 | 19 | #--------------------------------------------------------------------------------------------- 20 | # Purpose: build.sh is used to run pre-PR publish steps. It performs three primary functions: 21 | # - Runs all bats tests on shellmock 22 | # - Runs all bats tests on the sample-bats 23 | # - Runs shellcheck against the code and fails if lint errors are found. 24 | #--------------------------------------------------------------------------------------------- 25 | 26 | lint() { 27 | echo "...linting $1" 28 | if ! shellcheck -s bash -x "$1"; then 29 | echo "ERROR: shellcheck of $1 has errors" 30 | exit 1 31 | fi 32 | } 33 | 34 | # Run linting on scripts that users consume. 35 | (cd sample-bats && lint sample.sh) 36 | (cd sample-bats && lint sample.bats) 37 | lint install.sh 38 | lint build.sh 39 | (cd bin && lint shellmock) 40 | (cd test && lint shellmock.bats) 41 | 42 | echo "...Running bats tests for sample-bats" 43 | (cd sample-bats && bats ./*.bats) 44 | 45 | echo ".../Running bats tests for test" 46 | (cd test && bats ./*.bats) 47 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | #-------------------------------------------------------------------------------- 3 | # SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | # SPDX-License-Identifier: Apache-2.0 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | #--------------------------------------------------------------------------------- 18 | # File: install.sh 19 | # Purpose: 20 | # This script copies shellmock to the appropriate installation root. 21 | #--------------------------------------------------------------------------------- 22 | 23 | set -e 24 | 25 | 26 | PREFIX="$1" 27 | if [ -z "$1" ]; then 28 | { echo "usage: $0 " 29 | echo " e.g. $0 /usr/local" 30 | } >&2 31 | exit 1 32 | fi 33 | 34 | mkdir -p "$PREFIX"/bin 35 | cp -R bin/* "$PREFIX"/bin/ 36 | 37 | 38 | echo "Installed bash_shell_mock to $PREFIX/bin/shellmock" 39 | -------------------------------------------------------------------------------- /sample-bats/sample.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | #-------------------------------------------------------------------------------- 3 | # SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | # SPDX-License-Identifier: Apache-2.0 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | #--------------------------------------------------------------------------------- 18 | 19 | #--------------------------------------------------------------------------------- 20 | # File: sample.bats 21 | # Purpose: 22 | # This is a sample bats file that demonstrates the use of the 23 | # mocking framework. 24 | #--------------------------------------------------------------------------------- 25 | 26 | setup() 27 | { 28 | # Source the shellmock functions into the shell. 29 | #shellcheck source=../bin/shellmock 30 | . ../bin/shellmock 31 | 32 | skipIfNot "$BATS_TEST_DESCRIPTION" 33 | 34 | shellmock_clean 35 | } 36 | 37 | teardown() 38 | { 39 | if [ -z "$TEST_FUNCTION" ];then 40 | shellmock_clean 41 | rm -f sample.out 42 | fi 43 | } 44 | 45 | #----------------------------------------------------------------------------------- 46 | # This test case demonstrates a normal bats test case where sample.sh is under test. 47 | # sample.sh will echo "sample found" based on the response to the grep command. 48 | # The default output will always be "sample found" because the script ensures 49 | # that grep will return 0. 50 | #----------------------------------------------------------------------------------- 51 | @test "sample.sh-success" { 52 | 53 | run ./sample.sh 54 | 55 | [ "$status" = "0" ] 56 | 57 | # Validate using lines array. 58 | [ "${lines[0]}" = "sample found" ] 59 | 60 | # Optionally since this is a single line you can use $output 61 | [ "$output" = "sample found" ] 62 | 63 | } 64 | 65 | #---------------------------------------------------------------------------------------- 66 | # This test case demonstrates that the else condition if grep does not find the match. 67 | # By forcing the status of 1 grep will cause the "sample not found" message to be echoed. 68 | # To pull this off we need to mock the grep command. 69 | #---------------------------------------------------------------------------------------- 70 | @test "sample.sh-failure" { 71 | 72 | 73 | shellmock_expect grep --status 1 --match '"sample line" sample.out' 74 | 75 | shellmock_debug "starting the test" 76 | 77 | run ./sample.sh 78 | 79 | shellmock_dump 80 | 81 | [ "$status" = "1" ] 82 | [ "$output" = "sample not found" ] 83 | 84 | 85 | shellmock_verify 86 | [ "${capture[0]}" = 'grep-stub "sample line" sample.out' ] 87 | 88 | } 89 | 90 | #----------------------------------------------------------------------------------- 91 | # This test case demonstrates mocking the grep command using the partial mock feature. 92 | # The sample.sh calls grep with two arguments. The first argument is "sample line". 93 | #----------------------------------------------------------------------------------- 94 | @test "sample.sh-success-partial-mock" { 95 | 96 | shellmock_expect grep --status 0 --type partial --match '"sample line"' 97 | 98 | run ./sample.sh 99 | 100 | shellmock_dump 101 | 102 | [ "$status" = "0" ] 103 | 104 | # Validate using lines array. 105 | [ "${lines[0]}" = "sample found" ] 106 | 107 | # Optionally since this is a single line you can use $output 108 | [ "$output" = "sample found" ] 109 | 110 | shellmock_verify 111 | [ "${#capture[@]}" = "1" ] 112 | [ "${capture[0]}" = 'grep-stub "sample line" sample.out' ] 113 | 114 | } 115 | 116 | #----------------------------------------------------------------------------------- 117 | # This test case demonstrates mocking the grep command using the partial mock feature. 118 | # The sample.sh calls grep with two arguments. The first argument is "sample line". 119 | # 120 | # The only difference between this and the previous test is that the argument is passed 121 | # as single quotes 'sample line'. 122 | # 123 | # In that case you will notice that the command[] matches show as double quotes vs 124 | # single quotes. That is because the arguments are normalized to double quotes. 125 | #----------------------------------------------------------------------------------- 126 | @test "sample.sh-success-partial-mock-with-single-quotes" { 127 | 128 | shellmock_expect grep --status 0 --type partial --match "'sample line'" 129 | 130 | run ./sample.sh 131 | 132 | shellmock_dump 133 | 134 | [ "$status" = "0" ] 135 | 136 | # Validate using lines array. 137 | [ "${lines[0]}" = "sample found" ] 138 | 139 | # Optionally since this is a single line you can use $output 140 | [ "$output" = "sample found" ] 141 | 142 | shellmock_verify 143 | [ "${#capture[@]}" = "1" ] 144 | [ "${capture[0]}" = 'grep-stub "sample line" sample.out' ] 145 | 146 | } 147 | 148 | #----------------------------------------------------------------------------------- 149 | # This sample simply demonstrates the regex matching using grep. 150 | #----------------------------------------------------------------------------------- 151 | @test "sample.sh-mock-with-regex" { 152 | 153 | shellmock_expect grep --status 0 --type regex --match '"sample line" s.*' 154 | shellmock_expect grep --status 1 --type regex --match '"sample line" b.*' 155 | 156 | run grep "sample line" sample.out 157 | [ "$status" = "0" ] 158 | 159 | run grep "sample line" sample1.out 160 | [ "$status" = "0" ] 161 | 162 | run grep "sample line" bfile.out 163 | [ "$status" = "1" ] 164 | 165 | run grep "sample line" bats.out 166 | [ "$status" = "1" ] 167 | 168 | shellmock_dump 169 | 170 | shellmock_verify 171 | [ "${#capture[@]}" = "4" ] 172 | [ "${capture[0]}" = 'grep-stub "sample line" sample.out' ] 173 | [ "${capture[1]}" = 'grep-stub "sample line" sample1.out' ] 174 | [ "${capture[2]}" = 'grep-stub "sample line" bfile.out' ] 175 | [ "${capture[3]}" = 'grep-stub "sample line" bats.out' ] 176 | 177 | } 178 | @test "sample.sh-mock-with-custom-script" { 179 | 180 | shellmock_expect grep --status 0 --type partial --match "string1" --exec "echo mycustom {}" 181 | 182 | run grep string1 file1 183 | 184 | shellmock_dump 185 | [ "$status" = "0" ] 186 | [ "${lines[0]}" = "mycustom string1 file1" ] 187 | 188 | run grep string1 file2 189 | shellmock_dump 190 | [ "$status" = "0" ] 191 | [ "${lines[0]}" = "mycustom string1 file2" ] 192 | 193 | shellmock_verify 194 | [ "${#capture[@]}" = "2" ] 195 | [ "${capture[0]}" = 'grep-stub string1 file1' ] 196 | [ "${capture[1]}" = 'grep-stub string1 file2' ] 197 | } 198 | -------------------------------------------------------------------------------- /sample-bats/sample.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #-------------------------------------------------------------------------------- 3 | # SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | # SPDX-License-Identifier: Apache-2.0 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | #--------------------------------------------------------------------------------- 18 | #--------------------------------------------------------------------------------- 19 | # File: sample.sh 20 | # Purpose: 21 | # This is a sample script that we intend to test using the mock framework. 22 | # 23 | #--------------------------------------------------------------------------------- 24 | echo "sample line" > sample.out 25 | 26 | if ! grep "sample line" sample.out > /dev/null; then 27 | echo "sample not found" 28 | exit 1 29 | fi 30 | 31 | echo "sample found" 32 | -------------------------------------------------------------------------------- /test/shellmock.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | #shellcheck disable=SC2030,SC2031 3 | 4 | #-------------------------------------------------------------------------------- 5 | # SPDX-Copyright: Copyright (c) Capital One Services, LLC 6 | # SPDX-License-Identifier: Apache-2.0 7 | # 8 | # Licensed under the Apache License, Version 2.0 (the "License"); 9 | # you may not use this file except in compliance with the License. 10 | # You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | #--------------------------------------------------------------------------------- 20 | 21 | #--------------------------------------------------------------------- 22 | # File: shellmock.bats 23 | # Purpose: 24 | # This is a bats testing script that is used to test the features 25 | # of the mock framework itself. 26 | # 27 | # You can run the tests via: bats shellmock.bats 28 | #--------------------------------------------------------------------- 29 | setup() 30 | { 31 | # For testing setup the path so that install is not required. 32 | export PATH=../bin:$PATH 33 | unset SHELLMOCK_V1_COMPATIBILITY 34 | #shellcheck source=../bin/shellmock 35 | . shellmock 36 | 37 | } 38 | 39 | teardown() 40 | { 41 | if [ -z "$TEST_FUNCTION" ]; then 42 | shellmock_clean 43 | fi 44 | if [ -d "$TEST_TEMP_DIR" ]; then 45 | rm -rf "$TEST_TEMP_DIR" 46 | fi 47 | } 48 | 49 | @test "shellmock_expect --status 0" { 50 | 51 | skipIfNot status-0 52 | 53 | shellmock_clean 54 | shellmock_expect cp --status 0 --match "a b" --output "mock a b success" 55 | 56 | run cp a b 57 | [ "$status" = "0" ] 58 | [ "$output" = "mock a b success" ] 59 | 60 | shellmock_verify 61 | [ "${#capture[@]}" = "1" ] 62 | [ "${capture[0]}" = 'cp-stub a b' ] 63 | 64 | } 65 | 66 | @test "shellmock_expect --status 1" { 67 | 68 | skipIfNot status-1 69 | 70 | shellmock_clean 71 | shellmock_expect cp --status 1 --match "a b" --output "mock a b failed" 72 | 73 | run cp a b 74 | [ "$status" = "1" ] 75 | [ "$output" = "mock a b failed" ] 76 | 77 | shellmock_verify 78 | [ "${#capture[@]}" = "1" ] 79 | [ "${capture[0]}" = 'cp-stub a b' ] 80 | 81 | } 82 | 83 | @test "shellmock_expect-multiple-responses" { 84 | 85 | skipIfNot multi-resources 86 | 87 | shellmock_clean 88 | shellmock_expect cp --status 0 --match "a b" --output "mock a b success" 89 | shellmock_expect cp --status 1 --match "a b" --output "mock a b failed" 90 | 91 | run cp a b 92 | [ "$status" = "0" ] 93 | [ "$output" = "mock a b success" ] 94 | 95 | run cp a b 96 | [ "$status" = "1" ] 97 | [ "$output" = "mock a b failed" ] 98 | 99 | # not a match 100 | run cp a c 101 | [ "$status" = "99" ] 102 | 103 | shellmock_verify 104 | [ "${#capture[@]}" = "3" ] 105 | [ "${capture[0]}" = 'cp-stub a b' ] 106 | [ "${capture[1]}" = 'cp-stub a b' ] 107 | [ "${capture[2]}" = 'cp-stub a c' ] 108 | 109 | } 110 | 111 | @test "shellmock_expect --status 0 partial-match" { 112 | 113 | skipIfNot partial-match 114 | 115 | shellmock_clean 116 | shellmock_expect cp --status 0 --type partial --match "a" --output "mock success" 117 | 118 | run cp a b 119 | [ "$status" = "0" ] 120 | [ "$output" = "mock success" ] 121 | 122 | run cp a c 123 | [ "$status" = "0" ] 124 | [ "$output" = "mock success" ] 125 | 126 | shellmock_verify 127 | [ "${#capture[@]}" = "2" ] 128 | [ "${capture[0]}" = 'cp-stub a b' ] 129 | [ "${capture[1]}" = 'cp-stub a c' ] 130 | 131 | } 132 | 133 | @test "shellmock_expect --status 0 partial-match with double quotes" { 134 | 135 | skipIfNot partial-match-double 136 | 137 | shellmock_clean 138 | shellmock_expect cp --status 0 --type partial --match '"a file.c"' --output "mock success" 139 | 140 | run cp "a file.c" b 141 | [ "$status" = "0" ] 142 | [ "$output" = "mock success" ] 143 | 144 | run cp "a file.c" c 145 | [ "$status" = "0" ] 146 | [ "$output" = "mock success" ] 147 | 148 | shellmock_verify 149 | [ "${#capture[@]}" = "2" ] 150 | [ "${capture[0]}" = 'cp-stub "a file.c" b' ] 151 | [ "${capture[1]}" = 'cp-stub "a file.c" c' ] 152 | 153 | } 154 | 155 | @test "shellmock_expect --status 0 partial-match with single quotes" { 156 | 157 | skipIfNot partial-match-single 158 | 159 | shellmock_clean 160 | shellmock_expect cp --status 0 --type partial --match "'a file.c'" --output "mock success" 161 | 162 | run cp 'a file.c' b 163 | [ "$status" = "0" ] 164 | [ "$output" = "mock success" ] 165 | 166 | run cp 'a file.c' c 167 | [ "$status" = "0" ] 168 | [ "$output" = "mock success" ] 169 | 170 | # Because the input parameters into the mock are normalized the single 171 | # quotes will appear as double quotes in the shellmock.out file. 172 | 173 | shellmock_verify 174 | [ "${#capture[@]}" = "2" ] 175 | [ "${capture[0]}" = 'cp-stub "a file.c" b' ] 176 | [ "${capture[1]}" = 'cp-stub "a file.c" c' ] 177 | 178 | } 179 | 180 | @test "shellmock_expect failed matches" { 181 | 182 | skipIfNot failed-matches 183 | 184 | shellmock_clean 185 | shellmock_expect cp --status 0 --type exact --match "a b" --output "mock a b success" 186 | 187 | run cp a b 188 | [ "$status" = "0" ] 189 | [ "$output" = "mock a b success" ] 190 | 191 | run cp a c 192 | [ "$status" = "99" ] 193 | 194 | grep 'No record match found stdin:\*\* cmd:cp args:\*a c\*' shellmock.err 195 | 196 | shellmock_verify 197 | [ "${#capture[@]}" = "2" ] 198 | [ "${capture[0]}" = 'cp-stub a b' ] 199 | [ "${capture[1]}" = 'cp-stub a c' ] 200 | 201 | } 202 | 203 | @test "shellmock_expect failed partial matches" { 204 | 205 | skipIfNot failed-partial-matches 206 | 207 | shellmock_clean 208 | shellmock_expect cp --status 0 --type partial --match "a" --output "mock success" 209 | 210 | run cp a b 211 | [ "$status" = "0" ] 212 | [ "$output" = "mock success" ] 213 | 214 | run cp a c 215 | [ "$status" = "0" ] 216 | [ "$output" = "mock success" ] 217 | 218 | run cp b b 219 | [ "$status" = "99" ] 220 | grep 'No record match found stdin:\*\* cmd:cp args:\*b b\*' shellmock.err 221 | 222 | shellmock_verify 223 | [ "${#capture[@]}" = "3" ] 224 | [ "${capture[0]}" = 'cp-stub a b' ] 225 | [ "${capture[1]}" = 'cp-stub a c' ] 226 | [ "${capture[2]}" = 'cp-stub b b' ] 227 | 228 | } 229 | 230 | @test "shellmock_expect execute on match" { 231 | 232 | skipIfNot exec-on-match 233 | 234 | shellmock_clean 235 | shellmock_expect cp --status 0 --type exact --match "a b" --exec "echo executed." 236 | 237 | run cp a b 238 | [ "$status" = "0" ] 239 | [ "$output" = "executed." ] 240 | 241 | shellmock_verify 242 | [ "${#capture[@]}" = "1" ] 243 | [ "${capture[0]}" = 'cp-stub a b' ] 244 | 245 | } 246 | 247 | @test "shellmock_expect execute on match args with double quotes" { 248 | 249 | skipIfNot exec-on-match-with-double-quotes 250 | 251 | shellmock_clean 252 | shellmock_expect cp --status 0 --type exact --match '"a b.c" b' --exec "echo executed." 253 | 254 | run cp "a b.c" b 255 | [ "$status" = "0" ] 256 | [ "$output" = "executed." ] 257 | 258 | shellmock_verify 259 | [ "${#capture[@]}" = "1" ] 260 | [ "${capture[0]}" = 'cp-stub "a b.c" b' ] 261 | 262 | } 263 | 264 | @test "shellmock_expect execute on match args with single quotes" { 265 | 266 | skipIfNot exec-on-match-with-single-quotes 267 | 268 | shellmock_clean 269 | shellmock_expect cp --status 0 --type exact --match "'a b.c' b" --exec "echo executed." 270 | 271 | run cp 'a b.c' b 272 | [ "$status" = "0" ] 273 | [ "$output" = "executed." ] 274 | 275 | # Single quotes will be converted to double quotes when the arguments are normalized. 276 | # so match on double quotes instead. 277 | 278 | shellmock_verify 279 | [ "${#capture[@]}" = "1" ] 280 | [ "${capture[0]}" = 'cp-stub "a b.c" b' ] 281 | 282 | } 283 | 284 | @test "shellmock_expect execute on partial match" { 285 | 286 | skipIfNot exec-on-partial 287 | 288 | shellmock_clean 289 | shellmock_expect cp --status 0 --type partial --match "a" --exec "echo executed." 290 | 291 | run cp a b 292 | [ "$status" = "0" ] 293 | [ "$output" = "executed." ] 294 | 295 | run cp a c 296 | [ "$status" = "0" ] 297 | [ "$output" = "executed." ] 298 | 299 | shellmock_verify 300 | [ "${#capture[@]}" = "2" ] 301 | [ "${capture[0]}" = 'cp-stub a b' ] 302 | [ "${capture[1]}" = 'cp-stub a c' ] 303 | 304 | } 305 | 306 | @test "shellmock_expect execute on match with {} substitution" { 307 | 308 | skipIfNot substitution 309 | 310 | shellmock_clean 311 | shellmock_expect cp --status 0 --type exact --match "a b" --exec "echo t1 {} tn" 312 | 313 | run cp a b 314 | [ "$status" = "0" ] 315 | [ "$output" = "t1 a b tn" ] 316 | 317 | shellmock_verify 318 | [ "${#capture[@]}" = "1" ] 319 | [ "${capture[0]}" = 'cp-stub a b' ] 320 | 321 | } 322 | 323 | @test "shellmock_expect source" { 324 | 325 | skipIfNot source 326 | 327 | shellmock_clean 328 | shellmock_expect test.bash --status 0 --type exact --match "" --source "./test.bash" 329 | 330 | #shellcheck disable=SC1091 331 | . tmpstubs/test.bash 332 | 333 | [ "$TEST_PROP" = "test-prop" ] 334 | 335 | shellmock_verify 336 | [ "${#capture[@]}" = "1" ] 337 | [ "${capture[0]}" = 'test.bash-stub' ] 338 | 339 | } 340 | 341 | @test "shellmock_expect multiple responses outside \$BATS_TEST_DIRNAME" { 342 | 343 | skipIfNot outside-dirname 344 | 345 | shellmock_clean 346 | 347 | export TEST_TEMP_DIR="$BATS_TEST_DIRNAME/tempbin" 348 | mkdir -p "$TEST_TEMP_DIR" 349 | export BATS_TEST_DIRNAME=$TEST_TEMP_DIR 350 | export CAPTURE_FILE=$BATS_TEST_DIRNAME/shellmock.out 351 | export shellmock_capture_err=$BATS_TEST_DIRNAME/shellmock.err 352 | export PATH=$BATS_TEST_DIRNAME/tmpstubs:$PATH 353 | 354 | shellmock_clean 355 | shellmock_expect cp --status 0 --match "a b" --output "mock a b success" 356 | shellmock_expect cp --status 1 --match "a b" --output "mock a b failed" 357 | 358 | run cp a b 359 | [ "$status" = "0" ] 360 | [ "$output" = "mock a b success" ] 361 | 362 | run cp a b 363 | [ "$status" = "1" ] 364 | [ "$output" = "mock a b failed" ] 365 | 366 | # not a match 367 | run cp a c 368 | [ "$status" = "99" ] 369 | 370 | shellmock_verify 371 | [ "${#capture[@]}" = "3" ] 372 | [ "${capture[0]}" = 'cp-stub a b' ] 373 | [ "${capture[1]}" = 'cp-stub a b' ] 374 | [ "${capture[2]}" = 'cp-stub a c' ] 375 | 376 | } 377 | 378 | @test "shellmock_clean inside directory with spaces" { 379 | 380 | skipIfNot clean-dir-spaces 381 | 382 | export TEST_TEMP_DIR="$BATS_TEST_DIRNAME/temp dir" 383 | mkdir -p "$TEST_TEMP_DIR" 384 | export BATS_TEST_DIRNAME="$TEST_TEMP_DIR" 385 | export CAPTURE_FILE="$BATS_TEST_DIRNAME/shellmock.out" 386 | export shellmock_capture_err="$BATS_TEST_DIRNAME/shellmock.err" 387 | export PATH="$BATS_TEST_DIRNAME/tmpstubs:$PATH" 388 | 389 | touch "$CAPTURE_FILE" 390 | touch "$shellmock_capture_err" 391 | mkdir -p "$BATS_TEST_DIRNAME/tmpstubs" 392 | 393 | shellmock_clean 394 | [ ! -f "$CAPTURE_FILE" ] 395 | [ ! -f "$shellmock_capture_err" ] 396 | [ ! -d "$BATS_TEST_DIRNAME/tmpstubs" ] 397 | } 398 | 399 | @test "shellmock_expect inside directory with spaces" { 400 | 401 | skipIfNot expect-dir-spaces 402 | 403 | shellmock_clean 404 | 405 | TEST_TEMP_DIR="$BATS_TEST_DIRNAME/temp dir" 406 | mkdir -p "$TEST_TEMP_DIR" 407 | export BATS_TEST_DIRNAME="$TEST_TEMP_DIR" 408 | export CAPTURE_FILE="$BATS_TEST_DIRNAME/shellmock.out" 409 | export shellmock_capture_err="$BATS_TEST_DIRNAME/shellmock.err" 410 | export PATH="$BATS_TEST_DIRNAME/tmpstubs:$PATH" 411 | 412 | shellmock_clean 413 | shellmock_expect cp --exec "echo executed." 414 | 415 | run cp 416 | [ "$status" = "0" ] 417 | [ "$output" = "executed." ] 418 | 419 | shellmock_verify 420 | [ "${#capture[@]}" = "1" ] 421 | [ "${capture[0]}" = 'cp-stub' ] 422 | 423 | } 424 | 425 | @test "shellmock_verify inside directory with spaces" { 426 | 427 | skipIfNot verify-dir-spaces 428 | 429 | shellmock_clean 430 | shellmock_expect cp --exec "echo executed." 431 | 432 | run cp 433 | [ "$status" = "0" ] 434 | [ "$output" = "executed." ] 435 | 436 | #shellcheck disable=SC2031 437 | TEST_TEMP_DIR="$BATS_TEST_DIRNAME/temp dir" 438 | mkdir -p "$TEST_TEMP_DIR" 439 | 440 | #shellcheck disable=SC2031 441 | mv "$BATS_TEST_DIRNAME/tmpstubs" "$TEST_TEMP_DIR/tmpstubs" 442 | 443 | #shellcheck disable=SC2031 444 | export BATS_TEST_DIRNAME="$TEST_TEMP_DIR" 445 | 446 | #shellcheck disable=SC2031 447 | mv "$CAPTURE_FILE" "$BATS_TEST_DIRNAME/shellmock.out" 448 | 449 | #shellcheck disable=SC2031 450 | export CAPTURE_FILE="$BATS_TEST_DIRNAME/shellmock.out" 451 | 452 | shellmock_verify 453 | [ "${#capture[@]}" = "1" ] 454 | [ "${capture[0]}" = "cp-stub" ] 455 | } 456 | 457 | @test "shellmock_expect --match '--version'" { 458 | 459 | skipIfNot match-version 460 | 461 | shellmock_clean 462 | shellmock_expect foo --match "--version" --output "Foo version" 463 | 464 | run foo --version 465 | [ "$status" = "0" ] 466 | [ "$output" = "Foo version" ] 467 | 468 | shellmock_verify 469 | [ "${#capture[@]}" = "1" ] 470 | [ "${capture[0]}" = "foo-stub --version" ] 471 | 472 | } 473 | 474 | @test "shellmock_expect --status 0 regex-match" { 475 | 476 | skipIfNot regex-match 477 | 478 | shellmock_clean 479 | shellmock_expect cp --status 0 --type regex --match "-a -s script\(\'t.*\'\)" --output "mock success" 480 | 481 | run cp -a -s "script('testit')" 482 | [ "$status" = "0" ] 483 | [ "$output" = "mock success" ] 484 | 485 | run cp -a -s "script('testit2')" 486 | 487 | [ "$status" = "0" ] 488 | [ "$output" = "mock success" ] 489 | 490 | run cp -a -s "script('Testit2')" 491 | [ "$status" = "99" ] 492 | 493 | shellmock_verify 494 | [ "${#capture[@]}" = "3" ] 495 | [ "${capture[0]}" = "cp-stub -a -s script('testit')" ] 496 | [ "${capture[1]}" = "cp-stub -a -s script('testit2')" ] 497 | [ "${capture[2]}" = "cp-stub -a -s script('Testit2')" ] 498 | 499 | } 500 | 501 | @test "shellmock_expect quotes compatibility test" { 502 | 503 | skipIfNot quotes-compatibility-v1.0-test 504 | 505 | export SHELLMOCK_V1_COMPATIBILITY="enabled" 506 | 507 | shellmock_clean 508 | shellmock_expect cp --status 0 --match "a b c" --output "mock a b success" 509 | 510 | run cp "a b" c 511 | [ "$status" = "0" ] 512 | [ "$output" = "mock a b success" ] 513 | 514 | shellmock_verify 515 | [ "${capture[0]}" = "cp-stub a b c" ] 516 | } 517 | 518 | @test "shellmock_expect --status 0 with stdin" { 519 | 520 | skipIfNot status-0-stdin 521 | 522 | shellmock_clean 523 | 524 | #--------------------------------------------------------------- 525 | # Had issues getting the run echo "a b" | cat to work so 526 | # I used the exec feature to create stubs to invoke the cat-stub 527 | #--------------------------------------------------------------- 528 | shellmock_expect helper --status 0 --match "a b" --exec 'echo a b | cat' 529 | shellmock_expect helper --status 0 --match "a c" --exec 'echo a c | cat' 530 | 531 | shellmock_expect cat --status 0 --match-stdin "a b" --output "mock success" 532 | 533 | run helper a b 534 | [ "$status" = "0" ] 535 | [ "$output" = "mock success" ] 536 | 537 | run helper a c 538 | [ "$status" = "99" ] 539 | 540 | shellmock_verify 541 | [ "${#capture[@]}" = "4" ] 542 | [ "${capture[0]}" = 'helper-stub a b' ] 543 | [ "${capture[1]}" = 'a b | cat-stub' ] 544 | [ "${capture[2]}" = 'helper-stub a c' ] 545 | [ "${capture[3]}" = 'a c | cat-stub' ] 546 | 547 | } 548 | 549 | @test "shellmock_expect --status 0 with stdin and args" { 550 | 551 | skipIfNot status-0-stdin-and-args 552 | 553 | shellmock_clean 554 | 555 | #--------------------------------------------------------------- 556 | # Had issues getting the run echo "a b" | cat to work so 557 | # I used the exec feature to create stubs to invoke the cat-stub 558 | #--------------------------------------------------------------- 559 | shellmock_expect helper --status 0 --match "a b" --exec 'echo a b | cat -t -v' 560 | shellmock_expect helper --status 0 --match "a b" --exec 'echo a b | cat -p -q' 561 | 562 | shellmock_expect cat --status 0 --match-args "-t -v" --match-stdin "a b" --output "mock success" 563 | 564 | run helper a b 565 | [ "$status" = "0" ] 566 | [ "$output" = "mock success" ] 567 | 568 | run helper a b 569 | [ "$status" = "99" ] 570 | 571 | 572 | shellmock_verify 573 | [ "${#capture[@]}" = "4" ] 574 | [ "${capture[0]}" = 'helper-stub a b' ] 575 | [ "${capture[1]}" = 'a b | cat-stub -t -v' ] 576 | [ "${capture[2]}" = 'helper-stub a b' ] 577 | [ "${capture[3]}" = 'a b | cat-stub -p -q' ] 578 | 579 | } 580 | 581 | @test "shellmock_expect --status 0 with stdin and args multi-response" { 582 | 583 | skipIfNot status-0-stdin-and-args-multi 584 | 585 | shellmock_clean 586 | 587 | #--------------------------------------------------------------- 588 | # Had issues getting the run echo "a b" | cat to work so 589 | # I used the exec feature to create stubs to invoke the cat-stub 590 | #--------------------------------------------------------------- 591 | shellmock_expect helper --status 0 --match "a b" --exec 'echo a b | cat -t -v' 592 | shellmock_expect helper --status 0 --match "a b" --exec 'echo a b | cat -p -q' 593 | shellmock_expect helper --status 0 --match "a b" --exec 'echo a b | cat -t -v' 594 | 595 | shellmock_expect cat --status 0 --match-args "-t -v" --match-stdin "a b" --output "mock success 1" 596 | shellmock_expect cat --status 0 --match-args "-t -v" --match-stdin "a b" --output "mock success 2" 597 | 598 | run helper a b 599 | [ "$status" = "0" ] 600 | [ "$output" = "mock success 1" ] 601 | 602 | run helper a b 603 | [ "$status" = "99" ] 604 | 605 | run helper a b 606 | [ "$status" = "0" ] 607 | [ "$output" = "mock success 2" ] 608 | 609 | shellmock_verify 610 | [ "${#capture[@]}" = "6" ] 611 | [ "${capture[0]}" = 'helper-stub a b' ] 612 | [ "${capture[1]}" = 'a b | cat-stub -t -v' ] 613 | [ "${capture[2]}" = 'helper-stub a b' ] 614 | [ "${capture[3]}" = 'a b | cat-stub -p -q' ] 615 | [ "${capture[4]}" = 'helper-stub a b' ] 616 | [ "${capture[5]}" = 'a b | cat-stub -t -v' ] 617 | 618 | } 619 | 620 | @test "shellmock_expect --status 0 with stdin and regex" { 621 | 622 | skipIfNot status-0-stdin-regex 623 | 624 | shellmock_clean 625 | 626 | #--------------------------------------------------------------- 627 | # Had issues getting the run echo "a b" | cat to work so 628 | # I used the exec feature to create stubs to invoke the cat-stub 629 | #--------------------------------------------------------------- 630 | shellmock_expect helper --status 0 --match "a b" --exec 'echo a b | cat' 631 | shellmock_expect helper --status 0 --match "a c" --exec 'echo a c | cat' 632 | 633 | shellmock_expect cat --status 0 --stdin-match-type regex --match-stdin "a.*" --output "mock success" 634 | 635 | run helper a b 636 | [ "$status" = "0" ] 637 | [ "$output" = "mock success" ] 638 | 639 | run helper a c 640 | [ "$status" = "0" ] 641 | [ "$output" = "mock success" ] 642 | 643 | shellmock_verify 644 | [ "${#capture[@]}" = "4" ] 645 | [ "${capture[0]}" = 'helper-stub a b' ] 646 | [ "${capture[1]}" = 'a b | cat-stub' ] 647 | [ "${capture[2]}" = 'helper-stub a c' ] 648 | [ "${capture[3]}" = 'a c | cat-stub' ] 649 | 650 | } 651 | 652 | @test "shellmock_expect --status 0 with stdin partial match" { 653 | 654 | skipIfNot status-0-stdin-partial 655 | 656 | shellmock_clean 657 | 658 | #--------------------------------------------------------------- 659 | # Had issues getting the run echo "a b" | cat to work so 660 | # I used the exec feature to create stubs to invoke the cat-stub 661 | #--------------------------------------------------------------- 662 | shellmock_expect helper --status 0 --match "a b" --exec 'echo a b | cat' 663 | shellmock_expect helper --status 0 --match "a c" --exec 'echo a c | cat' 664 | 665 | shellmock_expect cat --status 0 --stdin-match-type regex --match-stdin "a" --output "mock success" 666 | 667 | run helper a b 668 | [ "$status" = "0" ] 669 | [ "$output" = "mock success" ] 670 | 671 | run helper a c 672 | [ "$status" = "0" ] 673 | [ "$output" = "mock success" ] 674 | 675 | shellmock_verify 676 | [ "${#capture[@]}" = "4" ] 677 | [ "${capture[0]}" = 'helper-stub a b' ] 678 | [ "${capture[1]}" = 'a b | cat-stub' ] 679 | [ "${capture[2]}" = 'helper-stub a c' ] 680 | [ "${capture[3]}" = 'a c | cat-stub' ] 681 | 682 | } 683 | -------------------------------------------------------------------------------- /test/test.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #-------------------------------------------------------------------------------- 3 | # SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | # SPDX-License-Identifier: Apache-2.0 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | #--------------------------------------------------------------------------------- 18 | 19 | #--------------------------------------------------------------------------------- 20 | # File: test.bash 21 | # Purpose: 22 | # This script is used in the testing of shellmock. In particularly it is used 23 | # in the testing of the --source option of the shellmock_expect utility. 24 | #--------------------------------------------------------------------------------- 25 | 26 | export TEST_PROP=test-prop 27 | --------------------------------------------------------------------------------