github.com/crowdsecurity/crowdsec@v1.6.1/test/README.md (about) 1 2 # What is this? 3 4 This directory contains scripts for functional testing. The tests are run with 5 the [bats-core](https://github.com/bats-core/bats-core) framework, which is an 6 active fork of the older BATS (Bash Automated Testing System). 7 8 With the addition of [the ansible playbooks](ansible/README.md) it is possible 9 to use VMs to test the binary packages, service management and other CPU 10 architectures. 11 12 ### cscli 13 14 | Feature | Covered | Notes | 15 | :-------------------- | :----------------- | :------------------------- | 16 | `cscli alerts` | - | | 17 | `cscli bouncers` | `10_bouncers` | | 18 | `cscli capi` | `01_base` | `status` only | 19 | `cscli collections` | `20_collections` | | 20 | `cscli config` | `01_base` | minimal testing (no crash) | 21 | `cscli dashboard` | - | docker inside docker 😞 | 22 | `cscli decisions` | `9[78]_ipv[46]*` | | 23 | `cscli hub` | `dyn_bats/99_hub` | | 24 | `cscli lapi` | `01_base` | | 25 | `cscli machines` | `30_machines` | | 26 | `cscli metrics` | - | | 27 | `cscli parsers` | - | | 28 | `cscli postoverflows` | - | | 29 | `cscli scenarios` | - | | 30 | `cscli simulation` | `50_simulation` | | 31 | `cscli version` | `01_base` | | 32 33 ### crowdsec 34 35 | Feature | Covered | Notes | 36 | :----------------------------- | :------------- | :----------------------------------------- | 37 | `systemctl` start/stop/restart | - | | 38 | agent behavior | `40_live-ban` | minimal testing (simple ssh-bf detection) | 39 | forensic mode | `40_cold-logs` | minimal testing (simple ssh-bf detection) | 40 | starting without LAPI | `02_nolapi` | | 41 | starting without agent | `03_noagent` | | 42 | starting without CAPI | `04_nocapi` | | 43 | prometheus testing | - | | 44 45 ### API 46 47 | Feature | Covered | Notes | 48 | :----------------- | :--------------- | :----------- | 49 | alerts GET/POST | `9[78]_ipv[46]*` | | 50 | decisions GET/POST | `9[78]_ipv[46]*` | | 51 | stream mode | `99_lapi-stream-mode | | 52 53 54 # How to use it 55 56 ## pre-requisites 57 58 - `git submodule init; git submodule update` 59 - `base64` 60 - `bash>=4.4` 61 - `curl` 62 - `daemonize` 63 - `jq` 64 - `nc` 65 - `openssl` 66 - `python3` 67 68 ## Running all tests 69 70 Run `make clean bats-all` to perform a test build + run. 71 To repeat test runs without rebuilding crowdsec, use `make bats-test`. 72 73 74 ## Debugging tests 75 76 See `./test/run-tests --help` to run/debug specific tests. 77 78 Example: `./test/run-tests test/bats/02_nolapi.bats -f "cscli config backup"` (the string is a regexp). 79 You need to provide a path for a test file or directory (even if it's the full 'test/bats') to use the `-f` option. 80 81 82 # How does it work? 83 84 In BATS, you write tests in the form of Bash functions that have unique 85 descriptions (the name of the test). You can do most things that you can 86 normally do in a shell function. If there is any error condition, the test 87 fails. A set of functions is provided to implement assertions, and a mechanism 88 of `setup`/`teardown` is provided at the level of individual tests (functions) 89 or group of tests (files). 90 91 The stdout/stderr of the commands within the test function are captured by 92 bats-core and will only be shown if the test fails. If you want to always print 93 something to debug your test case, you can redirect the output to the file 94 descriptor 3: 95 96 ```sh 97 @test "mytest" { 98 echo "hello world!" >&3 99 run some-command 100 assert_success 101 echo "goodbye." >&3 102 } 103 ``` 104 105 If you do that, please remove it once the test development is finished, because 106 this practice breaks the TAP protocol (unless each line has a '#' as first 107 character, but really, it's better to avoid unnecessary output when tests succeed). 108 109 You can find here the documentation for the main framework and the plugins we use in this test suite: 110 111 - [bats-core tutorial](https://bats-core.readthedocs.io/en/stable/tutorial.html) 112 - [Writing tests](https://bats-core.readthedocs.io/en/stable/writing-tests.html) 113 - [bats-assert](https://github.com/bats-core/bats-assert) 114 - [bats-support](https://github.com/bats-core/bats-support) 115 - [bats-file](https://github.com/bats-core/bats-file) 116 - [bats-mock](https://github.com/grayhemp/bats-mock) 117 118 > As it often happens with open source, the first results from search engines refer to the old, unmaintained forks. 119 > Be sure to use the links above to find the good versions. 120 121 Since bats-core is [TAP (Test Anything Protocol)](https://testanything.org/) 122 compliant, its output is in a standardized format. It can be integrated with a 123 separate [tap reporter](https://www.npmjs.com/package/tape#pretty-reporters) or 124 included in a larger test suite. The TAP specification is pretty minimalist and 125 some glue may be needed. 126 127 128 # setup and teardown 129 130 If you have read the bats-core tutorial linked above, you are aware of the 131 `setup` and `teardown` functions. 132 133 What you may have overlooked is that the script body outside the functions is 134 executed multiple times, so we have to be careful of what we put there. 135 136 Here we have a look at the execution flow with two tests: 137 138 ```sh 139 echo "begin" >&3 140 141 setup_file() { 142 echo "setup_file" >&3 143 } 144 145 teardown_file() { 146 echo "teardown_file" >&3 147 } 148 149 setup() { 150 echo "setup" >&3 151 } 152 153 teardown() { 154 echo "teardown" >&3 155 } 156 157 @test "test 1" { 158 echo "test #1" >&3 159 } 160 161 @test "test 2" { 162 echo "test #2" >&3 163 } 164 165 echo "end" >&3 166 ``` 167 168 The above test suite produces the following output: 169 170 ``` 171 begin 172 end 173 setup_file 174 begin 175 end 176 ✓ test 1 177 setup 178 test #1 179 teardown 180 begin 181 end 182 ✓ test 2 183 setup 184 test #2 185 teardown 186 teardown_file 187 ``` 188 189 See how "begin" and "end" are repeated three times each? The code outside 190 setup/teardown/test functions is really executed three times (more as you add 191 more tests). You can put there variables or function definitions, but keep it 192 to a minimum and [don't write anything to the standard 193 output](https://bats-core.readthedocs.io/en/stable/writing-tests.html#code-outside-of-test-cases). 194 For most things you want to use `setup_file()` instead. 195 196 But.. there is a but. Quoting from [the FAQ](https://bats-core.readthedocs.io/en/stable/faq.html): 197 198 > You can simply source <your>.sh files. However, be aware that source`ing 199 > files with errors outside of any function (or inside `setup_file) will trip 200 > up bats and lead to hard to diagnose errors. Therefore, it is safest to only 201 > source inside setup or the test functions themselves. 202 203 This doesn't mean you can't do that, just that you're on your own if the is an error. 204 205 206 # Testing crowdsec 207 208 ## Fixtures 209 210 For the purpose of functional tests, crowdsec and its companions (cscli, plugin 211 notifiers, bouncers) are installed in a local environment, which means tests 212 should not install or touch anything outside a `./test/local` directory. This 213 includes binaries, configuration files, databases, data downloaded from 214 internet, logs... The use of `/tmp` is tolerated, but BATS also provides [three 215 useful 216 variables](https://bats-core.readthedocs.io/en/stable/writing-tests.html#special-variables): 217 `$BATS_SUITE_TMPDIR`, `$BATS_FILE_TMPDIR` and `$BATS_TEST_TMPDIR` that let you 218 ensure your desired level of isolation of temporary files across the tests. 219 220 When built with `make bats-build`, the binaries will look there by default for 221 their configuration and data needs. So you can run `./local/bin/cscli` from 222 a shell with no need for further parameters. 223 224 To set up the installation described above we provide a couple of scripts, 225 `instance-data` and `instance-crowdsec`. They manage fixture and background 226 processes; they are meant to be used in setup/teardown in several ways, 227 according to the specific needs of the group of tests in the file. 228 229 - `instance-data make` 230 231 Creates a tar file in `./local-init/init-config-data.tar`. 232 The file contains all the configuration, hub and database files needed 233 to restore crowdsec to a known initial state. 234 Things like `machines add ...`, `capi register`, `hub update`, `collections 235 install crowdsecurity/linux` are executed here so they don't need to be 236 repeated for each test or group of tests. 237 238 - `instance-data load` 239 240 Extracts the files created by `instance-data make` for use by the local 241 crowdsec instance. Crowdsec must not be running while this operation is 242 performed. 243 244 - `instance-crowdsec [ start | stop ]` 245 246 Runs (or stops) crowdsec as a background process. PID and lockfiles are 247 written in `./local/var/run/`. 248 249 250 Here are some ways to use these two scripts. 251 252 - case 1: load a fresh crowsec instance + data for each test (01_base, 10_bouncers, 20_collections...) 253 254 This offers the best isolation, but the tests run slower. More importantly, 255 since there is no concept of "grouping" tests in bats-core with the exception 256 of files, if you need to perform some setup that is common to two or more 257 tests, you will have to repeat the code. 258 259 - case 2: load a fresh set of data for each test, but run crowdsec only for 260 the tests that need it, possibly after altering the configuration 261 (02_nolapi, 03_noagent, 04_nocapi, 40_live-ban) 262 263 This is useful because: 1) you sometimes don't want crowdsec to run at all, 264 for example when testing `cscli` in isolation, or you may want to tweak the 265 configuration inside the test function before running the lapi/agent. See 266 how we use `yq` to change the YAML files to that effect. 267 268 - case 3: start crowdsec with the initial set of configuration+data once, and keep it 269 running for all the tests (50_simulation, 98_ipv4, 98_ipv6) 270 271 This offers no isolation across tests, which over time could break more 272 often as result, but you can rely on the test order to test more complex 273 scenarios with a reasonable performance and the least amount of code. 274 275 276 ## status, stdout and stderr 277 278 As we said, if any error occurs inside a test function, the test 279 fails immediately. You call `mycommand`, it exits with $? != 0, the test fails. 280 281 But how to test the output, then? If we call `run mycommand`, then $? will be 0 282 allowing the test to keep running. The real error status is stored in the 283 `$status` variable, and the command output and standard error content are put 284 together in the `$output` variable. By specifying `run --separate-stderr`, you 285 can have separated `$output` and `$stderr` variables. 286 287 The above is better explained in the bats-core tutorial. If you have not read it 288 yet, now is a good time. 289 290 For convenience, the `rune` function is an alias for `run --separate-stderr`, which 291 can be used in most cases. For example, you don't want extraneous log messages in 292 the way when you check the output of a command. 293 294 The `$output` variable gets special treatment with the 295 [bats-support](https://github.com/bats-core/bats-support) and 296 [bats-assert][https://github.com/bats-core/bats-assert) plugins and can be 297 checked with `assert_*` commands. The `$stderr` variable does not have these, 298 but we can use `run echo "$stderr"` and then check `$output` with asserts. 299 300 Remember that `run` always overwrites the `$output` variable, so if you consume 301 it with `run jq <(output)` you can only do it once, because the second time it 302 will read the output of the `jq` command. But you can construct a list of all 303 the values you want and check them all in a single step. 304 305 Note that `<(output)` is substituted with the file name of a file descriptor, 306 so `mycmd <(output)` can become `mycmd /dev/fd/23`, `mycmd /tmp//sh-np.hpc7Zs` 307 or `mycmd /proc/self/fd/38` depending on the platform. To have it fed to 308 standard input, use `< <(output)`. 309 310 See the `lib/*.sh` and `bats/*.bats` files for other tricks we employ. 311 312 ## file operations 313 314 We included the [bats-file](https://github.com/bats-core/bats-file) plugin to 315 check the result of file system operations: existence, type/size/ownership checks 316 on files, symlinks, directories, sockets. 317 318 ## mocking external commands 319 320 The [bats-mock](https://github.com/grayhemp/bats-mock) plugin allows you to define 321 a "fake" behavior for the external commands called by a package under test, and 322 to record and assert which parameters are passed to it. 323 324 ## gotchas 325 326 - pay attention to tests that are not run - for example "bats warning: Executed 143 327 instead of expected 144 tests". They are especially tricky to debug. 328 329 - using the `load` command in `teardown()` causes tests to be silently skipped or break in "funny" 330 ways. The other functions seem safe. 331 332 # Testing with MySQL and Postgres 333 334 By default, the tests are run with the embedded sqlite database engine. This should be 335 enough in most cases, since the database operations are abstracted via the `ent` ORM. 336 337 You can however easily test with a different engine. 338 339 ## Postgres 340 341 Run Postgres somewhere, version 10 or above - easy to do in a docker container. 342 343 You also need to install a postgresql-client package or equivalent, to provide 344 recent pg_dump and pg_restore executables (not older than the PG version in the docker container). 345 346 ``` 347 $ sudo docker run --detach --name=postgres -p 5432:5432 --env="POSTGRES_PASSWORD=postgres" postgres:latest 348 ``` 349 350 The name of the container is not really important. 351 If you are not using Docker, you may need to adjust the `PGHOST`/`PGPORT`/`PGPASSWORD`/`PGUSER` variables 352 (defaults are 127.0.0.1, 5432, postgres, postgres). 353 354 An additional user and database both named `crowdsec_test` will be created. 355 356 Now you can build and run the tests (we skip bats-test-hub here, they really 357 should not be affected by a change in DB). 358 359 ``` 360 $ export DB_BACKEND=postgres 361 $ make clean bats-build bats-fixture bats-test 362 ``` 363 364 or with the pgx driver: 365 366 ``` 367 $ export DB_BACKEND=pgx 368 $ make clean bats-build bats-fixture bats-test 369 ``` 370 371 The value of DB_BACKEND must not change between the build/fixture/test steps. 372 373 ## MySQL/MariaDB 374 375 Same considerations as above, with the following changes: 376 377 ``` 378 $ sudo docker run --cap-add=sys_nice --detach --name=mysql -p 3306:3306 --env="MYSQL_ROOT_PASSWORD=password" mysql 379 [...] 380 $ export DB_BACKEND=mysql 381 $ make clean bats-build bats-fixture bats-test 382 ``` 383 384 or for MariaDB 385 386 ``` 387 $ sudo docker run --cap-add=sys_nice --detach --name=mariadb -p 3306:3306 --env="MYSQL_ROOT_PASSWORD=password" mariadb 388 ``` 389 390 A mysql-client package is required as well. 391 392 ## troubleshooting 393 394 - CAPI is disabled, why? 395 Most tests don't need it. Helper scripts are provided in `test/enable-capi` 396 and `test/disable-capi` for interactive use, and two library functions 397 `config_enable_capi` and `config_disable_capi` to call inside the tests. 398 You still need to call `cscli capi register` after enabling it. 399 400 - My tests are hanging forever, why? 401 See if you have a jq/yq or similar process waiting for standard input. Hint: 402 you can pass a file from the result of the previous `run` command with 403 `<(output)`. This substitutes the expression with a file name, but if you 404 really want it in standard input, you have to use `< <(output)`. Bash is 405 awesome but the syntax is often weird. 406 407 - I can't do X with jq. 408 If you prefer you can use yq. It can parse and generate json, and it has a 409 different syntax. 410 411 - I get "while parsing /tmp/....: yaml: line 5: mapping values are not allowed in this context" 412 Check the heredocs (the <<EOT blocks). Each line must start with a hard TAB 413 followed by spaces. You are probably missing some tabs. 414 415 ## gotchas 416 417 - Testing with Postgres or MySQL/MariaDB leads to (unpredictably) failing 418 tests in the GitHub workflows, so we had to disable them by default. We do 419 run these in a separate environment before doing releases. They should always 420 pass if you run them in a development machine. 421