summaryrefslogtreecommitdiffstats
path: root/doc/devel/unit-tests.dox
blob: 2ab129b9c50c07091ce47524ce01de2401662168 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
// Copyright (C) 2015-2022 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

/**

 @page unitTests Building Kea with Unit Tests

By default, Kea is built without unit-tests as they're used mostly by
developers and prospective contributors. Kea's unit-tests are using
<a href="https://github.com/google/googletest">gtest framework</a> from
Google. Google's approach has changed over the years. For some time,
they were very keen on not installing gtest as a normal software would
be, but rather provide gtest as sources. This was further complicated
with the fact that some Linux distributions packaged gtest and tried
to mimic its installation. Kea tries its best to accommodate all typical
situations and provides two switches to point to gtest. You can use
`--with-gtest` or `--with-gtest-source`. Both attempt to locate gtest
on their own. However, if neither of them can find it, you can specify
the path explicitly. For example, on ubuntu with googletest package installed,
you can do the following for Kea to find it:

@code
sudo apt install googletest
./configure --with-gtest-source=/usr/src/googletest
@endcode

Depending on how you compiled or installed \c gtest (e.g. from sources
or using some package management system) one of those two switches will
find \c gtest. After that you make and run the unit-tests with:

@code
make
make check
@endcode

As usual, using \c -jX option will speed up compilation. This parameter is
even more useful for unit-tests as there are over 6000 unit-tests and their
compilation is significantly slower than just the production Kea sources.

Kea should work with reasonably recent gtest versions. We recently tried
with 1.7.0, 1.8.0, 1.8.1 and 1.10.0.

@section unitTestsEnvironmentVariables Environment Variables

The following environment variable can affect the unit tests:

- KEA_LOCKFILE_DIR - Specifies a directory where the logging system should
  create its lock file. If not specified, it is <i>prefix</i>/var/run/kea,
  where <i>prefix</i> defaults to /usr/local. This variable must not end
  with a slash. There is one special value, "none", which instructs Kea to
  not create a lock file at all. This may cause issues if several processes
  log to the same file.  (Also see the Kea User's Guide, section 15.3.)

- KEA_LOGGER_DESTINATION - Specifies the logging destination. If not set, logged
  messages will not be recorded anywhere. There are three special values:
  stdout, stderr and syslog. Any other value is interpreted as a filename.
  (Also see Kea User's Guide, section 15.3.)

- KEA_LOG_CHECK_VERBOSE - Specifies the log check default verbosity. If not
  set, unit tests using the log utils to verify that logs are generated as
  expected are by default silent. If set, these unit tests display real
  and expected logs.

- KEA_MYSQL_HAVE_SSL - Specifies the SSL/TLS support status of MySQL.
  When not set the corresponding MySQL global variable is read and
  the environment of the unit test process is updated so usually this
  variable is manually set only in order to enforce a particular status.

- KEA_PIDFILE_DIR - Specifies the directory which should be used for PID files
  as used by dhcp::Daemon or its derivatives. If not specified, the
  default is <i>prefix</i>/var/run/kea, where <i>prefix</i> defaults to
  /usr/local. This variable must not end with a slash.

- KEA_SOCKET_TEST_DIR - If set, it specifies the directory where Unix
  sockets are created. There is an operating system limitation on how
  long a Unix socket path can be, typically slightly over 100
  characters. By default unit-tests create sockets in temporary folder
  under /tmp folder. KEA_SOCKET_TEST_DIR can be specified to instruct
  unit-tests to use a different directory. It must not end with slash.

- KEA_TEST_DB_WIPE_DATA_ONLY - Unit tests which use a Kea unit test
  database take steps to ensure they are starting with an empty database
  of the correct schema version.  The first step taken is to simply
  delete the transient data (such as leases, reservations, etc..), provided
  the schema exists and is the expected version.  If the schema does not
  exist, is not the expected version, or for some reason the data wipe fails,
  the schema will be dropped and recreated. Setting this value to "false"
  will cause the test setup logic to always drop and create the database
  schema. The default value is "true".

- KEA_TLS_CHECK_VERBOSE - Specifies the TLS check default verbosity. If not
  set, TLS unit tests triggering expected failures are by default silent.
  If set, these TLS unit tests display the error messages which are very
  dependent on the cryptographic backend and boost library versions.

@note Setting KEA_TEST_DB_WIPE_DATA_ONLY to false may dramatically
increase the time it takes each unit test to execute.

- GTEST_OUTPUT - Save the test results in XML files. Make it point to a location
where a file or directory can be safely created. If there is no file or
directory at that location, adding a trailing slash
`GTEST_OUTPUT=${PWD}/test-results/` will create a directory containing an XML
file for each directory being tested. Leaving the slash out will create a single
XML file and will put all the test results in it.

- DEBUG - Set this variable to make shell tests output the commands that are
run. They are shown just before they are effectively run. Can be set to
anything e.g. `DEBUG=true`. `unset DEBUG` to remove this behavior.

@section unitTestsSanitizers Use Sanitizers

  GCC and LLVM support some sanitizers which perform additional tests
  at runtime, for instance the ThreadSanitizer (aka TSan) detects data
  race in executed C++ code (unfortunately on macOS it intercepts
  signals and fails to send them to waiting select system calls so
  some tests always fail when it is used, experiments are run with
  different versions of Tsan).

  The simplest way to enable a sanitizer is to add it to the CXXFLAGS
  environment variable in .configure by e.g. <i>-fsanitize=thread</i>.

  When enabling lcov (code coverage), some gtest functions are detected as
  not being thread safe. It is recommended to disable lcov when enabling
  thread sanitizer.

@section unitTestsDatabaseConfig Databases Configuration for Unit Tests

  With the use of databases requiring separate authorisation, there are
  certain database-specific pre-requisites for successfully running the unit
  tests.  These are listed in the following sections.

  @subsection unitTestsDatabaseUsers Database Users Required for Unit Tests

  Unit tests validating database backends require that the <i>keatest</i>
  database is created. This database should be empty.  The unit tests
  also require that the <i>keatest</i> user is created and that this user
  is configured to access the database with a password of <i>keatest</i>.
  Unit tests use these credentials to create database schema, run test cases
  and drop the schema. Thus, the <i>keatest</i> user must have sufficiently
  high privileges to create and drop tables, as well as insert and modify the
  data within those tables.

  The database backends which support read only access to the host
  reservations databases (currently MySQL and PostgreSQL) include unit
  tests verifying that a database user with read-only privileges can be
  used to retrieve host reservations. Those tests require another user,
  <i>keatest_readonly</i>, with SQL SELECT privilege to the <i>keatest</i>
  database (i.e. without INSERT, UPDATE etc.), is also created.
  <i>keatest_readonly</i> should also have the password <i>keatest</i>.

  The following sections provide step-by-step guidelines how to setup the
  databases for running unit tests.

  @subsection mysqlUnitTestsPrerequisites MySQL Database

  The steps to create the database and users are:

  -# Log into MySQL as root:
  @verbatim
  % mysql -u root -p
  Enter password:
     :
  mysql>@endverbatim\n
  -# Create the test database.  This must be called "keatest":
  @verbatim
  mysql> CREATE DATABASE keatest;
  mysql>@endverbatim\n
  -# Create the users under which the test client will connect to the database
  (the apostrophes around the words <i>keatest</i>, <i>keatest_readonly</i>, and
   <i>localhost</i> are required):
  @verbatim
  mysql> CREATE USER 'keatest'@'localhost' IDENTIFIED BY 'keatest';
  mysql> CREATE USER 'keatest_readonly'@'localhost' IDENTIFIED BY 'keatest';
  mysql> CREATE USER 'keatest_secure'@'localhost' IDENTIFIED BY 'keatest';
  mysql> ALTER USER 'keatest_secure'@'localhost' REQUIRE X509;
  mysql>@endverbatim\n
  Some old versions of MySQL do not support the REQUIRE keyword in ALTER
  USER. Fortunately all versions support it in GRANT even if it is less secure
  as the requirement will apply only to commands for the database instead
  to all connections so all commands. And of course in production many
  stronger requirements are available: X509 only requires the user to
  present a certificate: you can specify which certificate by requiring
  for instance a particular Subject Name, etc.
  -# Grant the created users permissions to access the <i>keatest</i> database
  (again, the apostrophes around the user names and <i>localhost</i>
  are required):
  @verbatim
  mysql> GRANT ALL ON keatest.* TO 'keatest'@'localhost';
  mysql> GRANT SELECT ON keatest.* TO 'keatest_readonly'@'localhost';
  mysql> GRANT ALL ON keatest.* TO 'keatest_secure'@'localhost';
  mysql>@endverbatim\n
  When the REQUIRE in ALTER USER is not supported change the last line by:
  @verbatim
  mysql> GRANT ALL ON keatest.* TO 'keatest_secure'@'localhost' REQUIRE X509;
  mysql>@endverbatim\n
  -# If you get <i>You do not have the SUPER privilege and binary logging is
  enabled</i> error message, you need to add:
  @verbatim
  mysql> SET GLOBAL LOG_BIN_TRUST_FUNCTION_CREATORS = 1;
  mysql>@endverbatim\n
  -# Exit MySQL:
  @verbatim
  mysql> quit
  Bye
  %@endverbatim

  The unit tests are run automatically when "make check" is executed (providing
  that Kea has been built with the \c --with-mysql switch (see the installation
  section in the <a href="https://kea.readthedocs.io/">Kea Administrator
  Reference Manual</a>).

 @subsection mysqlUnitTestsTLS MySQL Database with SSL/TLS

  Usually MySQL is compiled with SSL/TLS support using OpenSSL.
  This is easy to verify using the:

@verbatim
mysql> SHOW GLOBAL VARIABLES LIKE 'have_ssl';
@endverbatim

  The variable is documented to have three possible values:

- DISABLED: compiled with TLS support but it was not configured

- YES: compiled with configured TLS support

- NO: not compiled with TLS support

The value of this MySQL global variable is reflected by the
KEA_MYSQL_HAVE_SSL environment variable.

The keatest_secure user requires X509 so a client certificate. Of course
in production a stricter requirement should be used, in particular when
a client certificate should be bound to a particular user.

MySQL unit tests reuse the asiolink library setup. This .my.cnf
configuration file works with MariaDB 10.6.4:

@verbatim
[mysqld]
ssl_cert=<kea-sources>/src/lib/asiolink/testutils/ca/kea-server.crt
ssl_key=<kea-sources>/src/lib/asiolink/testutils/ca/kea-server.key
ssl_ca=<kea-sources>/src/lib/asiolink/testutils/ca/kea-ca.crt

[client-mariadb]
ssl_cert=<kea-sources>/src/lib/asiolink/testutils/ca/kea-client.crt
ssl_key=<kea-sources>/src/lib/asiolink/testutils/ca/kea-client.key
ssl_ca=<kea-sources>/src/lib/asiolink/testutils/ca/kea-ca.crt
ssl-verify-server-cert
@endverbatim

The last statement requires mutual authentication named two way in the
MariaDB documentation. For MySQL versions greater than 5.7.11 this
statement should be replaced by:

@verbatim
[client]
...
ssl-mode=VERIFY_IDENTITY
@endverbatim

On Debian some MySQL packages use GnuTLS instead OpenSSL to provide
the SSL/TLS support: this is known to not work with this proposed setup.

 @subsection pgsqlUnitTestsPrerequisites PostgreSQL Database

  PostgreSQL set up differs from system to system. Please consult your
  operating system-specific PostgreSQL documentation. The remainder of
  that section uses Ubuntu 13.10 x64 (with PostgreSQL 9.0+) as an example.

  On Ubuntu, PostgreSQL is installed (with <tt>sudo apt-get install
  postgresql</tt>) under user <i>postgres</i>. To create new databases
  or add new users, initial commands must be issued under this username:

@verbatim
$ sudo -u postgres psql postgres
[sudo] password for thomson:
psql (9.1.12)
Type "help" for help.
postgres=# CREATE USER keatest WITH PASSWORD 'keatest';
CREATE ROLE
postgres=# CREATE DATABASE keatest;
CREATE DATABASE
postgres=# GRANT ALL PRIVILEGES ON DATABASE keatest TO keatest;
GRANT
postgres=# \q
@endverbatim

  PostgreSQL versions earlier than 9.0 don't provide an SQL statement for granting
  privileges on all tables in a database. In newer PostgreSQL versions, it is
  possible to grant specific privileges on all tables within a schema.
  However, this only affects tables which exist when the privileges are granted.
  To ensure that the user has specific privileges to tables dynamically created
  by the unit tests, the default schema privileges must be altered.

  The following example demonstrates how to create the user <i>keatest_readonly</i>,
  which has SELECT privilege to the tables within the <i>keatest</i> database,
  in Postgres 9.0+. For earlier versions of Postgres, it is recommended to
  simply grant full privileges to <i>keatest_readonly</i>, using the
  same steps as for the <i>keatest</i> user.

@verbatim
$ psql -U postgres
Password for user postgres:
psql (9.1.12)
Type "help" for help.

postgres=# CREATE USER keatest_readonly WITH PASSWORD 'keatest';
CREATE ROLE
postgres=# \q

$ psql -U keatest
Password for user keatest:
psql (9.1.12)
Type "help" for help.

keatest=> ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES to keatest_readonly;
ALTER DEFAULT PRIVILEGES
keatest=> \q
@endverbatim

  Note that the <i>keatest</i> user (rather than <i>postgres</i>) is used to grant
  privileges to the <i>keatest_readonly</i> user. This ensures that the SELECT
  privilege is granted only on the tables that the <i>keatest</i> user can access
  within the public schema.

  It seems this no longer works on recent versions of PostgreSQL: if you get
  a permission problem on SELECT on the schema_version table for
  eatest_readonly, please try with the schema loaded:

@verbatim
$ psql -h localhost -U keatest -d keatest
Password for user keatest:
psql (11.3 (Debian 11.3-1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.

keatest=> GRANT SELECT ON ALL TABLES IN SCHEMA public TO keatest_readonly;
GRANT
keatest=> \q
@endverbatim

  Now we  should be able to log into the newly created database using both user
  names:
@verbatim
$ psql -d keatest -U keatest
Password for user keatest:
psql (9.1.12)
Type "help" for help.

keatest=> \q

$ psql -d keatest -U keatest_readonly
Password for user keatest_readonly:
psql (9.1.12)
Type "help" for help.

keatest=>
@endverbatim

  If instead of seeing keatest=> prompt, your login is refused with an error
  code about failed peer or
  <tt>Ident authentication failed for user "keatest"</tt>, it means that
  PostgreSQL is configured to check unix username and reject login attempts if
  PostgreSQL names are different. To alter that, the PostgreSQL pg_hba.conf
  configuration file must be changed. It usually resides at
  <tt>/var/lib/postgresql/data/pg_hba.conf</tt> or at
  <tt>/etc/postgresql/${version}/main/pg_hba.conf</tt>, but you can find out
  for sure by running
  <tt>sudo -u postgres psql -t -c 'SHOW hba_file'</tt>. Make sure
  that all the authentication methods are changed to "md5" like this:

@verbatim
local   all             all                                     md5
host    all             all             127.0.0.1/32            md5
host    all             all             ::1/128                 md5
@endverbatim

  Another possible problem is that you get no password prompt. This is
  most probably because you have no <tt>pg_hba.conf</tt> config file
  and everybody is by default trusted. As it has a very bad effect
  on the security you should have been warned this is a highly unsafe
  configuration. The solution is the same, i.e., require password or
  md5 authentication method.

  If you lose the postgres user access you can first add:
@verbatim
local   all             postgres                                trust
@endverbatim
  to trust only the local postgres user. Note the postgres user can
  be pgsql on some systems.

  Please consult your PostgreSQL user manual before applying those changes as
  those changes may expose your other databases that you run on the same system.
  In general case, it is a poor idea to run anything of value on a system
  that runs tests. Use caution!

  The unit tests are run automatically when "make check" is executed (providing
  that Kea has been build with the \c --with-pgsql switch (see the installation
  section in the <a href="https://kea.readthedocs.io">Kea Administrator
  Reference Manual</a>).

@section unitTestsKerberos Kerberos Configuration for Unit Tests

The GSS-TSIG hook library uses the GSS-API with Kerberos. While there are
no doubts that the hook can be safely used with a valid Kerberos configuration
in production, unit tests reported problems on some systems.

GSS-TSIG hook unit tests use a setup inherited from bind9 with old crypto
settings which are not allowed by default Kerberos system configuration.
A simple workaround is to set the KRB5_CONFIG environment variable to
a random value that doesn't match a file (e.g. KRB5_CONFIG=).

@section writingShellScriptsAndTests Writing shell scripts and tests

Shell tests are `shellcheck`ed. But there are other writing practices that are
good to follow in order to keep, not only shell tests, but shell scripts in
general, POSIX-compliant. See below:

- For portability, all shell scripts should have a shebang.
@code
#!/bin/sh
@endcode
The `sh` shell can differ on various operating systems. On most systems it is
GNU sh. Notable exceptions are Alpine which links it to ash, FreeBSD which has
the primordial non-GNU sh, Ubuntu which links it to dash. These four shells
should all be tested against, when adding shell scripts or making changes to
them.

- Reference variables with curly brackets.
@code
${var}  # better
$var
@endcode
For consistency with cases where you need advanced features from the variables
which make the curly brackets mandatory. Such cases are:
@code
# Retrieving variable/string length...
${#var}

# Defaulting to a given value when the variable is undefined...
${var-default}

# Substituting the variable with a given value when the variable is defined...
${var+value}

# Concatenating the value of a variable with an alphanumeric constant...
${var}constant
@endcode

- Always use `printf` instead of `echo`. There are times when a newline is not
desired such as when you want to print on a single line from multiple points
in your script or when you want to get the character count from an expression:
@code
var1='not '
var2=' you want to ignore'

# Prints the number of characters.
printf '%s' "${var1}something${var2}" | wc -c
# Result:
  32

# This one prints a plus one i.e. the inherent newline.
echo "${var1}something${var2}" | wc -c
# Result:
  33

# `echo` does have `-n` to suppress newline, but...
# SC2039: In POSIX sh, echo flags are undefined.
echo -n "${var1}something${var2}" | wc -c
# Result:
  32  # sometimes, other times an error
@endcode
`printf` also has the benefit of separating the format from the actual variables
which has many use cases. One such use case is coloring output with ANSI escape
sequence codes, see the `test_finish` function in
`src/lib/testutils/dhcp_test_lib.sh.in`, which is not possible with POSIX echo.

- `set -e` should be enabled at all times to immediately fail when a command
returns a non-zero exit code. There are times when you expect a non-zero exit
code in your tests. This is what the `run_command` function in
`src/lib/testutils/dhcp_test_lib.sh.in` is for. It momentarily disables the `-e`
flag to capture the output and exit code and enables it again afterwards. The
variables used are `${EXIT_CODE}` and `${OUTPUT}`. /dev/stderr is not captured.
`run_command` also doesn't work with pipes and redirections. When these
mechanisms are needed, you can always wrap your complex expression in a function
and then call `run_command wrapping_function`. Alternatively, if you only care
about checking for zero exit code, you can use `if` conditions.
@code
# The non-zero exit code does not stop script execution, but we can still adjust
# behavior based on it.
if maybe-failing-command; then
  f
else
  g
fi
@endcode
There are times when your piped or redirected command that is expected to return
non-zero is so small or has so few instantiations that it doesn't deserve a
separate function. Such an example could be grepping for something in a
variable. `grep` returns a non-zero exit code if it doesn't find anything. In
that case, you can add `|| true` at the end to signal the fact that you allow
finding nothing like so:
@code
printf '%s' "${var}" | grep -F 'search-criterion' || true
@endcode

- `set -u` should be enabled at all times to immediately signal an undefined
variable. If you're a stickler for the legacy behavior of defaulting to an empty
space then you can reference all your variables with:
@code
# Default variable is an empty space.
${var-}

# Or like this if you prefer to quote the empty space.
${var-''}
@endcode

- SC2086: Double quote to prevent globbing and word splitting.
Even though covered by shellcheck, it's worth mentioning because shellcheck
doesn't always warn you because of what might be a systematic deduction of when
quoting is not needed. Globbing is a pattern matching mechanism. It's used a lot
with the `*` wildcard character e.g. `ls *.txt`. Sometimes, you want to glob
intentionally. In that case, you can omit quoting, but it is preferable to take
the wildcard characters outside the variable so that you are able to quote to
prevent other globbing and word splitting e.g.:
@code
# Globbing done right
ls "${var}"*.txt

# Word splitting problem
path='/home/my user'
ls ${path}

# Result:
  ls: cannot access '/home/my': No such file or directory
  ls: cannot access 'user': No such file or directory

# Word splitting avoided
path='/home/my user'
ls "${path}"

# Result:
  Desktop
  Documents
  Downloads
@endcode
If you have an expression composed of multiple variables don't just quote the
variables. It's correct, but not readable. Quote the entire expression.
@code
# no
"${var1}"some-fixed-contiguous-value"${var2}"

# yes
"${var1}some-fixed-contiguous-value${var2}"
@endcode

- Single quote expressions when no variables are inside. This is to avoid the
need to escape special shell characters like `$`.

- All shell tests are created from `.in` autoconf template files. They
initially contain template variables like `@prefix@` which are then substituted
with the configured values. All of these should be double quoted, not
single-quoted since they themselves can contain shell variables that need to be
expanded.

- Use `$(...)` notation instead of legacy backticks. One important advantage is
that the `$(...)` notation allows for nested executions.
@code
# SC2006 Use `$(...)` notation instead of legacy backticked `...`.
hostname=`cat /etc/hostname`

# Better
hostname=$(cat /etc/hostname)

# Nested executions
is_ssh_open=$(nc -vz $(cat /etc/hostname).lab.isc.org 22)

# Results in confusing "command not found" messages.
is_ssh_open=`nc -vz `cat /etc/hostname`.lab.isc.org 22`
@endcode

- When using `test` and `[`, `==` is just a convenience alias for `=`. Use `=`
because it's more widely supported. If using, `[[`, then indeed `==` has extra
features like glob matching. But don't use `[[`, it's not part of the POSIX
standard.

- Capturing parameters in functions or scripts simply cannot be done without
breaking POSIX compliance. In POSIX, pass the quoted parameters `"${@}"` as
positional parameters to all the function and scripts invocations. if it gets
too unmanageable or you need custom positional arguments then break your script
into multiple scripts or handle all possible parameters and don't accept any
ad-hoc parameters.
@code
# Neither of these preserve original quoting.
parameters="${*}"
parameters="${@}"

# In advanced shells this could be done with lists.
parameters=( "${@}" )
do-something --some --other --optional --parameters "${parameters[@]}"

# Proper POSIX way
do-something --some --other --optional --parameters "${@}"
@endcode

- Never use `eval`. It doesn't preserve original quoting. Have faith that there
are always good alternatives.

 */