Compare commits

...

112 Commits

Author SHA1 Message Date
Jaromil
0d06c994cf fix tests to run also when swap present 2024-05-12 22:09:46 +02:00
Jaromil
6c383ffd64 move unfinished portable veracrypt version in extras 2024-05-12 21:47:57 +02:00
nerun
ddb2de6072 Updated copyright year in 'generate_translatable_strings' script. 2024-05-06 15:06:56 +02:00
nerun
3398b7bf89 Updated extras/translations/README. 2024-05-06 15:06:56 +02:00
nerun
abe5704658 Updated all language files to Tomb v2.10 (.po/.pot). 2024-05-06 15:06:56 +02:00
nerun
d0b84d78a0 Fixed tomb listing failure message: localization friendly. 2024-05-06 15:06:56 +02:00
Melroy van den Berg
91adbbe183 Update copyright in translation source 2024-01-26 00:38:37 +01:00
Melroy van den Berg
766cd27c1b Update copyright lines 2024-01-26 00:38:13 +01:00
Narrat
dee2b0f8c4 list_processes: use lsof to list processes
Parsing the output from lsof had possibilities for race conditions.
Either due to short lived processes or issuing "tomb ps" from a terminal which cwd is from inside the tomb.
This would spit out available users on the system.
To avoid this use the lsof output directly.

In the future formatting could be reintroduced via commands like
"lsof +D "$tombmount" -F Lc" or "lsof +D "$tombmount" -F Lc0".

This fixes #503
2024-01-26 00:37:46 +01:00
nerun
13eeef7c6c manpage: replaced 'mlocate' to 'mlocate/plocate', because of commit 59d7331. 2024-01-26 00:30:58 +01:00
nerun
194d60fe9a manpage: better formatting of .EX/.EE macros 2024-01-26 00:30:58 +01:00
nerun
7f91cc917d manpage: fixed typo in "bind-hooks" 2024-01-26 00:30:58 +01:00
nerun
09d981f0fc manpage: 80 columns line break in PASSWORD INPUT. 2024-01-26 00:30:58 +01:00
vladislav doster
e1afecb832 fix: remove duplicate help in subcommands_opts 2024-01-26 00:28:50 +01:00
Melroy van den Berg
e97c088a26 Update copyright in README 2024-01-24 08:42:08 +01:00
Melroy van den Berg
9385ddee43 Update download link 2024-01-24 08:42:08 +01:00
27792f4421 Fix umount issue when path have spaces in directory or file name 2024-01-12 15:09:47 +01:00
8be3163022 Fix spaces in bind-hooks paths 2024-01-12 15:09:47 +01:00
0rtz
21da75adab Use cat instead of _cat
_cat is not defined/autoloaded anywhere, so use plain unix cat utility
to avoid getting "command not found" error
2023-12-16 15:52:34 +01:00
bittin1ddc447d824349b2
a0b633fb77 Translated using Weblate (Swedish)
Currently translated at 100.0% (290 of 290 strings)

Translation: Tomb/tomb
Translate-URL: https://hosted.weblate.org/projects/tomb/tomb/sv/
2023-12-07 02:33:37 +01:00
Sven Geuer
f9ec17d126 Fix some table mimicking lines to honor the recent line width 2023-09-30 22:55:28 +02:00
Sven Geuer
dcdf4cb3bb Fix operators to compare numerically, not lexicographically
The operator < compares two strings lexicographically resulting in that a 100MB tomb is considered smaller than 47MB or 18MB.

Closes #489
2023-09-30 22:54:35 +02:00
Jaromil
0c8d4cf477 short README, info moved to homepage 2023-09-20 16:52:12 +02:00
Jaromil
51452e4e6a documentation update for release
long due, was ready in november 2022 and basically left unchanged at
that stage, except the deprecation of veracrypt in the experimental
portable branch, which is not included in the stable release.
2023-09-18 16:04:19 +02:00
nerun
58035cf14a Added README to extras/translations. 2023-08-24 21:21:18 +02:00
nerun
06ea734878 Added Brazilian Portuguese (pt_BR) translation (for Tomb 2.10). Also
created tomb.pot template for version 2.10.
2023-08-24 21:21:18 +02:00
nerun
347462cb0c Updated Brazilian Portuguese translation (pt_BR). Also updated Makefile
to include this translation.
2023-08-24 21:21:18 +02:00
nerun
cd9fa46964 Translations: updated 'es', 'fr', 'it' and 'ru.po' to line up help message
(PR #481 and Jaromil request in PR #483).
2023-08-24 21:21:18 +02:00
nerun
d8989e1955 extras/kdf-keys: fixed hexencode warning caused by wrong type. 2023-08-24 21:11:42 +02:00
nerun
59d7331cb0 Fixed issue with mlocate. Now tomb accepts also plocate for search/index. 2023-06-27 22:47:10 +02:00
Dyne.org foundation
4d72a2180a Added translation using Weblate (Portuguese (Brazil)) 2023-06-27 16:53:44 +02:00
nerun
7249afe98e tomb help: "engrave" lined up (added +5 spaces). 2023-06-27 07:30:31 +02:00
nerun
0eed3cf321 doc: updated tomb_manpage.pdf with new version. 2023-06-27 07:30:31 +02:00
nerun
e316ef22cc manpage: fixed wrong information about --kdfmem. 2023-06-26 03:09:54 +02:00
nerun
82e5342334 extras/gtomb: inserted a "wait" statement in the dig, forge, lock and open functions;
spelling standardization (canceled to cancelled);
simplified the call to MONMORT icon inside zenity functions;
zenity warning function changes;
fixed missing dots.
2023-06-25 19:37:27 +02:00
nerun
4e3221e937 extras/gtomb: replaced soft with hard tabs to maintain code standard. 2023-06-25 19:37:27 +02:00
Narrat
364a457fbc Remove unused check on python2 2023-06-24 04:35:48 +02:00
Alex
571cdeafbf Translated using Weblate (French)
Currently translated at 98.6% (286 of 290 strings)

Translation: Tomb/tomb
Translate-URL: https://hosted.weblate.org/projects/tomb/tomb/fr/
2023-03-21 16:59:45 +01:00
Maxime Leroy
b9cf58054a Translated using Weblate (French)
Currently translated at 96.2% (279 of 290 strings)

Translation: Tomb/tomb
Translate-URL: https://hosted.weblate.org/projects/tomb/tomb/fr/
2023-03-21 16:59:45 +01:00
Selene ToyKeeper
646d2c33fd Use $SUDO_ASKPASS if defined. Allows non-terminal sudo password prompts.
The sudo program checks this env var and uses it, but only if --askpass
was given at the command line, or if it thinks there is no terminal.
But the terminal detection is unreliable, so give it --askpass if there
is an askpass program defined in the environment.

To try it, simply "export SUDO_ASKPASS=/usr/bin/ssh-askpass" before
running tomb.

For me personally, this makes it possible to have a hotkey to run
"pass" and "pass tomb" related commands.  Without this patch, invoking
via hotkey causes my window manager to lock up while waiting for a
password on the VT where Xorg was started... and since it's locked up,
I can't change to the VT to enter the data it's waiting for.  So I have
to log in via ssh from another host to recover it.

So, instead of locking up... now it can use a GUI askpass program.
2023-03-21 16:58:18 +01:00
nerun
713cfd3071 extras/gtomb: SWAPOFF defaults to false. 2023-03-21 16:54:57 +01:00
nerun
5eaa92e614 extras/gtomb again: sudo no longer stored in variable. 2023-03-21 16:54:57 +01:00
nerun
d65a3f1586 extras/gtomb severe update. 2023-03-21 16:54:57 +01:00
Jaromil
b5eae55c8b portable: remove strtosize and use MiB 2022-12-21 10:08:33 +01:00
Jaromil
0130fcf86d update docs 2022-11-21 09:44:21 +01:00
Jaromil
30d2e92a77 sponsor buttons 2022-11-16 15:25:42 +01:00
Jaromil
0d004f312d improve build status badges on readme 2022-11-16 15:25:42 +01:00
Jaromil
b14be5022e fix readme build status links 2022-11-16 15:25:42 +01:00
Jaromil
ed841b9f32 update CI scripts 2022-11-16 15:25:42 +01:00
Jaromil
6e8ef0a29a documentation update for a new release 2022-11-16 15:25:42 +01:00
Jaromil
5772d39e19 updated portable readme with links to milestones and more info 2022-11-14 10:40:15 +01:00
Jaromil
1ae5845c61 portable tomb tests in ci 2022-11-14 10:40:15 +01:00
Jaromil
fce89023a5 try to use native ubuntu matrix on github ci
remove doas tests

removed comparison from bind-hook test
2022-11-14 10:40:15 +01:00
Jaromil
be911b1e16 first basic implementation of portable tomb
this new "flavor" of tomb uses veracrypt for mounted volumes and
POSIX sh only for its scripting, is a work in progress and still
lacks full functionality, but provides a proof-of-concept to be
developed further if needs arise.
2022-11-13 22:00:41 +01:00
Chris Vogel
12684e6740 cmd lock: extend parameters to --filesystem
The --filesystem option can be used  to  specify
an  alternative  filesystem used to format the tomb, in place of the default "ext4".

Beside "btrfs" now the following parameters to --filesystem are supported:

"ext3"    using operating system defaults
"ext4"    using operating system defaults
"btrfs"   for tombs >= 47MB using operating system defaults
"btrfsmixedmode"    for tombs >=18MB btrfs mixed mode (see mkfs.btrfs(8))
"ext3maxinodes"     ext3 with a maximum of inodes (for many small files)
"ext4maxinodes"     ext4 with a maximum of inodes (for many small files)

These changes help use scenarios in which there is a great number of small files
and/or directories in a small filesystem, like e.g. the pass-tomb extension to pass.
2022-10-28 16:19:28 +02:00
Jaromil
42390b78c0 add a notice and link to tomb3 in readme 2022-10-24 15:28:05 +02:00
Valentin Heidelberger
3639b2e0ec fix typo Succesfully -> Successfully 2022-10-20 12:25:40 +02:00
Chris Vogel
8502bdc722 Adjusted messages returned about zramswap
As explained to me here https://github.com/dyne/Tomb/pull/447#discussion_r999777746
it would be bad to change existing messages. Reverted to the already existing messages
and added new messages about zramswap.
2022-10-20 12:22:23 +02:00
Chris Vogel
e463a6e600 fixed patch error
Something went wrong when I tried to move my changes from my installation
into the git. Now the tests complete successfully.
2022-10-20 12:22:23 +02:00
Chris Vogel
6af298e15f recognize zram as swap
Check if unencrypted swap is zram. If it is zram check whether a writeback to
disk is configured.

Unencrypted zramswap not written to disk is accepted.

ToDo (as for other unencrypted swap): check if the writeback happens on an
already encrypted disk/partition.
2022-10-20 12:22:23 +02:00
Jaromil
1655fd5a99 add tomb/.cleanexit lock to check on clean umount 2022-10-11 08:26:37 +02:00
Jens Rischbieth
8782dab9ed Update Dockerfile
Building the image fails as pinentry doesn't exist in the repository, only pinentry-curses. Image building works w/ only pinentry-curses.
2022-07-20 22:43:01 +02:00
AHOHNMYC
3b44a6be3a Translated using Weblate (Russian)
Currently translated at 99.3% (286 of 288 strings)

Translation: Tomb/tomb
Translate-URL: https://hosted.weblate.org/projects/tomb/tomb/ru/
2022-05-26 01:27:24 +02:00
Francisco Serrador
7a1b699cea Translated using Weblate (Spanish)
Currently translated at 100.0% (288 of 288 strings)

Translation: Tomb/tomb
Translate-URL: https://hosted.weblate.org/projects/tomb/tomb/es/
2022-05-26 01:27:24 +02:00
luzhen
85af9459aa Translated using Weblate (French)
Currently translated at 74.6% (215 of 288 strings)

Translation: Tomb/tomb
Translate-URL: https://hosted.weblate.org/projects/tomb/tomb/fr/
2022-05-26 01:27:24 +02:00
Jaromil
f4e2ae2f97 add win11 compatibility notice to readme 2022-04-15 11:07:40 +02:00
Jaromil
62806769af disable broken gpg recipient tests 2022-04-13 22:53:36 +02:00
Jaromil
8be3e7d2a0 gpg remove check for secret key 2022-04-13 22:53:36 +02:00
Jaromil
97343cf590 fix detection of gpg recipient id validity 2022-04-13 22:53:36 +02:00
Jaromil
60034b0b55 update test docker to devuan chimaera
install pinentry curses and gpg

update github action checkout v3
2022-04-13 22:53:36 +02:00
Narrat
b7822afaf0
lo_mount: check for loop support needs privilege escalation (#437)
If there is no free loop device, the call of loopsetup -f will create one and return it. For this it needs privilege escalation.
It doesn't need those, if there is already an used device, but that cannot be guaranteed.

Closes #436
2022-04-07 15:20:38 +02:00
Jaromil
6955719f04 fix shellcheck linter 2022-03-03 10:27:35 +01:00
Jaromil
8ceeca8769
KDF support for argon2 memory intensive algorithm (#432)
* KDF support for argon2 memory intensive algorithm

following many requests, here is support for argon2 KDF to be switched
on using --kdftype argon2 (--kdf iterations --kdfmem memory)

effective memory required is 2^memory KiB, defaults to 18 (262 MiB)
number of iterations are still specified as --kdf argument

requires the argon2 reference C implementation from P-H-C
also requires tomb-kdb-pbkdf2-gensalt in extras/kdf-keys

example usage:
tomb forge -k argon.key --kdf 10 --kdftype argon2

* manual updates for argon2
2022-02-20 22:05:01 +01:00
Jaromil
03c93ef976
Sudo loopback improve (#435)
* small improvements to loopback setup and --sudo

* support reading hostname from file

also tolerate not finding the hostname (fill localhost)

address #428

* cleanup and support sup,sud,pkexec
2022-02-20 21:57:05 +01:00
Jaromil
5a5eb6ddcf
wrap all references to $tombmount string into quotes (#434)
this may fix whitespace issues referred by #433 and previously related
to bind mounts as of #222
2022-02-20 21:10:47 +01:00
Jaromil
9323c1caf8 reduce noise of superuser password requests
messages downgraded to verbose mode (fix #431)

also updated dates and version
2022-01-31 04:44:10 +01:00
Weblate (bot)
f7046c5941
Translations update from Weblate (#401)
* Translated using Weblate (French)

Currently translated at 74.6% (215 of 288 strings)

Translation: Tomb/tomb
Translate-URL: https://hosted.weblate.org/projects/tomb/tomb/fr/

* Added translation using Weblate (Chinese (Simplified))

* Translated using Weblate (French)

Currently translated at 76.0% (219 of 288 strings)

Translation: Tomb/tomb
Translate-URL: https://hosted.weblate.org/projects/tomb/tomb/fr/

* Translated using Weblate (French)

Currently translated at 76.3% (220 of 288 strings)

Translation: Tomb/tomb
Translate-URL: https://hosted.weblate.org/projects/tomb/tomb/fr/

* Translated using Weblate (French)

Currently translated at 78.4% (226 of 288 strings)

Translation: Tomb/tomb
Translate-URL: https://hosted.weblate.org/projects/tomb/tomb/fr/

* Translated using Weblate (French)

Currently translated at 95.4% (275 of 288 strings)

Translation: Tomb/tomb
Translate-URL: https://hosted.weblate.org/projects/tomb/tomb/fr/

Co-authored-by: luzhen <luzhen@uniontech.com>
Co-authored-by: Dyne.org foundation <translate@dyne.org>
Co-authored-by: Maxime Leroy <lisacintosh@gmail.com>
2022-01-30 16:21:18 +01:00
Artur Malimonov
b6ffe1a2f1
GitHub Actions CI (#430)
Add Github Actions CI config
2022-01-08 01:26:51 +01:00
Jaromil
84ef4bef4b remove pkexec autodetection 2021-10-21 11:49:54 +02:00
Jaromil
e0ba8c5f4d wider support for privilege escalation tools
now supporting also pkexec (polkit daemon), suckless' sup and
sud.dyne.org

pkexec is autodetected when polkit is running

manpage documents the --sudo flag which overrides any autodetection
2021-10-20 16:27:27 +02:00
Jaromil
90eec3d830 adopt external utility cat instead of zsh builtin
fix #426
2021-10-13 15:46:34 +02:00
Damien Ready
930b414889 Correct some typos 2021-10-01 18:32:44 +02:00
Jaromil
61a9d1a420 updates to dockerfile 2021-08-10 10:57:43 +02:00
timvisee
d2d35bc8db Quiet cryptsetup when opening a Tomb with -q provided 2021-07-21 09:06:22 +02:00
timvisee
585af6a61b Quiet fsck when opening a Tomb with -q provided 2021-07-21 09:06:22 +02:00
Jaromil
2a744fe89d quick install instructions for qt tray
fix #413
2021-06-24 07:42:29 +02:00
Jaromil
b235f16ce4 correct shell sequence to open without tomb script
fix #372 thanks to @grcancelliere
2021-06-24 07:36:50 +02:00
heat-wave
ce521ed2e2 Validate user-supplied sudo alternative (in name only) 2021-04-15 12:27:26 +02:00
heat-wave
087ecd25a2 Restrict access to doas.conf 2021-04-15 12:27:26 +02:00
heat-wave
24a89b680d Disable sphinx tests to test loop devices limit hypothesis 2021-04-15 12:27:26 +02:00
heat-wave
3860487a0b Fix typos in doas config and --sudo opt definition 2021-04-15 12:27:26 +02:00
heat-wave
61386ca646 Support for sudo alternatives such as doas 2021-04-15 12:27:26 +02:00
Denis Roio
ae21619d04
Merge pull request #408 from heat-wave/fix/sphinx-test-configs
Fix configs and dockerfile to enable sphinx in tests
2021-01-31 00:10:22 +01:00
Jaromil
7f2e22c517 fix read-only opening of tombs using -o ro
skip touch, chown and some minor operations when read-only
2021-01-25 18:26:49 +01:00
Jaromil
fb3ffcec03 manpage mention of fallocate(1) for faster dig 2021-01-25 15:14:31 +01:00
heat-wave
815b8f4218 Fix configs and dockerfile to enable sphinx in tests 2021-01-23 19:16:29 +00:00
Jaromil
f35ad11e3f updated documentation for release 2021-01-04 22:00:29 +01:00
Denis Roio
c0d1a7584d
Merge pull request #406 from mcrapet/dig_sudo
dig/forge unecessary sudo
2021-01-04 10:51:40 +01:00
Matthieu Crapet
c5701793fb minor typo/formatting fixes
Signed-off-by: Matthieu Crapet <mcrapet@gmail.com>
2021-01-02 10:22:06 +01:00
Matthieu Crapet
02812f4c06 tomb forge: useless sudo and chown
Depending script invokation, behavior is not exactly similar.
Assuming that if SUDO_USER is set, the _sudo invokation can be dropped (EUID=0).
In the other case, user has created file, owner is already good, don't call chown.

Preparation:
$ tomb dig foo.tomb -s 10

Method 1:
$ sudo tomb forge foo.tomb.key -v

Method 2:
$ tomb forge foo.tomb.key -v
... ask user password to gain superuser privileges
...
Sorry, user <username> is not allowed to execute '/bin/chown <uid>:<gid> foo.tomb.key' as root on <hostname>.

Signed-off-by: Matthieu Crapet <mcrapet@gmail.com>
2021-01-02 10:20:26 +01:00
Matthieu Crapet
99f10bf215 tomb dig: useless sudo and chown
Depending script invokation, behavior is not exactly similar.
Assuming that if SUDO_USER is set, the _sudo invokation can be dropped (EUID=0).
In the other case, user has created file, owner is already good, don't call chown.

Method 1:
$ sudo tomb dig foo.tomb -s 10 -v

Method 2:
$ tomb dig foo.tomb -s 10 -v
... ask user password to gain superuser privileges
...
Sorry, user <username> is not allowed to execute '/bin/chown <uid>:<gid> foo.tomb' as root on <hostname>.

Signed-off-by: Matthieu Crapet <mcrapet@gmail.com>
2021-01-02 10:12:55 +01:00
Matthieu Crapet
859a5c7783 TMPPREFIX is not supposed to be a directory
http://zsh.sourceforge.net/Doc/Release/Files.html
TMPPREFIX defaults to /tmp/zsh (for zsh shell)

Note: --tmp command line switch is not documented?

Signed-off-by: Matthieu Crapet <mcrapet@gmail.com>
2021-01-02 10:09:51 +01:00
Matthieu Crapet
312915b4b3 fix potential wrong _USER value
"id -u" gives then uid not a the name.
https://man7.org/linux/man-pages/man1/id.1.html

Signed-off-by: Matthieu Crapet <mcrapet@gmail.com>
2021-01-02 10:09:51 +01:00
Jaromil
d227695778 add support for tombs formatted with the btrfs filesystem
basic functionality working for open, close and resize

still needs test coverage and some minor checks
2020-12-29 13:50:04 +01:00
Jaromil
8d5a85658f explicit return codes for all operations 2020-12-29 11:52:23 +01:00
Jaromil
0ac5a34c20 close luks mapper and end with an error on lock format failures 2020-12-29 10:56:20 +01:00
Jaromil
d8360688b3 fix wrong comparison of $pass_asked left by last commits
fix #404
2020-12-29 10:52:16 +01:00
Jaromil
7a81ad032d add zsh to version output 2020-12-29 08:58:14 +01:00
Denis Roio
9f30f7da89
Merge pull request #403 from catleeball/cflags
Read CFLAGS in kdf-keys makefile
2020-12-29 08:07:09 +01:00
🎶🎷🐛 Lee Ball
6d87b7e355 Read CFLAGS in kdf-keys makefile
Added $(CFLAGS) in the kdf-keys makefile to allow users to specify
additional build flags.
2020-12-23 22:03:49 -08:00
Jaromil
b6fff10c2a add file among dependencies in the INSTALL guide
fix #396
2020-12-16 12:52:17 +01:00
Jaromil
b0de6e07b2 adopt everywhere -z test to check when variables are empty
check works both for empty ("") and non-existing vars and is a fix
for regression #398 to work on older Zsh versions. It is normalized
through all tomb's code.
2020-12-15 18:22:38 +01:00
94 changed files with 18728 additions and 5659 deletions

4
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,4 @@
github: dyne
patreon: dyneorg
open_collective: dyne

45
.github/workflows/linux.yml vendored Normal file
View File

@ -0,0 +1,45 @@
name:
💀 Linux Tomb
on:
push:
paths-ignore:
- 'doc/**'
- 'portable/**'
- '*.md'
branches:
- master
pull_request:
paths-ignore:
- 'doc/**'
- 'portable/**'
- '*.md'
branches:
- master
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}
cancel-in-progress: true
jobs:
build-matrix:
strategy:
matrix:
os: [ubuntu-22.04, ubuntu-20.04]
runs-on: ${{ matrix.os }}
steps:
- name: Install tomb dependencies
run: |
sudo apt-get update -y -q
sudo apt-get install -y -q zsh cryptsetup gpg gawk libgcrypt20-dev steghide qrencode python2 python3-pip python3-dev libssl-dev make gcc sudo gettext bsdmainutils file pinentry-curses xxd libsodium23 libsodium-dev
- uses: actions/checkout@v3
- name: Build the KDF extras
run: |
make --directory=extras/kdf-keys
sudo make --directory=extras/kdf-keys install
- name: Disable swap
run: sudo swapoff -a
- name: Run main tests
run: sudo make test
- name: Run KDF tests
run: sudo make -C extras/kdf-keys test

97
.github/workflows/portable.yml vendored Normal file
View File

@ -0,0 +1,97 @@
name: ☠️ Portable tomb
on:
push:
paths:
- 'portable/**'
- '.github/workflows/portable.yml'
branches:
- master
pull_request:
paths:
- 'portable/**'
- '.github/workflows/portable.yml'
branches:
- master
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}
cancel-in-progress: true
jobs:
ubuntu-20:
name: 🐧 test Ubuntu-20
runs-on: ubuntu-20.04
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v3
- name: Install portable tomb dependencies
run: |
sudo apt-get install -q -y make openssl wget fuse3 util-linux
sudo make -C portable download-deps-ubuntu20
- name: Run portable tomb test
run: sudo make -C portable create-open-close
- uses: actions/upload-artifact@v3
with:
name: ubuntu-test.tomb
path: portable/test.tomb*
retention-days: 1
freebsd:
runs-on: macos-12
name: 😈 test FreeBSD-13
needs: [ubuntu-20]
steps:
- uses: actions/checkout@v2
- name: Download portable tomb volume built on Ubuntu
uses: actions/download-artifact@v3
with:
name: ubuntu-test.tomb
path: portable
- name: Test in FreeBSD
id: test
uses: vmactions/freebsd-vm@v0
with:
usesh: true
prepare: |
pkg install -y sudo gmake bash fusefs-libs3 fusefs-lkl e2fsprogs wget
wget https://files.dyne.org/tomb3/third-party/veracrypt-freebsd13-amd64
mv veracrypt-freebsd13-amd64 /usr/bin/veracrypt
wget https://files.dyne.org/tomb3/third-party/zenroom-freebsd13-amd64
mv zenroom-freebsd13-amd64 /usr/bin/zenroom
chmod a+x /usr/bin/veracrypt /usr/bin/zenroom
kldload fusefs
run: |
sudo gmake -C portable
sudo gmake -C portable check-random-data
- uses: actions/upload-artifact@v3
with:
name: freebsd-test.tomb
path: portable/test.tomb*
retention-days: 1
ubuntu-last-cleanup:
name: 🎯 final Ubuntu tests and cleanup
runs-on: ubuntu-20.04
needs: [freebsd]
steps:
- uses: actions/checkout@v3
- name: Install compiler and dependencies
run: |
sudo apt-get install -q -y make openssl wget fuse3 util-linux
sudo make -C portable download-deps-ubuntu20
- name: Download tomb built on FreeBSD
uses: actions/download-artifact@v3
with:
name: freebsd-test.tomb
path: portable
- name: Check integrity FreeBSD->Ubuntu
run: sudo make -C portable check-random-data
- name: delete FreeBSD tomb artifacts
uses: geekyeggo/delete-artifact@v2
with:
name: freebsd-test.tomb
- name: delete Ubuntu tomb artifacts
uses: geekyeggo/delete-artifact@v2
with:
name: ubuntu-test.tomb

View File

@ -2,27 +2,28 @@ Cryptsetup was originally written in 2004 by Jana Saout
LUKS extensions are written in 2006 by Clemens Fruhwirth
Tomb is written and maintained since 2007 by Denis Roio <J@dyne.org>
Tomb is written and maintained since 2007 by [Denis "Jaromil" Roio](https://jaromil.dyne.org)
Tomb includes code and advices by Anathema, Boyska, Hellekin O. Wolf,
Daniel Rodriguez, Parazyd, Alexandre Pujol, AitorATuin, Narrat and
Artur Malimonov.
Daniel Rodriguez, Parazyd, Alexandre Pujol, AitorATuin, Narrat, Artur
Malimonov and Chris Vogel.
The 'gtomb' GUI based on Zenity is written by Parazyd.
The 'gtomb' GUI based on Zenity is written by Parazyd and Daniel Dias Rodrigues.
The Qt5 desktop tray GUI is written by Gianluca Montecchi.
Python Tomb wrappers are contributed by Reiven and Boyska.
The Docker Tomb wrapper is contributed by Greg Tczap
The Docker Tomb wrapper is contributed by Greg Tczap and Jens Rischbieth.
Artwork is contributed by Jordi aka Mon Mort and Logan VanCuren.
Gettext internationalization and Spanish translation is contributed by
Daniel Rodriguez. French translation by Hellekin and Roy Lockhart,
Russian translation by fsLeg, German translation by Jerry Polfer,
Italian translation by Massimiliano Augello and Swedish translation by
PLJ / Kosovoper.
Daniel Rodriguez and Francisco Serrador. French translation by
Hellekin and Roy Lockhart, Russian translation by fsLeg and AHOHNMYC,
German translation by Jerry Polfer, Italian translation by
Massimiliano Augello and Swedish translation by PLJ / Kosovoper,
general fixes contributed by Daniel Dias Rodrigues.
Testing, reviews and documentation contributed by Dreamer, Vlax,
Shining the Translucent, Mancausoft, Asbesto Molesto, Nignux, TheJH,
@ -31,13 +32,12 @@ Polfer, Jim Turner, Maxime Arthaud, RobertMX, mhogomchungu Mandeep
Bhutani, Emil Lundberg, Joel Montes de Oca, Armin Mesbah, Arusekk,
Stephan Schindel, Asbjørn Apeland, Victor Calvert, bjonnh, SargoDevel,
AitorATuin, Alexis Danizan, Sven Geuer, Greg Tczap, Aaron Janse, Mark
Mykkanen, Alexis Danizan, Steve Litt, James R, and...
the Linux Action Show!
Mykkanen, Alexis Danizan, Steve Litt, James R, Matthieu Crapet, Selene
ToyKeeper, Valentin Heidelberg and... the Linux Action Show!
Tomb includes an implementation of the "Password-Based Key Derivation
Function v2" based on GCrypt and written by Anthony Thyssen, with
fixes contributed by AitorATuin.
Tomb developers can be contacted via GitHub issues on
https://www.github.com/dyne/Tomb or over IRC https://irc.dyne.org
channel **#dyne** (or directly over port 9999 with SSL)
Some of the Tomb developers can be contacted via [GitHub discussions](https://github.com/dyne/Tomb/discussions)
or over Telegram via the [Dyne.org Chat Channel](https://t.me/dyne_chat).

View File

@ -1,5 +1,30 @@
# Tomb ChangeLog
## 2.10
### Sep 2023
This release adds optional support for Argon2 KDF brute-force
protection and introduces support for doas as an alternative to sudo
for privilege escalation. It also improves support for BTRFS formatted
Tombs, adds zram detection as swap memory, updates documentation and
translations and cleans up the script code.
## 2.9
### Jan 2021
This release fixes all bugs introduced by the unfortunate 2.8 release
series in 2020 as well introduces support for BTRFS formatted
Tombs. The fixes are for password insertion to work on all desktops,
as well the fix to a regression when using old Zsh versions. The new
feature is activated by the '--filesystem' flag on 'lock' commands.
It only supports BTRFS as internal filesystem of a Tomb instead of the
default EXT4; resizing works as well to create and send or receive
subvolumes and snapshots inside a Tomb. There are also some cleanups,
small error handling improvements and no more need for suid actions by
'forge' and 'dig' commands.
## 2.8.1
### Nov 2020

View File

@ -5,6 +5,7 @@
Tomb needs a few programs to be installed on a system in order to work:
* zsh
* file
* sudo
* gnupg
* cryptsetup
@ -50,7 +51,7 @@ The key can also be hidden in an image, to be used as key later
tomb bury -k secrets.tomb.key nosferatu.jpg (hide the key in a jpeg image)
tomb open -k nosferatu.jpg secrets.tomb (use the jpeg image to open the tomb)
Or backupped to a QRCode that can be printed on paper and hidden in
Or backed up to a QRCode that can be printed on paper and hidden in
books. QRCodes can be scanned with any mobile application, resulting
into a block of text that can be used with `-k` just as a normal key.

View File

@ -22,16 +22,16 @@ cryptsetup v2.1 a new default has been introduced (luks2) and the
Using Tomb version 2.6 (and future releases) the problem opening tombs
using recent GNU/Linux distributions is fixed.
# Whitespaces in KDF passwords
# Whitespace in KDF passwords
## Issue affecting passwords used with PBKDF2 keys (<2.6)
Up until and including Tomb's version 2.5 the PBKDF2 wrapper for keys
in Tomb has a bug affecting passwords that contain whitespaces. Since
in Tomb has a bug affecting passwords that contain whitespace. Since
the passwords are trimmed at the first whitespace, this makes them
weaker, while fortunately the KDF transformation still applies.
This issue is fixed in Tomb version 2.6: all users adopting KDF keys
that have passwords containing whitespaces should change them,
that have passwords containing whitespace should change them,
knowing that their "old password" is trimmed until the whitespace.
Users adopting GPG keys or plain (without KDF wrapper) can ignore

View File

@ -2,6 +2,12 @@ PROG = tomb
PREFIX ?= /usr/local
MANDIR ?= ${PREFIX}/share/man
deps:
@[ -r /etc/debian_version ] && { \
apt-get install -qy zsh cryptsetup file gnupg pinentry-curses; }
@[ -r /etc/fedora-release ] ^^ { \
yum install -y zsh cryptsetup file gnupg pinentry-curses; }
all:
@echo
@echo "Tomb is a script and does not need compilation, it can be simply executed."
@ -26,4 +32,4 @@ test:
make -C extras/test
lint:
shellcheck -s zsh -e SC1073,SC1027,SC1072,SC1083,SC1009 tomb
shellcheck -s bash -e SC1058,SC1073,SC1072,SC1009 tomb

295
README.md
View File

@ -1,52 +1,14 @@
..... ..
.H8888888h. ~-. . uW8"
888888888888x `> u. .. . : `t888
X~ `?888888hx~ ...ue888b .888: x888 x888. 8888 .
' x8.^"*88*" 888R Y888r ~`8888~'888X`?888f` 9888.z88N
`-:- X8888x 888R I888> X888 888X '888> 9888 888E
488888> 888R I888> X888 888X '888> 9888 888E
.. `"88* 888R I888> X888 888X '888> 9888 888E
x88888nX" . u8888cJ888 X888 888X '888> 9888 888E
!"*8888888n.. : "*888*P" "*88%""*88" '888!` .8888 888"
' "*88888888* 'Y" `~ " `"` `%888*%"
^"***"` "`
# Tomb: The Linux Crypto Undertaker
*A minimalistic commandline tool to manage encrypted volumes* aka **The Crypto Undertaker**
[![Build Status](https://github.com/dyne/tomb/actions/workflows/linux.yml/badge.svg)](https://github.com/dyne/Tomb/actions)
<!-- [![Build Status](https://github.com/dyne/tomb/actions/workflows/portable.yml/badge.svg)](https://github.com/dyne/Tomb/actions) -->
[![software by Dyne.org](https://files.dyne.org/software_by_dyne.png)](http://www.dyne.org)
Minimalistic command line tool based on Linux dm-crypt and LUKS, trusted by hackers since 2007.
More information and updates on website: https://www.dyne.org/software/tomb
Get the stable .tar.gz signed release for production use!
Download it from https://files.dyne.org/tomb
For the instructions on how to get started using Tomb, see [INSTALL](INSTALL.md).
You can keep your volumes secure and easily manageable with simple commands.
![tomb's logo](https://github.com/dyne/Tomb/blob/master/extras/images/monmort.png)
[![Build Status](https://travis-ci.org/dyne/Tomb.svg?branch=master)](https://travis-ci.org/dyne/Tomb)
# What is Tomb, the crypto undertaker?
Tomb aims to be a free and open source system for easy encryption and
backup of personal files, written in code that is easy to review and
links well reliable GNU/Linux components.
Tomb's ambition is to improve safety by way of:
- a minimalist design consisting in small and well readable code
- facilitation of good practices, i.e: key/storage physical separation
- adoption of a few standard and well tested implementations.
At present, Tomb consists of a simple shell script (Zsh) using
standard filesystem tools (GNU) and the cryptographic API of the Linux
kernel (cryptsetup and LUKS). Tomb can also produce machine parsable
output to facilitate its use inside graphical applications.
# How does it work?
To create a Tomb, do:
```
$ tomb dig -s 100 secret.tomb
$ tomb forge secret.tomb.key
@ -56,254 +18,41 @@ To open it, do
```
$ tomb open secret.tomb -k secret.tomb.key
```
and after you are done
And after you are done
```
$ tomb close
```
or if you are in a hurry
Or, if you are in a hurry, kill all processes with open files inside your tomb and close it.
```
$ tomb slam all
$ tomb slam
```
## [Get started on dyne.org/software/tomb](https://dyne.org/software/tomb)
```
Syntax: tomb [options] command [arguments]
<a href="https://dyne.org/software/tomb"><img src="https://files.dyne.org/software_by_dyne.png" width="30%"></a>
Commands:
All information is found on our website.
// Creation:
dig create a new empty TOMB file of size -s in MiB
forge create a new KEY file and set its password
lock installs a lock on a TOMB to use it with KEY
Use only stable and signed releases in production!
// Operations on tombs:
open open an existing TOMB (-k KEY file or - for stdin)
index update the search indexes of tombs
search looks for filenames matching text patterns
list list of open TOMBs and information on them
ps list of running processes inside open TOMBs
close close a specific TOMB (or 'all')
slam slam a TOMB killing all programs using it
resize resize a TOMB to a new size -s (can only grow)
### 💾 [Download from files.dyne.org/tomb](https://files.dyne.org/tomb/)
// Operations on keys:
passwd change the password of a KEY (needs old pass)
setkey change the KEY locking a TOMB (needs old key and pass)
Tomb's development is community-based!
// Backup on paper:
engrave makes a QR code of a KEY to be saved on paper
## How can you help
// Steganography:
bury hide a KEY inside a JPEG image (for use with -k)
exhume extract a KEY from a JPEG image (prints to stdout)
cloak transform a KEY into a TEXT using CIPHER (for use with -k)
uncloak extract a KEY from a TEXT file using CIPHER (prints to stdout)
Donations are very welcome on [dyne.org/donate](https://www.dyne.org/donate)
Options:
Translations are also welcome: see our simple [translation guide](https://github.com/dyne/Tomb/blob/master/extras/translations/README.md)
-s size of the tomb file when creating/resizing one (in MiB)
-k path to the key to be used ('-k -' to read from stdin)
-n don't process the hooks found in tomb
-o options passed to commands: open, lock, forge (see man)
-f force operation (i.e. open even if swap is active)
-g use a GnuPG key to encrypt a tomb key
-r provide GnuPG recipients (separated by comma)
-R provide GnuPG hidden recipients (separated by comma)
--kdf forge keys armored against dictionary attacks
Tomb's code is short and readable: don't be afraid to inspect it! If you plan to submit a PR, please remember that this is a minimalist tool, and the code should be short and readable. Also, first, read our small intro to [Tomb's coding style](doc/HACKING.txt).
-h print this help
-v print version, license and list of available ciphers
-q run quietly without printing informations
-D print debugging information at runtime
```
We have a [space for issues](https://github.com/dyne/Tomb/issues) open for detailed bug reports. Always include the Tomb version being used when filing a case, please.
# What is this for, exactly?
This tool can be used to dig .tomb files (LUKS volumes), forge keys
protected by a password (GnuPG encryption) and use the keys to lock
the tombs. Tombs are like single files whose contents are inaccessible
in the absence of the key they were locked with and its password.
Once open, the tombs are just like normal folders and can contain
different files, plus they offer advanced functionalities like bind
and execution hooks and fast search, or they can be slammed close even
if busy. Keys can be stored on separate media like USB sticks, NFC,
on-line SSH servers or bluetooth devices to make the transport of data
safer: one always needs both the tomb and the key, plus its password,
to access it.
The tomb script takes care of several details to improve user's
behaviour and the security of tombs in everyday usage: protects the
typing of passwords from keyloggers, facilitates hiding keys inside
images, indexes and search a tomb's contents, mounts directories in
place, lists open tombs and selectively closes them, warns the user
about free space and last time usage, etc.
# How secure is this?
Death is the only sure thing in life. That said, Tomb is a pretty
secure tool especially because it is kept minimal, its source is
always open to review (even when installed) and its code is easy to
read with a bit of shell script knowledge.
All encryption tools being used in Tomb are included as default in
many GNU/Linux operating systems and therefore are regularly peer
reviewed: we don't add anything else to them really, just a layer of
usability.
The file [KNOWN_BUGS.md](KNOWN_BUGS.md) contains some notes on known
vulnerabilities and threat model analysis.
In absence or malfunction of the Tomb script it is always possible to
access the contents of a Tomb only using a dm-crypt enabled Linux
kernel, cryptsetup, GnuPG and any shell interpreter issuing the
following commands as root:
```
lo=$(losetup -f)
losetup -f secret.tomb
pass="$(gpg -d secret.key)"
echo -n -e "$pass" | cryptsetup --key-file - luksOpen $lo secret
mount /dev/mapper/secret /mnt
unset pass
```
One can change the last argument `/mnt` to where the Tomb has to be
mounted and made accessible. To close the tomb then use:
```
umount /mnt
cryptsetup luksClose /dev/mapper/secret
```
# Stage of development
Tomb is an evolution of the 'mknest' tool developed for the
[dyne:bolic](http://www.dynebolic.org) 100% Free GNU/Linux
distribution in 2001: its 'nesting' mechanism allowed the liveCD users
to encrypt and make persistent home directories. Since then the same
shell routines kept being maintained and used for dyne:bolic until
2007, when they were ported to work on more GNU/Linux distributions.
As of today, Tomb is a very stable tool also used in mission critical
situations by a number of activists in dangerous zones. It has been
reviewed by forensics analysts and it can be considered safe for
adoption where the integrity of information stored depends on the
user's behaviour and the strength of a standard AES-256 (XTS plain)
encryption algorithm (current default) or, at one's option, other
equivalent standards supported by the Linux kernel.
## Compatibility
Tomb can be used in conjunction with some other software applications,
some are developed by Dyne.org, but some also by third parties.
### Included extra applications
These auxiliary applications are found in the extras/ subdirectory of
distributed Tomb's sourcecode:
- [GTomb](extras/gtomb) is a graphical interface using zenity
- [gtk-tray](extras/gtk-tray) is a graphical tray icon for GTK panels
- [qt-tray](extras/qt-tray) is a graphical tray icon for QT panels
- [tomber](extras/tomber) is a wrapper to use Tomb in Python scripts
- [docker](extras/docker) is a wrapper to use Tomb through Docker
![skulls and pythons](https://github.com/dyne/Tomb/blob/master/extras/images/python_for_tomb.png)
### External applications
The following applications are not included in Tomb's distributed
sourcecode, but are known and tested to be compatible with Tomb:
- [Secrets](https://secrets.dyne.org) is a software that can be operated on-line and on-site to split a Tomb key in shares to be distributed to peers: some of them have to agree to combine back the shares in order to retrieve the key.
- [zuluCrypt](https://mhogomchungu.github.io/zuluCrypt/) is a graphical application to manage various types of encrypted volumes on GNU/Linux, among them also Tombs, written in C++.
- [Mausoleum](https://github.com/mandeep/Mausoleum) is a graphical interface to facilitate the creation and management of tombs, written in Python.
- [pass-tomb](https://github.com/roddhjav/pass-tomb) is a console based wrapper of the excellent password keeping program [pass](https://www.passwordstore.org) that helps to keep the whole tree of password encrypted inside a tomb. It is written in Bash.
If you are writing a project supporting Tomb volumes or wrapping Tomb, let us know!
## Compliancy
Tomb qualifies as sound for use on information rated as "top secret"
when used on an underlying stack of carefully reviewed hardware
(random number generator and other components) and software (Linux
kernel build, crypto modules, device manager, compiler used to built,
shell interpreter and packaged dependencies).
Tomb volumes are fully compliant with the FIPS 197 advanced encryption
standard published by NIST and with the following industry standards:
- Information technology -- Security techniques -- Encryption algorithms
- [ISO/IEC 18033-1:2015](http://www.iso.org/iso/home/store/catalogue_tc/catalogue_detail.htm?csnumber=54530) -- Part 1: General
- [ISO/IEC 18033-3:2010](http://www.iso.org/iso/home/store/catalogue_ics/catalogue_detail_ics.htm?csnumber=54531) -- Part 3: Block ciphers
Tomb implementation is known to address at least partially issues raised in:
- Information technology -- Security techniques -- Key management
- [ISO/IEC 11770-1:2010](http://www.iso.org/iso/home/store/catalogue_tc/catalogue_detail.htm?csnumber=53456) -- Part 1: Framework
- [ISO/IEC 11770-2:2008](http://www.iso.org/iso/home/store/catalogue_tc/catalogue_detail.htm?csnumber=46370) -- Part 2: Mechanisms using symmetric techniques
- [ISO/IEC 27005:2011](http://www.iso.org/iso/home/store/catalogue_tc/catalogue_detail.htm?csnumber=56742) Information technology -- Security techniques -- Information security risk management
- [ISO/IEC 24759:2014](http://www.iso.org/iso/home/store/catalogue_tc/catalogue_detail.htm?csnumber=59142) Information technology -- Security techniques -- Test requirements for cryptographic modules
Any help on further verification of compliancy is very welcome, as the
access to ISO/IEC document is limited due to its expensive nature.
# Use stable releases in production!
Anyone planning to use Tomb to store and access secrets should not use
the latest development version in Git, but use instead the .tar.gz
release on https://files.dyne.org/tomb . The stable version will
always ensure backward compatibility with older tombs: we make sure it
creates sane tombs and keys by running various tests before releasing
it. The development version in Git might introduce sudden bugs and is
not guaranteed to produce backward- or forward-compatible tombs and keys.
The development version in Git should be used to report bugs, test new
features and develop patches.
So be warned: do not use the latest Git version in production
environments, but use a stable release versioned and packed as
tarball on https://files.dyne.org/tomb
![Day of the dead](https://github.com/dyne/Tomb/blob/master/extras/images/DayOfTheDead.jpg)
# How can you help
Donations are very welcome, please go to https://www.dyne.org/donate
Translations are also welcome: they can be contributed editing sending
the .po files in [extras/translations](extras/translations).
The code is pretty short and readable. There is also a collection of
specifications and design materials in the [doc](doc) directory.
To contribute code and reviews visit https://github.com/dyne/Tomb
If you plan to commit code into Tomb, please keep in mind this is a
minimalist tool and its code should be readable. Guidelines on the
coding style are illustrated in [doc/HACKING.txt](doc/HACKING.txt).
Tomb's developers can be contacted using the issues on GitHub or over
IRC on https://irc.dyne.org channel **#dyne** (or direct port 9999 SSL)
There is also a [space for discussion](https://github.com/dyne/Tomb/discussions) of new features, desiderata and whatnot on github.
# Licensing
Tomb is Copyright (C) 2007-2020 by the Dyne.org Foundation and
maintained by Denis Roio <J@dyne.org>. More information on all
the developers involved is found in the [AUTHORS](AUTHORS.md) file.
Tomb is Copyright (C) 2007-2024 by the Dyne.org Foundation and maintained by [Jaromil](https://github.com/jaromil). The [AUTHORS](AUTHORS.md) file contains more information on all the developers involved. The license is GNU Public License v3.
This source code is free software; you can redistribute it and/or
modify it under the terms of the GNU Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
## [More info on dyne.org/software/tomb](https://dyne.org/software/tomb)
This source code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Please refer
to the GNU Public License for more details.
You should have received a copy of the GNU Public License along with
this source code; if not, write to: Free Software Foundation, Inc.,
675 Mass Ave, Cambridge, MA 02139, USA.

View File

@ -1,13 +1,11 @@
Overview
=========
What's a key?
It basicly is a gpg simmetrically encrypted, ascii-armored file.
It's encryption key is a function (see below, on KDF section) of your tomb
What is a key?
It is a gpg symmetrically encrypted, ascii-armored file.
The encryption key is a function (see below, on KDF section) of your tomb
passphrase.
Layout
======

View File

@ -31,11 +31,11 @@ Linux hard disk encryption settings
and LRW for standardisation. EME along with it's cousin CMC seems to
provide the best security level, but imposes additional encryption
steps. Plumb-IV is discussed only for reference, because it has the
same performance penalty as CMC, but in constrast suffers from
same performance penalty as CMC, but in contrast suffers from
weaknesses of CBC encryption.
As convention, this document will use the term "blocks", when it
referes to a single block of plain or cipher text (usually 16 byte),
refers to a single block of plain or cipher text (usually 16 byte),
and will use the term "sectors", when it refers to a 512-byte wide hard
disk block.
@ -171,8 +171,8 @@ Content leaks
cipher blocks. But how does this number grow in n? Obviously
exponentially. Plotting a few a decimal powers shows that the chance
for finding at least on identical cipher pair flips to 1 around n =
10^20 (n = 10^40 for a 256-bit cipher). This inflexion point is reached
for a 146 million TB storage (or a hundered thousand trillion trillions
10^20 (n = 10^40 for a 256-bit cipher). This inflection point is reached
for a 146 million TB storage (or a hundred thousand trillion trillions
TB storage for a 256-bit cipher).
^1The blocks with available preceding cipher blocks is 62/1KB for all

View File

@ -1,4 +1,4 @@
.TH tomb 1 "May 22, 2019" "tomb"
.TH tomb 1 "Jun 25, 2023" "tomb"
.SH NAME
Tomb \- the Crypto Undertaker
@ -32,23 +32,24 @@ Generates a file that can be used as a tomb and will occupy as much
space as its desired initial size, the unlocked \fI.tomb\fR file can
then be locked using a \fIkey\fR. It takes a mandatory \fI-s\fR option
which is the size in megabytes (MiB). Tombs are digged using random
data gathered from a non-blocking source (/dev/urandom).
data gathered from a non-blocking source (/dev/urandom). For very
large tombs this may take up too much time and entropy, then it is
possible to use \fIfallocate(1)\fR being aware it does not pre-fill
with random data, decreasing the tomb's security.
.B
.IP "forge"
Creates a new \fIkey\fR and prompts the user for a \fIpassword\fR to
protect its usage using symmetric encryption. This operation uses
random data from a non-blocking source (/dev/urandom) and it may take
long only in some cases; to switch using a blocking source the
\fI--use-random\fR flag can be used. The \fI-g\fR option switches on
the use of a GPG key instead of a password (asymmetric encryption),
then the \fI-r\fR option indicates the recipient key; more recipient
GPG ids can be indicated (comma separated). The default cipher to
protect the key is AES256, a custom one can be specified using the
\fI-o\fR option, for a list of supported ciphers use \fI-v\fR. For
additional protection against dictionary attacks on keys, the
\fI--kdf\fR option can be used when forging a key, making sure that
the \fItomb-kdb-pbkdf2\fR binaries in \fIextras/kdf\fR were compiled
Creates a new \fIkey\fR and prompts the user for a \fIpassword\fR to protect
its usage using symmetric encryption. This operation uses random data from a
non-blocking source (/dev/urandom) and it may take long only in some cases; to
switch using a blocking source the \fI--use-random\fR flag can be used. The
\fI-g\fR option switches on the use of a GPG key instead of a password
(asymmetric encryption), then the \fI-r\fR option indicates the recipient key;
more recipient GPG ids can be indicated (comma separated). The default cipher
to protect the key is AES256, a custom one can be specified using the \fI-o\fR
option, for a list of supported ciphers use \fI-v\fR. For additional protection
against dictionary attacks on keys, the \fI--kdf\fR option can be used when
forging a key, making sure that the binaries in \fIextras/kdf\fR were compiled
and installed on the system.
.B
@ -61,12 +62,32 @@ option \fI-k\fR should be used to specify a key file; in case of
encryption to GPG recipients the \fI-g\fR flag should be used followed
by \fI-r\fR and the recipient's secret GPG key id. The \fI-o\fR
option can be used to specify the cipher specification: default is
"aes-xts-plain64", old versions of Tomb used
"aes-cbc-essiv:sha256". If you are looking for something exotic, also
try "serpent-xts-plain64". More options may be found in cryptsetup(8)
and Linux documentation. This operation requires root privileges to
loopback mount, format the tomb (using LUKS and Ext4), then set the
key in its first LUKS slot.
"aes-xts-plain64", old versions of Tomb used "aes-cbc-essiv:sha256".
If you are looking for something exotic, also try
"serpent-xts-plain64". More options may be found in cryptsetup(8) and
Linux documentation. The \fI--filesystem\fR option can be used to
specify an alternative filesystem used to format the tomb,
in place of the default "ext4". This operation requires root
privileges to loopback mount, format the tomb (using LUKS and mkfs),
then set the key in its first LUKS slot.
.RS
Supported filesystems for \fI--filesystem\fR:
.PD 0
.IP "ext3" 15
using operating system defaults
.IP "ext4"
using operating system defaults
.IP "btrfs"
for tombs >= 47MB using operating system defaults
.IP "btrfsmixedmode"
for tombs >=18MB btrfs mixed mode (see mkfs.btrfs(8))
.IP "ext3maxinodes"
ext3 with a maximum of inodes (for many small files)
.IP "ext4maxinodes"
ext4 with a maximum of inodes (for many small files)
.PD
.RE
.B
.IP "open"
@ -101,7 +122,7 @@ internally to enumerate processes running in one or all tombs.
.IP "index"
Creates or updates the search indexes of all tombs currently open:
enables use of the \fIsearch\fR command using simple word patterns on
file names. Indexes are created using mlocate's updatedb(8) and
file names. Indexes are created using mlocate/plocate's updatedb(8) and
swish-e(1) if they are found on the system. Indexes allow one to search
very fast for filenames and contents inside a tomb, they are stored
inside it and are not accessible if the Tomb is closed. To avoid
@ -111,7 +132,7 @@ indexing a specific tomb simply touch a \fI.noindex\fR file in it.
.IP "search"
Takes any string as argument and searches for them through all tombs
currently open and previously indexed using the \fIindex\fR command.
The search matches filenames if mlocate is installed and then also
The search matches filenames if mlocate/plocate is installed and then also
file contents if swish++ is present on the system, results are listed
on the console.
@ -255,7 +276,8 @@ the \fIsize\fR of the new file to be created. Units are megabytes (MiB).
.B
.IP "-g"
Tell tomb to use a asymmetric GnuPG key encryption instead of a
symmetric passphrase to protect a tomb key. This option can be followed by \fI-r\fR when the command needs to specify recipient(s).
symmetric passphrase to protect a tomb key. This option can be
followed by \fI-r\fR when the command needs to specify recipient(s).
.B
.IP "-r \fI<gpg_id>[,<gpg_id2>]\fR"
Provide a new set of recipient(s) to encrypt a tomb key. \fIgpg_ids\fR
@ -263,17 +285,34 @@ can be one or more GPG key ID, comma separated. All GPG keys must be
trusted keys in GPG.
.B
.IP "--kdf \fI<itertime>\fR"
Activate the KDF feature against dictionary attacks when creating a
key: forces a delay of \fI<itertime>\fR times every time this key is
used. The actual time to wait depends on the CPU speed of the
computer where the key is used. Using 5 or 10 is a sane amount for
modern computers, the value is multiplied by 1 million.
Activate the KDF feature against dictionary attacks when creating a key: forces
a delay of \fI<itertime>\fR times every time this key is used. The actual time
to wait depends on the CPU speed (default) or the RAM size (argon2) of the
computer where the key is used. Using 5 or 10 is a sane amount for modern
computers, the value is multiplied by 1 million.
.B
.IP "--kdftype \fIargon2 | pbkdf2\fR"
Adopt the \fIargon2\fR algorithm for KDF, stressing the RAM capacity rather
than the CPU speed of the computer decrypting the tomb. Requires the
\fIargon2\fR binary by P-H-C to be installed, as packaged by most distros.
Default is \fIpbkdf2\fR.
.B
.IP "--kdfmem \fI<memory>\fR"
In case of \fIargon2\fR KDF algorithm, this value specifies the size of RAM
used: it consists of a number which is the elevated power of two in kilobytes.
Default is 18 which is 250 MiB (2^18 = 262,144 kilobytes).
.B
.IP "--sudo \fI<executable>\fR"
Select a different tool than sudo for privilege escalation.
Alternatives supported so far are: pkexec, doas, sup, sud. For any
alternative to work the executable must be included in the current
PATH.
.B
.IP "--sphx-user \fI<username>\fR"
Activate the SPHINX feature for password-authenticated key agreement.
This option indicates the \fI<username>\fR used to retrieve the
password from a sphinx oracle key reachable via TCP/IP.
.B
.IP "--sphx-host \fI<domain>\fR"
Activate the SPHINX feature for password-authenticated key agreement.
This option indicates the \fI<domain>\fR used to retrieve the password
@ -336,7 +375,7 @@ base root of the tomb.
.IP "bind-hooks"
This hook file consists of a simple text file named \fIbind-hooks\fR
containing a two column list of paths to files or directories inside
the tomb. The files and directories will be be made directly
the tomb. The files and directories will be made directly
accessible by the tomb \fIopen\fR command inside the current user's
home directory. Tomb uses internally the "mount \-o bind" command to
bind locations inside the tomb to locations found in $HOME. In the
@ -344,10 +383,10 @@ first column are indicated paths relative to the tomb and in the
second column are indicated paths relative to $HOME contents, for
example:
.EX
mail mail
.gnupg .gnupg
.fmrc .fetchmailrc
.mozilla .mozilla
mail mail
.gnupg .gnupg
.fmrc .fetchmailrc
.mozilla .mozilla
.EE
.B
@ -363,9 +402,9 @@ command.
.SH PRIVILEGE ESCALATION
The tomb commandline tool needs to acquire super user rights to
execute most of its operations: to do so it uses sudo(8), while
pinentry(1) is adopted to collect passwords from the user. Tomb
executes as super user only when required.
execute most of its operations: so it uses sudo(8) or other configured
tools, while pinentry(1) is adopted to collect passwords from the
user. Tomb executes as super user only when required.
To be made available on multi user systems, the superuser execution of
the tomb script can be authorized for users without jeopardizing the
@ -378,8 +417,8 @@ whole system's security: just add such a line to \fI/etc/sudoers\fR:
To avoid that tomb execution is logged by \fIsyslog\fR also add:
.EX
Cmnd_Alias TOMB = /usr/local/bin/tomb
Defaults!TOMB !syslog
Cmnd_Alias TOMB = /usr/local/bin/tomb
Defaults!TOMB !syslog
.EE
.SH PASSWORD INPUT
@ -430,16 +469,21 @@ prefix all invocations of tomb with a blank space, including two lines
in ".zshrc":
.EX
export HISTIGNORESPACE=1
alias tomb=' tomb'
export HISTIGNORESPACE=1
alias tomb=' tomb'
.EE
.SH PASSWORD INPUT
Tomb uses the external program "pinentry" to let users type the key password into a terminal or a graphical window. This program works in conjunction with "gpg-agent", a daemon running in background to facilitate secret key management with gpg. It is recommended one runs "gpg-agent" launching it from the X session initialization ("~/.xsession" or "~/.xinitrc" files) with this command:
Tomb uses the external program "pinentry" to let users type the key password
into a terminal or a graphical window. This program works in conjunction with
"gpg-agent", a daemon running in background to facilitate secret key
management with gpg. It is recommended one runs "gpg-agent" launching it from
the X session initialization ("~/.xsession" or "~/.xinitrc" files) with this
command:
.EX
eval $(gpg-agent --daemon --write-env-file "${HOME}/.gpg-agent-info")
eval $(gpg-agent --daemon --write-env-file "${HOME}/.gpg-agent-info")
.EE
In the future it may become mandatory to run gpg-agent when using tomb.
@ -461,7 +505,7 @@ commands: \fIopen\fR, \fIforge\fR \fIsetkey\fR, \fIpasswd\fR,
Using the package libsphinx
.UR https://github.com/stef/libsphinx
.UE
and its python client/daemon implementation pwdsphinx
and its python client/daemon implementation pwdsphinx
.UR https://github.com/stef/pwdsphinx
.UE
is possible to store and retrieve safely the password that locks the
@ -526,11 +570,11 @@ keeping all its profile data inside it:
cat <<EOF > /media/FOX.tomb/exec-hooks
#!/bin/sh
if [ "$1" = "open" ]; then
firefox -no-remote -profile "$2"/firefox-pro &
firefox -no-remote -profile "$2"/firefox-pro &
fi
EOF
chmod +x /media/FOX.tomb/exec-hooks
mkdir /media/FOX.tomb/firefox-pro
chmod +x /media/FOX.tomb/exec-hooks
mkdir /media/FOX.tomb/firefox-pro
.EE
.IP \(bu
@ -541,13 +585,13 @@ Script a tomb to archive Pictures using Shotwell, launching it on open:
cat <<EOF > /media/Pictures.tomb/bind-hooks
Pictures Pictures
EOF
cat <<EOF > /media/Pictures.tomb/exec-hooks
cat <<EOF > /media/Pictures.tomb/exec-hooks
#!/bin/sh
if [ "$1" = "open" ]; then
which shotwell > /dev/null
if [ "$?" = "0" ]; then
shotwell -d "$2"/Pictures/.shotwell &
fi
which shotwell > /dev/null
if [ "$?" = "0" ]; then
shotwell -d "$2"/Pictures/.shotwell &
fi
fi
EOF
chmod +x /media/Pictures.tomb/exec-hooks
@ -563,7 +607,7 @@ channel on \fIhttps://irc.dyne.org\fR.
.SH COPYING
This manual is Copyright (c) 2011-2019 by Denis Roio <\fIjaromil@dyne.org\fR>
This manual is Copyright (c) 2011-2021 by Denis Roio <\fIjaromil@dyne.org\fR>
This manual includes contributions by Boyska and Hellekin O. Wolf.

Binary file not shown.

View File

@ -1647,7 +1647,7 @@ change_tomb_key() {
_sudo cryptsetup luksClose "${mapper}" || _failure "Unexpected error in luksClose."
_success "Succesfully changed key for tomb: ::1 tomb file::" $TOMBFILE
_success "Successfully changed key for tomb: ::1 tomb file::" $TOMBFILE
_message "The new key is: ::1 new key::" $TOMBKEYFILE
return 0

View File

@ -1,31 +1,33 @@
##
# gregtzar/tomb
#
# This creates an Ubuntu derived base image and installs the tomb libarary
# This creates an Ubuntu derived base image and installs the tomb library
# along with it's dependencies.
FROM ubuntu:bionic
FROM dyne/devuan:chimaera
ARG DEBIAN_FRONTEND=noninteractive
ARG TOMB_VERSION=2.5
ARG TOMB_VERSION=2.9
# Install dependencies
RUN apt-get update -y && \
apt-get install -y \
apt-get install -y -q --no-install-recommends\
make \
sudo \
curl \
rsync \
build-essential \
gettext \
zsh \
gnupg \
cryptsetup \
pinentry-curses \
steghide
file xxd \
steghide \
mlocate \
swish-e
# Build and install Tomb from remote repo
RUN curl https://files.dyne.org/tomb/Tomb-$TOMB_VERSION.tar.gz -o /tmp/Tomb-$TOMB_VERSION.tar.gz && \
RUN curl https://files.dyne.org/tomb/releases/Tomb-$TOMB_VERSION.tar.gz -o /tmp/Tomb-$TOMB_VERSION.tar.gz && \
cd /tmp && \
tar -zxvf /tmp/Tomb-$TOMB_VERSION.tar.gz && \
cd /tmp/Tomb-$TOMB_VERSION && \
make install
make install

View File

@ -18,15 +18,18 @@
# You should have received a copy of the GNU General Public License
# along with this source code. If not, see <http://www.gnu.org/licenses/>.
ver="0.8"
TOMBPATH="/usr/local/bin/tomb" # Set this to your tomb executable's path
KDFPATH="/usr/local/bin/" # Set this to the path of your KDF binaries (if you're using them)
# {{{ SETTINGS
ver="0.9.1"
KDFPATH="/usr/libexec/tomb" # Path of your KDF binaries (if you're using them).
SWAPOFF="false" # Set to "true" to swapoff, or "false" to use -f (force) flag.
# The ones below should not need changing
TOMBPATH="$(which tomb)" # Tomb executable's path
HEXENC="$KDFPATH/tomb-kdb-hexencode"
GENSALT="$KDFPATH/tomb-kdb-pbkdf2-gensalt"
GETITER="$KDFPATH/tomb-kdb-pbkdf2-getiter"
PBKDF="$KDFPATH/tomb-kdb-pbkdf2"
_DD=/bin/dd
_DD="$(which dd)"
# }}}
# {{{ monmort icon
MONMORT="/tmp/monmort.png"
@ -41,337 +44,165 @@ b2RpZnkAMjAxMS0wMS0xMlQwOTozNDoyNCswMTowMNKiZVMAAAAASUVORK5CYII="
echo -e "$ICONB64" | base64 --decode > $MONMORT
# }}}
# {{{ sudo functions
function _sudo {
sudoassword=$(ask_password "Insert sudo password for user $USER")
echo -e "$sudoassword\n" | sudo -S -v
_sudowrong
}
function _sudowrong {
[[ $? = 0 ]] || {
sudoassword=$(ask_password "Wrong password. Insert sudo password for user $USER")
echo -e "$sudoassword\n" | sudo -S -v
_sudowrong
}
}
# }}}
# {{{ Zenity dialogs
Icon="--window-icon="$MONMORT""
function _zenques {
zenity \
--window-icon="$MONMORT" \
--question \
--text="$1"
zenity \
$Icon \
--question \
--text="$1"
}
function _fsel {
zenity \
--window-icon="$MONMORT" \
--file-selection \
--title="$1"
zenity \
$Icon \
--file-selection \
--title="$1"
}
function _fsave {
zenity \
--window-icon="$MONMORT" \
--file-selection \
--save \
--title="$1" \
--filename="$2"
zenity \
$Icon \
--file-selection \
--save \
--title="$1" \
--filename="$2"
}
function _zenwarn {
zenity \
--window-icon="$MONMORT" \
--warning \
--title="$1" \
--text="$2"
zenity \
$Icon \
--warning \
--title="Warning" \
--text="$1"
}
function _info {
which notify-send > /dev/null
if [[ $? == "0" ]]; then
_zenotif $*
else
_zeninfo $*
fi
which notify-send > /dev/null
if [[ $? == "0" ]]; then
_zenotif $*
else
_zeninfo $*
fi
}
function _zenotif {
zenity \
--window-icon="$MONMORT" \
--notification \
--title="$1" \
--text="$2"
zenity \
$Icon \
--notification \
--title="$1" \
--text="$2"
}
function _zeninfo {
zenity \
--window-icon="$MONMORT" \
--info \
--title="$1" \
--text="$2"
zenity \
$Icon \
--info \
--title="$1" \
--text="$2"
}
function _zenerr {
zenity \
--window-icon="$MONMORT" \
--error \
--title="$1" \
--text="$2"
zenity \
$Icon \
--error \
--title="$1" \
--text="$2"
}
function _zenprog {
zenity \
--window-icon="$MONMORT" \
--progress \
--auto-close \
--pulsate \
--title="$1" \
--text="$2"
zenity \
$Icon \
--progress \
--auto-close \
--pulsate \
--title="$1" \
--text="$2"
}
function _zenprognc {
zenity \
--window-icon="$MONMORT" \
--progress \
--auto-close \
--no-cancel \
--pulsate \
--title="$1" \
--text="$2"
zenity \
$Icon \
--progress \
--auto-close \
--no-cancel \
--pulsate \
--title="$1" \
--text="$2"
}
function _zenentry {
zenity \
--window-icon="$MONMORT" \
--entry \
--title="$1" \
--text="$2" \
--entry-text="$3"
}
# }}}
# {{{ Some pinentry code shamelessly stolen from tomb
# Ask user for a password
# Wraps around the pinentry command, from the GnuPG project, as it
# provides better security and conveniently use the right toolkit.
ask_password() {
local description="$1"
local title="${2:-Enter tomb password.}"
local output
local password
local gtkrc
local theme
# Distributions have broken wrappers for pinentry: they do
# implement fallback, but they disrupt the output somehow. We are
# better off relying on less intermediaries, so we implement our
# own fallback mechanisms. Pinentry supported: curses, gtk-2, qt4
# and x11.
# make sure LANG is set, default to C
LANG=${LANG:-C}
_verbose "asking password with tty=$TTY lc-ctype=$LANG"
if [[ "$DISPLAY" = "" ]]; then
if _is_found "pinentry-curses"; then
_verbose "using pinentry-curses"
output=`cat <<EOF | pinentry-curses
OPTION ttyname=$TTY
OPTION lc-ctype=$LANG
SETTITLE $title
SETDESC $description
SETPROMPT Password:
GETPIN
EOF`
else
_failure "Cannot find pinentry-curses and no DISPLAY detected."
fi
else # a DISPLAY is found to be active
# customized gtk2 dialog with a skull (if extras are installed)
if _is_found "pinentry-gtk-2"; then
_verbose "using pinentry-gtk2"
gtkrc=""
theme=/share/themes/tomb/gtk-2.0-key/gtkrc
for i in /usr/local /usr; do
[[ -r $i/$theme ]] && {
gtkrc="$i/$theme"
break
}
done
[[ "$gtkrc" = "" ]] || {
gtkrc_old="$GTK2_RC_FILES"
export GTK2_RC_FILES="$gtkrc"
}
output=`cat <<EOF | pinentry-gtk-2
OPTION ttyname=$TTY
OPTION lc-ctype=$LANG
SETTITLE $title
SETDESC $description
SETPROMPT Password:
GETPIN
EOF`
[[ "$gtkrc" = "" ]] || export GTK2_RC_FILES="$gtkrc_old"
# TODO QT4 customization of dialog
elif _is_found "pinentry-qt4"; then
_verbose "using pinentry-qt4"
output=`cat <<EOF | pinentry-qt4
OPTION ttyname=$TTY
OPTION lc-ctype=$LANG
SETTITLE $title
SETDESC $description
SETPROMPT Password:
GETPIN
EOF`
# TODO X11 customization of dialog
elif _is_found "pinentry-x11"; then
_verbose "using pinentry-x11"
output=`cat <<EOF | pinentry-x11
OPTION ttyname=$TTY
OPTION lc-ctype=$LANG
SETTITLE $title
SETDESC $description
SETPROMPT Password:
GETPIN
EOF`
else
if _is_found "pinentry-curses"; then
_verbose "using pinentry-curses"
_warning "Detected DISPLAY, but only pinentry-curses is found."
output=`cat <<EOF | pinentry-curses
OPTION ttyname=$TTY
OPTION lc-ctype=$LANG
SETTITLE $title
SETDESC $description
SETPROMPT Password:
GETPIN
EOF`
else
_failure "Cannot find any pinentry: impossible to ask for password."
fi
fi
fi # end of DISPLAY block
# parse the pinentry output
for i in ${(f)output}; do
[[ "$i" =~ "^ERR.*" ]] && {
_warning "Pinentry error: ::1 error::" ${i[(w)3]}
print "canceled"
return 1 }
# here the password is found
[[ "$i" =~ "^D .*" ]] && password="${i##D }"
done
[[ "$password" = "" ]] && {
_warning "Empty password"
print "empty"
return 1 }
print "$password"
return 0
}
_is_found() {
# returns 0 if binary is found in path
[[ "$1" = "" ]] && return 1
command -v "$1" 1>/dev/null 2>/dev/null
return $?
}
function _warning no() {
option_is_set -q || _msg warning $@
return 1
}
function _verbose xxx() {
option_is_set -D && _msg verbose $@
return 0
}
function _failure die() {
typeset -i exitcode=${exitv:-1}
option_is_set -q || _msg failure $@
# be sure we forget the secrets we were told
exit $exitcode
zenity \
$Icon \
--entry \
--title="$1" \
--text="$2" \
--entry-text="$3"
}
# }}}
# {{{ _clean - Clean function, removes sensitive stuff from memory
function _clean {
unset $?
local rr="$RANDOM"
while [[ ${#rr} -lt 500 ]]; do
rr+="$RANDOM"
done
unset $?
local rr="$RANDOM"
while [[ ${#rr} -lt 500 ]]; do
rr+="$RANDOM"
done
cmnd="$rr"; unset cmnd
tombname="$rr"; unset tombname
tombsize="$rr"; unset tombsize
keyfile="$rr"; unset keyfile
sudoassword="$rr"; unset sudoassword
tombtmp="/tmp/tombtmp"
if [ -f $tombtmp ]; then
dd if=/dev/urandom of=$tombtmp bs=800 count=1
rm -f $tombtmp
fi
tombtmp="$rr"; unset tombtmp
newkey="$rr"; unset newkey
jpegfile="$rr"; unset jpegfile
cmnd="$rr"; unset cmnd
tombname="$rr"; unset tombname
tombsize="$rr"; unset tombsize
keyfile="$rr"; unset keyfile
tombtmp="/tmp/tombtmp"
if [ -f $tombtmp ]; then
dd if=/dev/urandom of=$tombtmp bs=800 count=1
rm -f $tombtmp
fi
tombtmp="$rr"; unset tombtmp
newkey="$rr"; unset newkey
jpegfile="$rr"; unset jpegfile
}
# }}}
# {{{ _main - Main window
function _main {
_clean
cmnd=`zenity \
--window-icon="$MONMORT" \
--title="gtomb" \
--width=640 \
--height=420 \
--list \
--hide-header \
--text="gtomb v$ver\nThe GUI wrapper for Tomb, the crypto undertaker." \
--separator=" & " \
--column=Function \
--column=Description \
"dig" "Dig a new tomb of chosen size" \
"forge" "Forge a new key used to lock tombs" \
"lock" "Lock a non-locked tomb using an existing key" \
"open" "Open an existing tomb" \
"index" "Index the contents of all tombs." \
"search" "Search the content of indexed tombs." \
"list" "List all open tombs and information on them" \
"close" "Close a specific tomb (or all)" \
"slam" "Slam a tomb (or all) killing all programs using it" \
"resize" "Resize a tomb to a new size (can only grow)" \
"passwd" "Change the passphrase of a key" \
"setkey" "Change the key of an existing tomb" \
"engrave" "Generates a QR code of a key to be saved on paper" \
"bury" "Hide a key inside a JPEG image" \
"exhume" "Extract a key from a JPEG image"`
eval "_$cmnd"
_clean
cmnd=`zenity \
$Icon \
--title="gtomb" \
--width=400 \
--height=445 \
--list \
--hide-header \
--text="gtomb v$ver\nThe GUI wrapper for Tomb, the crypto undertaker." \
--separator=" & " \
--column=Function \
--column=Description \
"dig" "Dig a new tomb of chosen size" \
"forge" "Forge a new key used to lock tombs" \
"lock" "Lock a non-locked tomb using an existing key" \
"open" "Open an existing tomb" \
"index" "Index the contents of all tombs." \
"search" "Search the content of indexed tombs." \
"list" "List all open tombs and information on them" \
"close" "Close a specific tomb (or all)" \
"slam" "Slam a tomb (or all) killing all programs using it" \
"resize" "Resize a tomb to a new size (can only grow)" \
"passwd" "Change the passphrase of a key" \
"setkey" "Change the key of an existing tomb" \
"engrave" "Generates a QR code of a key to be saved on paper" \
"bury" "Hide a key inside a JPEG image" \
"exhume" "Extract a key from a JPEG image"`
if [[ "$?" = 1 && $SWAPOFF = "true" ]]; then
zenity --password --title="sudo swapon -a" | sudo swapon -a
unset $?
fi
eval "_$cmnd"
}
# }}}
# {{{ dig - Dig a new tomb
function _dig {
tombname=`_fsave "Choose where to dig your tomb" "secret.tomb"`
res=$?
tombname=`_fsave "Choose where to dig your tomb" "secret.tomb"`
res=$?
if [[ -f "$tombname" ]]; then
_zenerr "Error" "This tomb already exists. I am not digging here."
exec _main
elif [[ -z "$tombname" ]]; then
_info "gtomb" "Cancelled"
exec _main
fi
if [[ -f "$tombname" ]]; then
_zenerr "Error" "This tomb already exists. I am not digging here."
exec _main
elif [[ -z "$tombname" ]]; then
_info "gtomb" "Cancelled"
exec _main
fi
[[ $res = 0 ]] || exec _main
@ -387,7 +218,7 @@ function _dig {
exec _main
fi
[[ $res = 0 ]] || { _zenwarn "Warning" "Tomb digging canceled." ; exec _main }
[[ $res = 0 ]] || { _zenwarn "Tomb digging cancelled." ; exec _main }
"$TOMBPATH" dig -s "$tombsize" "$tombname" | \
_zenprog "Digging new tomb" "Please wait while your tomb is being dug..." &
@ -401,10 +232,12 @@ function _dig {
[[ -n "$PID_DD" && -z "$PID_ZEN" ]] && {
kill -9 $PID_DD
_zenwarn "Warning" "Tomb digging cancelled."
_zenwarn "Tomb digging cancelled."
rm -f "$tombname"
exec _main
}
wait
_info "Success" "Your tomb has been dug in $tombname"
exec _main
@ -413,42 +246,42 @@ function _dig {
# {{{ forge - Forge a new key
function _forge {
keyfile=`_fsave "Choose where to forge your key" "secret.tomb.key"`
res=$?
keyfile=`_fsave "Choose where to forge your key" "secret.tomb.key"`
res=$?
if [[ -f $keyfile ]]; then
_zenerr "Error" "This key already exists. I am not overwriting."
exec _main
elif [[ -z $keyfile ]]; then
_info "gtomb" "Canceled"
exec _main
fi
if [[ -f $keyfile ]]; then
_zenerr "Error" "This key already exists. I am not overwriting."
exec _main
elif [[ -z $keyfile ]]; then
_info "gtomb" "Cancelled"
exec _main
fi
kdf=""
kdfiter=""
if [[ -x $HEXENC ]] && [[ -x $GENSALT ]] && [[ -x $GETITER ]] && [[ -x $PBKDF ]]; then
_zenques "Do you want to use KDF? (Generates passwords armored against dictionary attacks)"
[[ $? == "0" ]] && {
kdf="--kdf"
kdfiter=`_zenentry "Iterations" "Enter the delay (itertime) in seconds for each time \n\
kdf=""
kdfiter=""
if [[ -x $GENSALT ]] && [[ -x $GETITER ]] && [[ -x $PBKDF ]]; then
_zenques "Do you want to use KDF? (Generates passwords armored against dictionary attacks)"
if [[ $? == "0" ]]; then
kdf="--kdf"
kdfiter=`_zenentry "Iterations" "Enter the delay (itertime) in seconds for each time \n\
this key is used:" "2"`
re='^[0-9]+$'
if ! [[ $kdfiter =~ $re ]]; then
_zenerr "Error" "Please choose a valid number."
exec _main
elif [[ -z $kdfiter ]]; then
_info "gtomb" "Canceled"
exec _main
fi
}
else
_zenotif "gtomb" "KDF binaries not found."
fi
re='^[0-9]+$'
if ! [[ $kdfiter =~ $re ]]; then
_zenerr "Error" "Please choose a valid number."
exec _main
elif [[ -z $kdfiter ]]; then
_info "gtomb" "Cancelled"
exec _main
fi
fi
else
_zenotif "gtomb" "KDF binaries not found."
fi
[[ $? = 0 ]] || exec _main
"$TOMBPATH" forge "$keyfile" "$kdf" "$kdfiter" | \
"$TOMBPATH" forge "$keyfile" "$kdf" "$kdfiter" "$FLAG" | \
_zenprog "Forging key" "Please wait while your key is being forged...\n\
You can move your mouse around and use your computer to speed up the process." &
@ -460,10 +293,12 @@ You can move your mouse around and use your computer to speed up the process." &
done
[[ -n "$PID_DD" && -z "$PID_ZEN" ]] && {
kill -9 $PID_DD
_zenwarn "Warning" "Forging cancelled."
_zenwarn "Forging cancelled."
rm -f $keyfile
exec _main
}
wait
_info "Success" "Your key is now forged in $keyfile"
exec _main
@ -472,17 +307,19 @@ You can move your mouse around and use your computer to speed up the process." &
# {{{ lock - Lock a non-locked tomb
function _lock {
tombname=`_fsel "Select a tomb to lock"`
[[ -n $tombname ]] || { _zenotif "gtomb" "Cancelled" ; exec _main }
tombname=`_fsel "Select a tomb to lock"`
[[ -n $tombname ]] || { _zenotif "gtomb" "Cancelled" ; exec _main }
[[ $? = 0 ]] || exec _main
keyfile=`_fsel "Choose the key for your tomb"`
[[ -n $keyfile ]] || { _zenotif "gtomb" "Cancelled" ; exec _main }
[[ $? = 0 ]] || exec _main
_sudo
"$TOMBPATH" lock "$tombname" -k "$keyfile" | \
_zenprognc "Locking your tomb" "Please wait while your tomb is being locked..."
wait
_info "Success" "Your tomb is now locked."
exec _main
}
@ -490,14 +327,16 @@ function _lock {
# {{{ open - Open an existing tomb
function _open {
tombname=`_fsel "Choose a tomb to open"`
tombname=`_fsel "Choose a tomb to open"`
[[ $? = 0 ]] || exec _main
keyfile=`_fsel "Choose the key for your tomb"`
[[ $? = 0 ]] || exec _main
_sudo
"$TOMBPATH" open "$tombname" -k "$keyfile"
"$TOMBPATH" open "$tombname" -k "$keyfile" "$FLAG"
wait
_info "Success" "Your tomb is now open."
exec _main
}
@ -505,33 +344,33 @@ function _open {
# {{{ list - list all open tombs, along with their mountpoint
function _list {
tombtmp="/tmp/tombtmp"
"$TOMBPATH" list --get-mountpoint > $tombtmp
tombname=`cat $tombtmp | \
sed 's/.*\/\([^\/]*\)$/\1\n &/' | \
zenity \
--title="Currently open tombs" \
--window-icon="$MONMORT" \
--width=640 --height=380 --list \
--separator=" & " \
--text="Here are your open tombs" \
--column=Tomb \
--column=Path `
tombtmp="/tmp/tombtmp"
"$TOMBPATH" list --get-mountpoint > $tombtmp
tombname=`cat $tombtmp | \
sed 's/.*\/\([^\/]*\)$/\1\n &/' | \
zenity \
--title="Currently open tombs" \
$Icon \
--width=400 --height=380 --list \
--separator=" & " \
--text="Here are your open tombs" \
--column="Tomb" \
--column="Path" `
tombname=`echo "$tombname" | cut -c1-16`
tombname=`echo "$tombname" | cut -c1-16`
[[ $? = 0 ]] || exec _main
listchoice=`zenity \
--title="Choose action" \
--window-icon="$MONMORT" \
--width=640 --height=400 --list \
$Icon \
--width=400 --height=380 --list \
--separator=" & " \
--text="What do you want to do with this tomb?" \
--column=Command \
--column=Description \
--column="Command" \
--column="Description" \
"disindex" "Disable indexing of this tomb." \
"enindex" "Enable indexing of this tomb." \
"enindex" "Enable indexing of this tomb." \
"close" "Close the selected tomb." \
"slam" "Slam the selected tomb." \
"binds" "Edit current bind-hooks." \
@ -541,13 +380,11 @@ function _list {
case $listchoice in
close)
_sudo
"$TOMBPATH" close "$tombname"
_zeninfo "Success" "Tomb closed successfully!"
exec _main
;;
slam)
_sudo
"$TOMBPATH" slam "$tombname"
_info "Success" "$tombname slammed successfully!"
exec _main
@ -595,22 +432,21 @@ function _list {
# {{{ close - Close open tomb(s)
function _close {
tombtmp="/tmp/tombtmp"
"$TOMBPATH" list --get-mountpoint > $tombtmp
echo "/all" >> $tombtmp
tombname=`cat $tombtmp | \
sed 's/.*\/\([^\/]*\)$/\1\n &/' | \
zenity \
--title="Choose a tomb to close" \
--window-icon="$MONMORT" \
--width=640 --height=380 --list \
--separator=" & " \
--column=Tomb \
--column=Path `
tombtmp="/tmp/tombtmp"
"$TOMBPATH" list --get-mountpoint > $tombtmp
echo "/all" >> $tombtmp
tombname=`cat $tombtmp | \
sed 's/.*\/\([^\/]*\)$/\1\n &/' | \
zenity \
--title="Choose a tomb to close" \
$Icon \
--width=640 --height=380 --list \
--separator=" & " \
--column=Tomb \
--column=Path `
[[ $? = 0 ]] || exec _main
_sudo
tombname=`echo "$tombname" | cut -c1-16`
"$TOMBPATH" close "$tombname"
_info "Success" "Closed successfully!"
@ -620,22 +456,21 @@ function _close {
# {{{ slam - Slam open tombs
function _slam {
tombtmp="/tmp/tombtmp"
"$TOMBPATH" list --get-mountpoint > $tombtmp
echo "/all" >> $tombtmp
tombname=`cat $tombtmp | \
sed 's/.*\/\([^\/]*\)$/\1\n &/' | \
zenity \
--title="Choose a tomb to slam" \
--window-icon="$MONMORT" \
--width=640 --height=380 --list \
--separator=" & " \
--column=Tomb \
--column=Path `
tombtmp="/tmp/tombtmp"
"$TOMBPATH" list --get-mountpoint > $tombtmp
echo "/all" >> $tombtmp
tombname=`cat $tombtmp | \
sed 's/.*\/\([^\/]*\)$/\1\n &/' | \
zenity \
--title="Choose a tomb to slam" \
$Icon \
--width=640 --height=380 --list \
--separator=" & " \
--column=Tomb \
--column=Path `
[[ $? = 0 ]] || exec _main
_sudo
tombname=`echo "$tombname" | cut -c1-16`
"$TOMBPATH" slam "$tombname"
_info "Success" "Slammed successfully!"
@ -645,11 +480,11 @@ function _slam {
# {{{ resize - Resize an existing *closed* tomb
function _resize {
tombname=`_fsel "Choose a tomb to resize"`
res=$?
_zenques "Is your tomb closed?"
tombname=`_fsel "Choose a tomb to resize"`
res=$?
_zenques "Is your tomb closed?"
[[ $? = 0 ]] || { _zenwarn "gtomb" "Please close the tomb before resizing." ; exec _main }
[[ $? = 0 ]] || { _zenwarn "Please close the tomb before resizing." ; exec _main }
[[ $res = 0 ]] || exec _main
@ -670,7 +505,6 @@ function _resize {
keyfile=`_fsel "Choose according keyfile"`
[[ $? = 0 ]] || exec _main
_sudo
"$TOMBPATH" resize "$tombname" -s "$tombsize" -k "$keyfile" | \
_zenprognc "Resizing tomb." "Please wait while your tomb is being resized..."
_info "Success" "Tomb resized successfully!"
@ -680,10 +514,10 @@ function _resize {
# {{{ passwd - Change existing key's passphrase
function _passwd {
keyfile=`_fsel "Choose a keyfile"`
keyfile=`_fsel "Choose a keyfile"`
[[ $? = 0 ]] || exec _main
"$TOMBPATH" passwd -k "$keyfile" | \
"$TOMBPATH" passwd -k "$keyfile" "$FLAG" | \
_zenprognc "Changing passphrase" "Please wait while your key's passphrase is being changed..."
_info "Success" "$keyfile passphrase changed successfully!"
@ -693,7 +527,7 @@ function _passwd {
# {{{ setkey - Change a tomb's keyfile
function _setkey {
tombname=`_fsel "Choose a tomb to change its keyfile"`
tombname=`_fsel "Choose a tomb to change its keyfile"`
[[ $? = 0 ]] || exec _main
keyfile=`_fsel "Chosse your tomb's old keyfile"`
@ -702,19 +536,18 @@ function _setkey {
newkey=`_fsel "Choose your tomb's new keyfile"`
[[ $? = 0 ]] || exec _main
_sudo
"$TOMBPATH" setkey -k "$newkey" "$keyfile" "$tombname" | \
"$TOMBPATH" setkey -k "$newkey" "$keyfile" "$tombname" "$FLAG" | \
_zenprognc "Changing key" "Please wait while your tomb's key is being changed..."
_info "Success" "$tombname keyfile successfully changed! Now using $newkey"
exec _main
exec _main
}
# }}}
# {{{ engrave - generate QR code of a key
function _engrave {
which qrencode || _zenwarn "Warning" "qrencode is not installed. Install it and try again"
keyfile=`_fsel "Choose a keyfile to engrave"`
which qrencode || _zenwarn "qrencode is not installed. Install it and try again."
keyfile=`_fsel "Choose a keyfile to engrave"`
[[ $? = 0 ]] || exec _main
jpegfile=`_fsave "Choose where to save your keyfile (PNG format)"`
@ -730,8 +563,8 @@ function _engrave {
# {{{ bury - hide a keyfile in a JPEG image
function _bury {
which steghide || _zenwarn "Warning" "steghide is not installed. Install it and try again"
keyfile=`_fsel "Choose keyfile"`
which steghide || _zenwarn "steghide is not installed. Install it and try again."
keyfile=`_fsel "Choose keyfile"`
[[ $? = 0 ]] || exec _main
jpegfile=`_fsel "Choose JPEG file"`
@ -745,8 +578,8 @@ function _bury {
# {{{ exhume - extract keyfile from JPEG
function _exhume {
which steghide || _zenwarn "Warning" "steghide is not installed. Install it and try again"
jpegfile=`_fsel "Choose JPEG file"`
which steghide || _zenwarn "steghide is not installed. Install it and try again."
jpegfile=`_fsel "Choose JPEG file"`
[[ $? = 0 ]] || exec _main
keyfile=`_fsave "Choose where to extract your key"`
@ -760,18 +593,18 @@ function _exhume {
# {{{ index - index the contents of open tombs
function _index {
which locate || _zenwarn "Warning" "mlocate is not installed. Install it and try again"
"$TOMBPATH" index | _zenprognc "Indexing" "Please wait while the open tombs are being indexed..."
_info "Success" "Tombs indexed!"
exec _main
which locate || _zenwarn "mlocate is not installed. Install it and try again."
"$TOMBPATH" index | _zenprognc "Indexing" "Please wait while the open tombs are being indexed..."
_info "Success" "Tombs indexed!"
exec _main
}
# }}}
# {{{ search - searches the contents of indexed tombs
function _search {
strings=""
_searchstring
exec _main
strings=""
_searchstring
exec _main
}
function _searchstring {
@ -799,7 +632,16 @@ function _searchstring {
function _ { _clean } # I like cleaning :)
[[ -x $TOMBPATH ]] || {
_zenwarn "Warning" "Tomb binary is not executable or doesn't exist in the current path. Install it or edit the script to point to the correct path."
_zenwarn "Tomb binary is not executable or doesn't exist in the current path. Install it or edit the script to point to the correct path."
exit 1 }
if [[ $SWAPOFF = "true" ]]; then
FLAG=""
zenity --password --title="sudo swapoff -a" | sudo swapoff -a
unset $?
else
FLAG="-f"
fi
_main

View File

@ -5,8 +5,11 @@ cd libsphinx
git submodule update --init --recursive --remote
cd src
sed -i 's|/usr/local|/usr|' makefile
make
sudo make install
ldconfig
pip3 install pwdsphinx
sudo mkdir -p /etc/sphinx
make && make install && ldconfig
cd ../..
git clone https://github.com/stef/pwdsphinx
cd pwdsphinx
python3 setup.py install
mkdir -p /etc/sphinx && cp ../test/sphinx.cfg /etc/sphinx/config && cd /etc/sphinx
openssl req -new -x509 -nodes -out server.crt -keyout server.key -subj '/CN=localhost'
sphinx init

View File

@ -2,10 +2,10 @@
PREFIX ?= /usr/local
all:
$(CC) -O2 -o tomb-kdb-pbkdf2 pbkdf2.c -lgcrypt
$(CC) -O2 -o tomb-kdb-pbkdf2-getiter benchmark.c -lgcrypt
$(CC) -O2 -o tomb-kdb-pbkdf2-gensalt gen_salt.c -lgcrypt
$(CC) -O2 -o tomb-kdb-hexencode hexencode.c
$(CC) -O2 $(CFLAGS) -o tomb-kdb-pbkdf2 pbkdf2.c -lgcrypt
$(CC) -O2 $(CFLAGS) -o tomb-kdb-pbkdf2-getiter benchmark.c -lgcrypt
$(CC) -O2 $(CFLAGS) -o tomb-kdb-pbkdf2-gensalt gen_salt.c -lgcrypt
$(CC) -O2 $(CFLAGS) -o tomb-kdb-hexencode hexencode.c
test:
@echo "Running Tomb-kdb tests"

View File

@ -41,7 +41,7 @@ int main(int argc, char *argv[]) {
} else {
while( (read_bytes=fread(buf, sizeof(char), 2, stdin)) != 0) {
if(read_bytes == 1) buf[1]='\0';
sscanf(buf, "%x", &c);
sscanf(buf, "%s", &c);
printf("%c", c);
}
return 0;

View File

@ -0,0 +1,46 @@
.PHONY: test
system := $(shell uname -s)
# if [ ${system} == "FreeBSD" ]; then sudo kldload fusefs; fi
TOMB ?= test.tomb
#########
## BUILD
#########
test: create-open-close check-random-data
########
## TEST
########
create-open-close:
TOMB=${TOMB} ./test/bats/bin/bats ./test/create_open_close.bats
chmod a+r ${TOMB}
# arg: TOMB='path to tomb containing random.data'
# assumes $TOMB.hash is the pre-calculated hash on creation
check-random-data:
TOMB=${TOMB} ./test/bats/bin/bats test/check-random-data.bats
########
## LINT
########
shellcheck:
shellcheck -s sh tomb -e SC2006,SC2059,SC2034
########
## DEPS
########
download-deps-ubuntu22:
curl https://files.dyne.org/tomb3/third-party/veracrypt-ubuntu22-amd64 -o /usr/local/bin/veracrypt && chmod +x /usr/local/bin/veracrypt
curl https://files.dyne.org/zenroom/nightly/zenroom-linux-amd64 -o /usr/local/bin/zenroom && chmod +x /usr/local/bin/zenroom
download-deps-ubuntu20:
curl https://files.dyne.org/tomb3/third-party/veracrypt-ubuntu20-amd64 -o /usr/local/bin/veracrypt && chmod +x /usr/local/bin/veracrypt
curl https://files.dyne.org/zenroom/nightly/zenroom-linux-amd64 -o /usr/local/bin/zenroom && chmod +x /usr/local/bin/zenroom
download-deps-freebsd13:
curl https://files.dyne.org/tomb3/third-party/veracrypt-freebsd13-amd64 -o /usr/local/bin/veracrypt && chmod +x /usr/local/bin/veracrypt
curl https://files.dyne.org/tomb3/third-party/zenroom-freebsd13-amd64 -o /usr/local/bin/zenroom && chmod +x /usr/local/bin/zenroom
#curl https://files.dyne.org/zenroom/nightly/zenroom-freebsd13-amd64 -o /usr/local/bin/zenroom && chmod +x /usr/local/bin/zenroom

69
extras/portable/README.md Normal file
View File

@ -0,0 +1,69 @@
# Portable Tomb :: the crypto undertaker runs everywhere
[![continuous integration tests badge](https://github.com/dyne/tomb/actions/workflows/portable.yml/badge.svg)](https://github.com/dyne/Tomb/actions) test coverage status for portability
## ⚠️ WORK IN PROGRESS 🛠️
This is the portable version of [Tomb](https://github.com/dyne/tomb)
[![software by Dyne.org](https://files.dyne.org/software_by_dyne.png)](http://www.dyne.org)
# Purpose
Portable tomb achieves direct **interoperable access to tomb volumes** between:
- GNU base Linux (Ubuntu)
- Busybox based Linux (Alpine)
- MS/Windows using WSL2 (Ubuntu)
- FreeBSD (WIP using libluksde)
- ~~Apple/OSX using [MacFUSE](https://osxfuse.github.io/)~~
After some extensive testing during 2022, __adoption of Veracrypt in portable Tomb has been dropped__ because of unreliability and bad performance.
Portable tomb stays as an experimental branch that aims to reduce dependencies and in particular uses only the **POSIX sh interpreter**.
# Status
Portable tomb development is in progress and tracked via issues and the [portable milestone](https://github.com/dyne/Tomb/milestone/9).
## Features
The following features will be implemented where possible:
- mount bind (Linux only)
- ps / slam
- resize (pending investigation)
- index & search ([recoll based](https://github.com/dyne/Tomb/issues/211))
- bury / exhume
## Dependencies
- FreeBSD: `fusefs-libs3 fusefs-lkl e2fsprogs util-linux libluksde`
- Linux: `fuse3 util-linux`
- crossplatform [Veracrypt binaries](https://files.dyne.org/tomb3/third-party) console-only
## Note on Veracrypt
The way upstream developers distribute Veracrypt is far from meeting our minimalist needs, but the console-only binary once installed has a few library dependencies and is all what we need.
I setup [my own build](https://github.com/jaromil/veracrypt) and provide binary builds of Veracrypt v1.25.9 for download on Tomb's file repository for testing purposes.
# Disclaimer
Tomb is Copyright (C) 2007-2023 by the Dyne.org Foundation and
developed by [Jaromil](https://github.com/jaromil).
This source code is free software; you can redistribute it and/or
modify it under the terms of the GNU Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This source code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Please refer
to the GNU Public License for more details.
You should have received a copy of the GNU Public License along with
this source code; if not, write to: Free Software Foundation, Inc.,
675 Mass Ave, Cambridge, MA 02139, USA.

View File

@ -0,0 +1,59 @@
#!/usr/bin/env bash
set -euo pipefail
if command -v greadlink >/dev/null; then
bats_readlinkf() {
greadlink -f "$1"
}
else
bats_readlinkf() {
readlink -f "$1"
}
fi
fallback_to_readlinkf_posix() {
bats_readlinkf() {
[ "${1:-}" ] || return 1
max_symlinks=40
CDPATH='' # to avoid changing to an unexpected directory
target=$1
[ -e "${target%/}" ] || target=${1%"${1##*[!/]}"} # trim trailing slashes
[ -d "${target:-/}" ] && target="$target/"
cd -P . 2>/dev/null || return 1
while [ "$max_symlinks" -ge 0 ] && max_symlinks=$((max_symlinks - 1)); do
if [ ! "$target" = "${target%/*}" ]; then
case $target in
/*) cd -P "${target%/*}/" 2>/dev/null || break ;;
*) cd -P "./${target%/*}" 2>/dev/null || break ;;
esac
target=${target##*/}
fi
if [ ! -L "$target" ]; then
target="${PWD%/}${target:+/}${target}"
printf '%s\n' "${target:-/}"
return 0
fi
# `ls -dl` format: "%s %u %s %s %u %s %s -> %s\n",
# <file mode>, <number of links>, <owner name>, <group name>,
# <size>, <date and time>, <pathname of link>, <contents of link>
# https://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html
link=$(ls -dl -- "$target" 2>/dev/null) || break
target=${link#*" $target -> "}
done
return 1
}
}
if ! BATS_PATH=$(bats_readlinkf "${BASH_SOURCE[0]}" 2>/dev/null); then
fallback_to_readlinkf_posix
BATS_PATH=$(bats_readlinkf "${BASH_SOURCE[0]}")
fi
export BATS_ROOT=${BATS_PATH%/*/*}
export -f bats_readlinkf
exec env BATS_ROOT="$BATS_ROOT" "$BATS_ROOT/libexec/bats-core/bats" "$@"

View File

@ -0,0 +1,98 @@
#!/usr/bin/env bash
bats_prefix_lines_for_tap_output() {
while IFS= read -r line; do
printf '# %s\n' "$line" || break # avoid feedback loop when errors are redirected into BATS_OUT (see #353)
done
if [[ -n "$line" ]]; then
printf '# %s\n' "$line"
fi
}
function bats_replace_filename() {
local line
while read -r line; do
printf "%s\n" "${line//$BATS_TEST_SOURCE/$BATS_TEST_FILENAME}"
done
if [[ -n "$line" ]]; then
printf "%s\n" "${line//$BATS_TEST_SOURCE/$BATS_TEST_FILENAME}"
fi
}
bats_quote_code() { # <var> <code>
printf -v "$1" -- "%s%s%s" "$BATS_BEGIN_CODE_QUOTE" "$2" "$BATS_END_CODE_QUOTE"
}
bats_check_valid_version() {
if [[ ! $1 =~ [0-9]+.[0-9]+.[0-9]+ ]]; then
printf "ERROR: version '%s' must be of format <major>.<minor>.<patch>!\n" "$1" >&2
exit 1
fi
}
# compares two versions. Return 0 when version1 < version2
bats_version_lt() { # <version1> <version2>
bats_check_valid_version "$1"
bats_check_valid_version "$2"
local -a version1_parts version2_parts
IFS=. read -ra version1_parts <<< "$1"
IFS=. read -ra version2_parts <<< "$2"
for i in {0..2}; do
if (( version1_parts[i] < version2_parts[i] )); then
return 0
elif (( version1_parts[i] > version2_parts[i] )); then
return 1
fi
done
# if we made it this far, they are equal -> also not less then
return 2 # use other failing return code to distinguish equal from gt
}
# ensure a minimum version of bats is running or exit with failure
bats_require_minimum_version() { # <required version>
local required_minimum_version=$1
if bats_version_lt "$BATS_VERSION" "$required_minimum_version"; then
printf "BATS_VERSION=%s does not meet required minimum %s\n" "$BATS_VERSION" "$required_minimum_version"
exit 1
fi
if bats_version_lt "$BATS_GUARANTEED_MINIMUM_VERSION" "$required_minimum_version"; then
BATS_GUARANTEED_MINIMUM_VERSION="$required_minimum_version"
fi
}
bats_binary_search() { # <search-value> <array-name>
if [[ $# -ne 2 ]]; then
printf "ERROR: bats_binary_search requires exactly 2 arguments: <search value> <array name>\n" >&2
return 2
fi
local -r search_value=$1 array_name=$2
# we'd like to test if array is set but we cannot distinguish unset from empty arrays, so we need to skip that
local start=0 mid end mid_value
# start is inclusive, end is exclusive ...
eval "end=\${#${array_name}[@]}"
# so start == end means empty search space
while (( start < end )); do
mid=$(( (start + end) / 2 ))
eval "mid_value=\${${array_name}[$mid]}"
if [[ "$mid_value" == "$search_value" ]]; then
return 0
elif [[ "$mid_value" < "$search_value" ]]; then
# This branch excludes equality -> +1 to skip the mid element.
# This +1 also avoids endless recursion on odd sized search ranges.
start=$((mid + 1))
else
end=$mid
fi
done
# did not find it -> its not there
return 1
}

View File

@ -0,0 +1,116 @@
#!/usr/bin/env bash
# reads (extended) bats tap streams from stdin and calls callback functions for each line
# bats_tap_stream_plan <number of tests> -> when the test plan is encountered
# bats_tap_stream_begin <test index> <test name> -> when a new test is begun WARNING: extended only
# bats_tap_stream_ok [--duration <milliseconds] <test index> <test name> -> when a test was successful
# bats_tap_stream_not_ok [--duration <milliseconds>] <test index> <test name> -> when a test has failed
# bats_tap_stream_skipped <test index> <test name> <skip reason> -> when a test was skipped
# bats_tap_stream_comment <comment text without leading '# '> <scope> -> when a comment line was encountered,
# scope tells the last encountered of plan, begin, ok, not_ok, skipped, suite
# bats_tap_stream_suite <file name> -> when a new file is begun WARNING: extended only
# bats_tap_stream_unknown <full line> <scope> -> when a line is encountered that does not match the previous entries,
# scope @see bats_tap_stream_comment
# forwards all input as is, when there is no TAP test plan header
function bats_parse_internal_extended_tap() {
local header_pattern='[0-9]+\.\.[0-9]+'
IFS= read -r header
if [[ "$header" =~ $header_pattern ]]; then
bats_tap_stream_plan "${header:3}"
else
# If the first line isn't a TAP plan, print it and pass the rest through
printf '%s\n' "$header"
exec cat
fi
ok_line_regexpr="ok ([0-9]+) (.*)"
skip_line_regexpr="ok ([0-9]+) (.*) # skip( (.*))?$"
not_ok_line_regexpr="not ok ([0-9]+) (.*)"
timing_expr="in ([0-9]+)ms$"
local test_name begin_index ok_index not_ok_index index scope
begin_index=0
index=0
scope=plan
while IFS= read -r line; do
case "$line" in
'begin '*) # this might only be called in extended tap output
((++begin_index))
scope=begin
test_name="${line#* "$begin_index" }"
bats_tap_stream_begin "$begin_index" "$test_name"
;;
'ok '*)
((++index))
if [[ "$line" =~ $ok_line_regexpr ]]; then
ok_index="${BASH_REMATCH[1]}"
test_name="${BASH_REMATCH[2]}"
if [[ "$line" =~ $skip_line_regexpr ]]; then
scope=skipped
test_name="${BASH_REMATCH[2]}" # cut off name before "# skip"
local skip_reason="${BASH_REMATCH[4]}"
bats_tap_stream_skipped "$ok_index" "$test_name" "$skip_reason"
else
scope=ok
if [[ "$line" =~ $timing_expr ]]; then
bats_tap_stream_ok --duration "${BASH_REMATCH[1]}" "$ok_index" "$test_name"
else
bats_tap_stream_ok "$ok_index" "$test_name"
fi
fi
else
printf "ERROR: could not match ok line: %s" "$line" >&2
exit 1
fi
;;
'not ok '*)
((++index))
scope=not_ok
if [[ "$line" =~ $not_ok_line_regexpr ]]; then
not_ok_index="${BASH_REMATCH[1]}"
test_name="${BASH_REMATCH[2]}"
if [[ "$line" =~ $timing_expr ]]; then
bats_tap_stream_not_ok --duration "${BASH_REMATCH[1]}" "$not_ok_index" "$test_name"
else
bats_tap_stream_not_ok "$not_ok_index" "$test_name"
fi
else
printf "ERROR: could not match not ok line: %s" "$line" >&2
exit 1
fi
;;
'# '*)
bats_tap_stream_comment "${line:2}" "$scope"
;;
'#')
bats_tap_stream_comment "" "$scope"
;;
'suite '*)
scope=suite
# pass on the
bats_tap_stream_suite "${line:6}"
;;
*)
bats_tap_stream_unknown "$line" "$scope"
;;
esac
done
}
normalize_base_path() { # <target variable> <base path>
# the relative path root to use for reporting filenames
# this is mainly intended for suite mode, where this will be the suite root folder
local base_path="$2"
# use the containing directory when --base-path is a file
if [[ ! -d "$base_path" ]]; then
base_path="$(dirname "$base_path")"
fi
# get the absolute path
base_path="$(cd "$base_path" && pwd)"
# ensure the path ends with / to strip that later on
if [[ "${base_path}" != *"/" ]]; then
base_path="$base_path/"
fi
printf -v "$1" "%s" "$base_path"
}

View File

@ -0,0 +1,22 @@
#!/usr/bin/env bash
BATS_TMPNAME="$BATS_RUN_TMPDIR/bats.$$"
BATS_PARENT_TMPNAME="$BATS_RUN_TMPDIR/bats.$PPID"
# shellcheck disable=SC2034
BATS_OUT="${BATS_TMPNAME}.out" # used in bats-exec-file
bats_preprocess_source() {
# export to make it visible to bats_evaluate_preprocessed_source
# since the latter runs in bats-exec-test's bash while this runs in bats-exec-file's
export BATS_TEST_SOURCE="${BATS_TMPNAME}.src"
bats-preprocess "$BATS_TEST_FILENAME" >"$BATS_TEST_SOURCE"
}
bats_evaluate_preprocessed_source() {
if [[ -z "${BATS_TEST_SOURCE:-}" ]]; then
BATS_TEST_SOURCE="${BATS_PARENT_TMPNAME}.src"
fi
# Dynamically loaded user files provided outside of Bats.
# shellcheck disable=SC1090
source "$BATS_TEST_SOURCE"
}

View File

@ -0,0 +1,107 @@
#!/usr/bin/env bash
# setup the semaphore environment for the loading file
bats_semaphore_setup() {
export -f bats_semaphore_get_free_slot_count
export -f bats_semaphore_acquire_while_locked
export BATS_SEMAPHORE_DIR="$BATS_RUN_TMPDIR/semaphores"
if command -v flock >/dev/null; then
bats_run_under_lock() {
flock "$BATS_SEMAPHORE_DIR" "$@"
}
elif command -v shlock >/dev/null; then
bats_run_under_lock() {
local lockfile="$BATS_SEMAPHORE_DIR/shlock.lock"
while ! shlock -p $$ -f "$lockfile"; do
sleep 1
done
# we got the lock now, execute the command
"$@"
local status=$?
# free the lock
rm -f "$lockfile"
return $status
}
else
printf "ERROR: flock/shlock is required for parallelization within files!\n" >&2
exit 1
fi
}
# $1 - output directory for stdout/stderr
# $@ - command to run
# run the given command in a semaphore
# block when there is no free slot for the semaphore
# when there is a free slot, run the command in background
# gather the output of the command in files in the given directory
bats_semaphore_run() {
local output_dir=$1
shift
local semaphore_slot
semaphore_slot=$(bats_semaphore_acquire_slot)
bats_semaphore_release_wrapper "$output_dir" "$semaphore_slot" "$@" &
printf "%d\n" "$!"
}
# $1 - output directory for stdout/stderr
# $@ - command to run
# this wraps the actual function call to install some traps on exiting
bats_semaphore_release_wrapper() {
local output_dir="$1"
local semaphore_name="$2"
shift 2 # all other parameters will be use for the command to execute
# shellcheck disable=SC2064 # we want to expand the semaphore_name right now!
trap "status=$?; bats_semaphore_release_slot '$semaphore_name'; exit $status" EXIT
mkdir -p "$output_dir"
"$@" 2>"$output_dir/stderr" >"$output_dir/stdout"
local status=$?
# bash bug: the exit trap is not called for the background process
bats_semaphore_release_slot "$semaphore_name"
trap - EXIT # avoid calling release twice
return $status
}
bats_semaphore_acquire_while_locked() {
if [[ $(bats_semaphore_get_free_slot_count) -gt 0 ]]; then
local slot=0
while [[ -e "$BATS_SEMAPHORE_DIR/slot-$slot" ]]; do
(( ++slot ))
done
if [[ $slot -lt $BATS_SEMAPHORE_NUMBER_OF_SLOTS ]]; then
touch "$BATS_SEMAPHORE_DIR/slot-$slot" && printf "%d\n" "$slot" && return 0
fi
fi
return 1
}
# block until a semaphore slot becomes free
# prints the number of the slot that it received
bats_semaphore_acquire_slot() {
mkdir -p "$BATS_SEMAPHORE_DIR"
# wait for a slot to become free
# TODO: avoid busy waiting by using signals -> this opens op prioritizing possibilities as well
while true; do
# don't lock for reading, we are fine with spuriously getting no free slot
if [[ $(bats_semaphore_get_free_slot_count) -gt 0 ]]; then
bats_run_under_lock bash -c bats_semaphore_acquire_while_locked && break
fi
sleep 1
done
}
bats_semaphore_release_slot() {
# we don't need to lock this, since only our process owns this file
# and freeing a semaphore cannot lead to conflicts with others
rm "$BATS_SEMAPHORE_DIR/slot-$1" # this will fail if we had not acquired a semaphore!
}
bats_semaphore_get_free_slot_count() {
# find might error out without returning something useful when a file is deleted,
# while the directory is traversed -> only continue when there was no error
until used_slots=$(find "$BATS_SEMAPHORE_DIR" -name 'slot-*' 2>/dev/null | wc -l); do :; done
echo $(( BATS_SEMAPHORE_NUMBER_OF_SLOTS - used_slots ))
}

View File

@ -0,0 +1,357 @@
#!/usr/bin/env bash
BATS_TEST_DIRNAME="${BATS_TEST_FILENAME%/*}"
BATS_TEST_NAMES=()
# shellcheck source=lib/bats-core/warnings.bash
source "$BATS_ROOT/lib/bats-core/warnings.bash"
# find_in_bats_lib_path echoes the first recognized load path to
# a library in BATS_LIB_PATH or relative to BATS_TEST_DIRNAME.
#
# Libraries relative to BATS_TEST_DIRNAME take precedence over
# BATS_LIB_PATH.
#
# Library load paths are recognized using find_library_load_path.
#
# If no library is found find_in_bats_lib_path returns 1.
find_in_bats_lib_path() { # <return-var> <library-name>
local return_var="${1:?}"
local library_name="${2:?}"
local -a bats_lib_paths
IFS=: read -ra bats_lib_paths <<< "$BATS_LIB_PATH"
for path in "${bats_lib_paths[@]}"; do
if [[ -f "$path/$library_name" ]]; then
printf -v "$return_var" "%s" "$path/$library_name"
# A library load path was found, return
return 0
elif [[ -f "$path/$library_name/load.bash" ]]; then
printf -v "$return_var" "%s" "$path/$library_name/load.bash"
# A library load path was found, return
return 0
fi
done
return 1
}
# bats_internal_load expects an absolute path that is a library load path.
#
# If the library load path points to a file (a library loader) it is
# sourced.
#
# If it points to a directory all files ending in .bash inside of the
# directory are sourced.
#
# If the sourcing of the library loader or of a file in a library
# directory fails bats_internal_load prints an error message and returns 1.
#
# If the passed library load path is not absolute or is not a valid file
# or directory bats_internal_load prints an error message and returns 1.
bats_internal_load() {
local library_load_path="${1:?}"
if [[ "${library_load_path:0:1}" != / ]]; then
printf "Passed library load path is not an absolute path: %s\n" "$library_load_path" >&2
return 1
fi
# library_load_path is a library loader
if [[ -f "$library_load_path" ]]; then
# shellcheck disable=SC1090
if ! source "$library_load_path"; then
printf "Error while sourcing library loader at '%s'\n" "$library_load_path" >&2
return 1
fi
return 0
fi
printf "Passed library load path is neither a library loader nor library directory: %s\n" "$library_load_path" >&2
return 1
}
# bats_load_safe accepts an argument called 'slug' and attempts to find and
# source a library based on the slug.
#
# A slug can be an absolute path, a library name or a relative path.
#
# If the slug is an absolute path bats_load_safe attempts to find the library
# load path using find_library_load_path.
# What is considered a library load path is documented in the
# documentation for find_library_load_path.
#
# If the slug is not an absolute path it is considered a library name or
# relative path. bats_load_safe attempts to find the library load path using
# find_in_bats_lib_path.
#
# If bats_load_safe can find a library load path it is passed to bats_internal_load.
# If bats_internal_load fails bats_load_safe returns 1.
#
# If no library load path can be found bats_load_safe prints an error message
# and returns 1.
bats_load_safe() {
local slug="${1:?}"
if [[ ${slug:0:1} != / ]]; then # relative paths are relative to BATS_TEST_DIRNAME
slug="$BATS_TEST_DIRNAME/$slug"
fi
if [[ -f "$slug.bash" ]]; then
bats_internal_load "$slug.bash"
return $?
elif [[ -f "$slug" ]]; then
bats_internal_load "$slug"
return $?
fi
# loading from PATH (retained for backwards compatibility)
if [[ ! -f "$1" ]] && type -P "$1" >/dev/null; then
# shellcheck disable=SC1090
source "$1"
return $?
fi
# No library load path can be found
printf "bats_load_safe: Could not find '%s'[.bash]\n" "$slug" >&2
return 1
}
bats_require_lib_path() {
if [[ -z "${BATS_LIB_PATH:-}" ]]; then
printf "%s: requires BATS_LIB_PATH to be set!\n" "${FUNCNAME[1]}" >&2
exit 1
fi
}
bats_load_library_safe() { # <slug>
local slug="${1:?}" library_path
bats_require_lib_path
# Check for library load paths in BATS_TEST_DIRNAME and BATS_LIB_PATH
if [[ ${slug:0:1} != / ]]; then
find_in_bats_lib_path library_path "$slug"
if [[ -z "$library_path" ]]; then
printf "Could not find library '%s' relative to test file or in BATS_LIB_PATH\n" "$slug" >&2
return 1
fi
else
# absolute paths are taken as is
library_path="$slug"
if [[ ! -f "$library_path" ]]; then
printf "Could not find library on absolute path '%s'\n" "$library_path" >&2
return 1
fi
fi
bats_internal_load "$library_path"
return $?
}
# immediately exit on error, use bats_load_library_safe to catch and handle errors
bats_load_library() { # <slug>
bats_require_lib_path
if ! bats_load_library_safe "$@"; then
exit 1
fi
}
# load acts like bats_load_safe but exits the shell instead of returning 1.
load() {
if ! bats_load_safe "$@"; then
echo "${FUNCNAME[0]} $LINENO" >&3
exit 1
fi
}
bats_redirect_stderr_into_file() {
"$@" 2>>"$bats_run_separate_stderr_file" # use >> to see collisions' content
}
bats_merge_stdout_and_stderr() {
"$@" 2>&1
}
# write separate lines from <input-var> into <output-array>
bats_separate_lines() { # <output-array> <input-var>
local output_array_name="$1"
local input_var_name="$2"
if [[ $keep_empty_lines ]]; then
local bats_separate_lines_lines=()
if [[ -n "${!input_var_name}" ]]; then # avoid getting an empty line for empty input
while IFS= read -r line; do
bats_separate_lines_lines+=("$line")
done <<<"${!input_var_name}"
fi
eval "${output_array_name}=(\"\${bats_separate_lines_lines[@]}\")"
else
# shellcheck disable=SC2034,SC2206
IFS=$'\n' read -d '' -r -a "$output_array_name" <<<"${!input_var_name}" || true # don't fail due to EOF
fi
}
run() { # [!|-N] [--keep-empty-lines] [--separate-stderr] [--] <command to run...>
# This has to be restored on exit from this function to avoid leaking our trap INT into surrounding code.
# Non zero exits won't restore under the assumption that they will fail the test before it can be aborted,
# which allows us to avoid duplicating the restore code on every exit path
trap bats_interrupt_trap_in_run INT
local expected_rc=
local keep_empty_lines=
local output_case=merged
local has_flags=
# parse options starting with -
while [[ $# -gt 0 ]] && [[ $1 == -* || $1 == '!' ]]; do
has_flags=1
case "$1" in
'!')
expected_rc=-1
;;
-[0-9]*)
expected_rc=${1#-}
if [[ $expected_rc =~ [^0-9] ]]; then
printf "Usage error: run: '-NNN' requires numeric NNN (got: %s)\n" "$expected_rc" >&2
return 1
elif [[ $expected_rc -gt 255 ]]; then
printf "Usage error: run: '-NNN': NNN must be <= 255 (got: %d)\n" "$expected_rc" >&2
return 1
fi
;;
--keep-empty-lines)
keep_empty_lines=1
;;
--separate-stderr)
output_case="separate"
;;
--)
shift # eat the -- before breaking away
break
;;
*)
printf "Usage error: unknown flag '%s'" "$1" >&2
return 1
;;
esac
shift
done
if [[ -n $has_flags ]]; then
bats_warn_minimum_guaranteed_version "Using flags on \`run\`" 1.5.0
fi
local pre_command=
case "$output_case" in
merged) # redirects stderr into stdout and fills only $output/$lines
pre_command=bats_merge_stdout_and_stderr
;;
separate) # splits stderr into own file and fills $stderr/$stderr_lines too
local bats_run_separate_stderr_file
bats_run_separate_stderr_file="$(mktemp "${BATS_TEST_TMPDIR}/separate-stderr-XXXXXX")"
pre_command=bats_redirect_stderr_into_file
;;
esac
local origFlags="$-"
set +eET
local origIFS="$IFS"
if [[ $keep_empty_lines ]]; then
# 'output', 'status', 'lines' are global variables available to tests.
# preserve trailing newlines by appending . and removing it later
# shellcheck disable=SC2034
output="$($pre_command "$@"; status=$?; printf .; exit $status)" && status=0 || status=$?
output="${output%.}"
else
# 'output', 'status', 'lines' are global variables available to tests.
# shellcheck disable=SC2034
output="$($pre_command "$@")" && status=0 || status=$?
fi
bats_separate_lines lines output
if [[ "$output_case" == separate ]]; then
# shellcheck disable=SC2034
read -d '' -r stderr < "$bats_run_separate_stderr_file"
bats_separate_lines stderr_lines stderr
fi
# shellcheck disable=SC2034
BATS_RUN_COMMAND="${*}"
IFS="$origIFS"
set "-$origFlags"
if [[ ${BATS_VERBOSE_RUN:-} ]]; then
printf "%s\n" "$output"
fi
if [[ -n "$expected_rc" ]]; then
if [[ "$expected_rc" = "-1" ]]; then
if [[ "$status" -eq 0 ]]; then
BATS_ERROR_SUFFIX=", expected nonzero exit code!"
return 1
fi
elif [ "$status" -ne "$expected_rc" ]; then
# shellcheck disable=SC2034
BATS_ERROR_SUFFIX=", expected exit code $expected_rc, got $status"
return 1
fi
elif [[ "$status" -eq 127 ]]; then # "command not found"
bats_generate_warning 1 "$BATS_RUN_COMMAND"
fi
# don't leak our trap into surrounding code
trap bats_interrupt_trap INT
}
setup() {
return 0
}
teardown() {
return 0
}
skip() {
# if this is a skip in teardown ...
if [[ -n "${BATS_TEARDOWN_STARTED-}" ]]; then
# ... we want to skip the rest of teardown.
# communicate to bats_exit_trap that the teardown was completed without error
# shellcheck disable=SC2034
BATS_TEARDOWN_COMPLETED=1
# if we are already in the exit trap (e.g. due to previous skip) ...
if [[ "$BATS_TEARDOWN_STARTED" == as-exit-trap ]]; then
# ... we need to do the rest of the tear_down_trap that would otherwise be skipped after the next call to exit
bats_exit_trap
# and then do the exit (at the end of this function)
fi
# if we aren't in exit trap, the normal exit handling should suffice
else
# ... this is either skip in test or skip in setup.
# Following variables are used in bats-exec-test which sources this file
# shellcheck disable=SC2034
BATS_TEST_SKIPPED="${1:-1}"
# shellcheck disable=SC2034
BATS_TEST_COMPLETED=1
fi
exit 0
}
bats_test_begin() {
BATS_TEST_DESCRIPTION="$1"
if [[ -n "$BATS_EXTENDED_SYNTAX" ]]; then
printf 'begin %d %s\n' "$BATS_SUITE_TEST_NUMBER" "${BATS_TEST_NAME_PREFIX:-}$BATS_TEST_DESCRIPTION" >&3
fi
setup
}
bats_test_function() {
local test_name="$1"
BATS_TEST_NAMES+=("$test_name")
}
# decides whether a failed test should be run again
bats_should_retry_test() {
# test try number starts at 1
# 0 retries means run only first try
(( BATS_TEST_TRY_NUMBER <= BATS_TEST_RETRIES ))
}

View File

@ -0,0 +1,386 @@
#!/usr/bin/env bash
# shellcheck source=lib/bats-core/common.bash
source "$BATS_ROOT/lib/bats-core/common.bash"
bats_capture_stack_trace() {
local test_file
local funcname
local i
BATS_DEBUG_LAST_STACK_TRACE=()
for ((i = 2; i != ${#FUNCNAME[@]}; ++i)); do
# Use BATS_TEST_SOURCE if necessary to work around Bash < 4.4 bug whereby
# calling an exported function erases the test file's BASH_SOURCE entry.
test_file="${BASH_SOURCE[$i]:-$BATS_TEST_SOURCE}"
funcname="${FUNCNAME[$i]}"
BATS_DEBUG_LAST_STACK_TRACE+=("${BASH_LINENO[$((i-1))]} $funcname $test_file")
case "$funcname" in
"$BATS_TEST_NAME" | setup | teardown | setup_file | teardown_file | setup_suite | teardown_suite)
break
;;
esac
if [[ "${BASH_SOURCE[$i + 1]:-}" == *"bats-exec-file" ]] && [[ "$funcname" == 'source' ]]; then
break
fi
done
}
bats_get_failure_stack_trace() {
local stack_trace_var
# See bats_debug_trap for details.
if [[ -n "${BATS_DEBUG_LAST_STACK_TRACE_IS_VALID:-}" ]]; then
stack_trace_var=BATS_DEBUG_LAST_STACK_TRACE
else
stack_trace_var=BATS_DEBUG_LASTLAST_STACK_TRACE
fi
# shellcheck disable=SC2016
eval "$(printf \
'%s=(${%s[@]+"${%s[@]}"})' \
"${1}" \
"${stack_trace_var}" \
"${stack_trace_var}")"
}
bats_print_stack_trace() {
local frame
local index=1
local count="${#@}"
local filename
local lineno
for frame in "$@"; do
bats_frame_filename "$frame" 'filename'
bats_trim_filename "$filename" 'filename'
bats_frame_lineno "$frame" 'lineno'
printf '%s' "${BATS_STACK_TRACE_PREFIX-# }"
if [[ $index -eq 1 ]]; then
printf '('
else
printf ' '
fi
local fn
bats_frame_function "$frame" 'fn'
if [[ "$fn" != "$BATS_TEST_NAME" ]] &&
# don't print "from function `source'"",
# when failing in free code during `source $test_file` from bats-exec-file
! [[ "$fn" == 'source' && $index -eq $count ]]; then
local quoted_fn
bats_quote_code quoted_fn "$fn"
printf "from function %s " "$quoted_fn"
fi
if [[ $index -eq $count ]]; then
printf 'in test file %s, line %d)\n' "$filename" "$lineno"
else
printf 'in file %s, line %d,\n' "$filename" "$lineno"
fi
((++index))
done
}
bats_print_failed_command() {
local stack_trace=("${@}")
if [[ ${#stack_trace[@]} -eq 0 ]]; then
return
fi
local frame="${stack_trace[${#stack_trace[@]} - 1]}"
local filename
local lineno
local failed_line
local failed_command
bats_frame_filename "$frame" 'filename'
bats_frame_lineno "$frame" 'lineno'
bats_extract_line "$filename" "$lineno" 'failed_line'
bats_strip_string "$failed_line" 'failed_command'
local quoted_failed_command
bats_quote_code quoted_failed_command "$failed_command"
printf '# %s ' "${quoted_failed_command}"
if [[ "$BATS_ERROR_STATUS" -eq 1 ]]; then
printf 'failed%s\n' "$BATS_ERROR_SUFFIX"
else
printf 'failed with status %d%s\n' "$BATS_ERROR_STATUS" "$BATS_ERROR_SUFFIX"
fi
}
bats_frame_lineno() {
printf -v "$2" '%s' "${1%% *}"
}
bats_frame_function() {
local __bff_function="${1#* }"
printf -v "$2" '%s' "${__bff_function%% *}"
}
bats_frame_filename() {
local __bff_filename="${1#* }"
__bff_filename="${__bff_filename#* }"
if [[ "$__bff_filename" == "$BATS_TEST_SOURCE" ]]; then
__bff_filename="$BATS_TEST_FILENAME"
fi
printf -v "$2" '%s' "$__bff_filename"
}
bats_extract_line() {
local __bats_extract_line_line
local __bats_extract_line_index=0
while IFS= read -r __bats_extract_line_line; do
if [[ "$((++__bats_extract_line_index))" -eq "$2" ]]; then
printf -v "$3" '%s' "${__bats_extract_line_line%$'\r'}"
break
fi
done <"$1"
}
bats_strip_string() {
[[ "$1" =~ ^[[:space:]]*(.*)[[:space:]]*$ ]]
printf -v "$2" '%s' "${BASH_REMATCH[1]}"
}
bats_trim_filename() {
printf -v "$2" '%s' "${1#"$BATS_CWD"/}"
}
# normalize a windows path from e.g. C:/directory to /c/directory
# The path must point to an existing/accessable directory, not a file!
bats_normalize_windows_dir_path() { # <output-var> <path>
local output_var="$1" path="$2"
if [[ "$output_var" != NORMALIZED_INPUT ]]; then
local NORMALIZED_INPUT
fi
if [[ $path == ?:* ]]; then
NORMALIZED_INPUT="$(cd "$path" || exit 1; pwd)"
else
NORMALIZED_INPUT="$path"
fi
printf -v "$output_var" "%s" "$NORMALIZED_INPUT"
}
bats_emit_trace() {
if [[ $BATS_TRACE_LEVEL -gt 0 ]]; then
local line=${BASH_LINENO[1]}
# shellcheck disable=SC2016
if [[ $BASH_COMMAND != '"$BATS_TEST_NAME" >> "$BATS_OUT" 2>&1 4>&1' && $BASH_COMMAND != "bats_test_begin "* ]] && # don't emit these internal calls
[[ $BASH_COMMAND != "$BATS_LAST_BASH_COMMAND" || $line != "$BATS_LAST_BASH_LINENO" ]] &&
# avoid printing a function twice (at call site and at definition site)
[[ $BASH_COMMAND != "$BATS_LAST_BASH_COMMAND" || ${BASH_LINENO[2]} != "$BATS_LAST_BASH_LINENO" || ${BASH_SOURCE[3]} != "$BATS_LAST_BASH_SOURCE" ]]; then
local file="${BASH_SOURCE[2]}" # index 2: skip over bats_emit_trace and bats_debug_trap
if [[ $file == "${BATS_TEST_SOURCE}" ]]; then
file="$BATS_TEST_FILENAME"
fi
local padding='$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$'
if (( BATS_LAST_STACK_DEPTH != ${#BASH_LINENO[@]} )); then
printf '%s [%s:%d]\n' "${padding::${#BASH_LINENO[@]}-4}" "${file##*/}" "$line" >&4
fi
printf '%s %s\n' "${padding::${#BASH_LINENO[@]}-4}" "$BASH_COMMAND" >&4
BATS_LAST_BASH_COMMAND="$BASH_COMMAND"
BATS_LAST_BASH_LINENO="$line"
BATS_LAST_BASH_SOURCE="${BASH_SOURCE[2]}"
BATS_LAST_STACK_DEPTH="${#BASH_LINENO[@]}"
fi
fi
}
# bats_debug_trap tracks the last line of code executed within a test. This is
# necessary because $BASH_LINENO is often incorrect inside of ERR and EXIT
# trap handlers.
#
# Below are tables describing different command failure scenarios and the
# reliability of $BASH_LINENO within different the executed DEBUG, ERR, and EXIT
# trap handlers. Naturally, the behaviors change between versions of Bash.
#
# Table rows should be read left to right. For example, on bash version
# 4.0.44(2)-release, if a test executes `false` (or any other failing external
# command), bash will do the following in order:
# 1. Call the DEBUG trap handler (bats_debug_trap) with $BASH_LINENO referring
# to the source line containing the `false` command, then
# 2. Call the DEBUG trap handler again, but with an incorrect $BASH_LINENO, then
# 3. Call the ERR trap handler, but with a (possibly-different) incorrect
# $BASH_LINENO, then
# 4. Call the DEBUG trap handler again, but with $BASH_LINENO set to 1, then
# 5. Call the EXIT trap handler, with $BASH_LINENO set to 1.
#
# bash version 4.4.20(1)-release
# command | first DEBUG | second DEBUG | ERR | third DEBUG | EXIT
# -------------+-------------+--------------+---------+-------------+--------
# false | OK | OK | OK | BAD[1] | BAD[1]
# [[ 1 = 2 ]] | OK | BAD[2] | BAD[2] | BAD[1] | BAD[1]
# (( 1 = 2 )) | OK | BAD[2] | BAD[2] | BAD[1] | BAD[1]
# ! true | OK | --- | BAD[4] | --- | BAD[1]
# $var_dne | OK | --- | --- | BAD[1] | BAD[1]
# source /dne | OK | --- | --- | BAD[1] | BAD[1]
#
# bash version 4.0.44(2)-release
# command | first DEBUG | second DEBUG | ERR | third DEBUG | EXIT
# -------------+-------------+--------------+---------+-------------+--------
# false | OK | BAD[3] | BAD[3] | BAD[1] | BAD[1]
# [[ 1 = 2 ]] | OK | --- | BAD[3] | --- | BAD[1]
# (( 1 = 2 )) | OK | --- | BAD[3] | --- | BAD[1]
# ! true | OK | --- | BAD[3] | --- | BAD[1]
# $var_dne | OK | --- | --- | BAD[1] | BAD[1]
# source /dne | OK | --- | --- | BAD[1] | BAD[1]
#
# [1] The reported line number is always 1.
# [2] The reported source location is that of the beginning of the function
# calling the command.
# [3] The reported line is that of the last command executed in the DEBUG trap
# handler.
# [4] The reported source location is that of the call to the function calling
# the command.
bats_debug_trap() {
# on windows we sometimes get a mix of paths (when install via nmp install -g)
# which have C:/... or /c/... comparing them is going to be problematic.
# We need to normalize them to a common format!
local NORMALIZED_INPUT
bats_normalize_windows_dir_path NORMALIZED_INPUT "${1%/*}"
local file_excluded='' path
for path in "${BATS_DEBUG_EXCLUDE_PATHS[@]}"; do
if [[ "$NORMALIZED_INPUT" == "$path"* ]]; then
file_excluded=1
break
fi
done
# don't update the trace within library functions or we get backtraces from inside traps
# also don't record new stack traces while handling interruptions, to avoid overriding the interrupted command
if [[ -z "$file_excluded" && "${BATS_INTERRUPTED-NOTSET}" == NOTSET ]]; then
BATS_DEBUG_LASTLAST_STACK_TRACE=(
${BATS_DEBUG_LAST_STACK_TRACE[@]+"${BATS_DEBUG_LAST_STACK_TRACE[@]}"}
)
BATS_DEBUG_LAST_LINENO=(${BASH_LINENO[@]+"${BASH_LINENO[@]}"})
BATS_DEBUG_LAST_SOURCE=(${BASH_SOURCE[@]+"${BASH_SOURCE[@]}"})
bats_capture_stack_trace
bats_emit_trace
fi
}
# For some versions of Bash, the `ERR` trap may not always fire for every
# command failure, but the `EXIT` trap will. Also, some command failures may not
# set `$?` properly. See #72 and #81 for details.
#
# For this reason, we call `bats_check_status_from_trap` at the very beginning
# of `bats_teardown_trap` and check the value of `$BATS_TEST_COMPLETED` before
# taking other actions. We also adjust the exit status value if needed.
#
# See `bats_exit_trap` for an additional EXIT error handling case when `$?`
# isn't set properly during `teardown()` errors.
bats_check_status_from_trap() {
local status="$?"
if [[ -z "${BATS_TEST_COMPLETED:-}" ]]; then
BATS_ERROR_STATUS="${BATS_ERROR_STATUS:-$status}"
if [[ "$BATS_ERROR_STATUS" -eq 0 ]]; then
BATS_ERROR_STATUS=1
fi
trap - DEBUG
fi
}
bats_add_debug_exclude_path() { # <path>
if [[ -z "$1" ]]; then # don't exclude everything
printf "bats_add_debug_exclude_path: Exclude path must not be empty!\n" >&2
return 1
fi
if [[ "$OSTYPE" == cygwin || "$OSTYPE" == msys ]]; then
local normalized_dir
bats_normalize_windows_dir_path normalized_dir "$1"
BATS_DEBUG_EXCLUDE_PATHS+=("$normalized_dir")
else
BATS_DEBUG_EXCLUDE_PATHS+=("$1")
fi
}
bats_setup_tracing() {
# Variables for capturing accurate stack traces. See bats_debug_trap for
# details.
#
# BATS_DEBUG_LAST_LINENO, BATS_DEBUG_LAST_SOURCE, and
# BATS_DEBUG_LAST_STACK_TRACE hold data from the most recent call to
# bats_debug_trap.
#
# BATS_DEBUG_LASTLAST_STACK_TRACE holds data from two bats_debug_trap calls
# ago.
#
# BATS_DEBUG_LAST_STACK_TRACE_IS_VALID indicates that
# BATS_DEBUG_LAST_STACK_TRACE contains the stack trace of the test's error. If
# unset, BATS_DEBUG_LAST_STACK_TRACE is unreliable and
# BATS_DEBUG_LASTLAST_STACK_TRACE should be used instead.
BATS_DEBUG_LASTLAST_STACK_TRACE=()
BATS_DEBUG_LAST_LINENO=()
BATS_DEBUG_LAST_SOURCE=()
BATS_DEBUG_LAST_STACK_TRACE=()
BATS_DEBUG_LAST_STACK_TRACE_IS_VALID=
BATS_ERROR_SUFFIX=
BATS_DEBUG_EXCLUDE_PATHS=()
# exclude some paths by default
bats_add_debug_exclude_path "$BATS_ROOT/lib/"
bats_add_debug_exclude_path "$BATS_ROOT/libexec/"
exec 4<&1 # used for tracing
if [[ "${BATS_TRACE_LEVEL:-0}" -gt 0 ]]; then
# avoid undefined variable errors
BATS_LAST_BASH_COMMAND=
BATS_LAST_BASH_LINENO=
BATS_LAST_BASH_SOURCE=
BATS_LAST_STACK_DEPTH=
# try to exclude helper libraries if found, this is only relevant for tracing
while read -r path; do
bats_add_debug_exclude_path "$path"
done < <(find "$PWD" -type d -name bats-assert -o -name bats-support)
fi
local exclude_paths path
# exclude user defined libraries
IFS=':' read -r exclude_paths <<< "${BATS_DEBUG_EXCLUDE_PATHS:-}"
for path in "${exclude_paths[@]}"; do
if [[ -n "$path" ]]; then
bats_add_debug_exclude_path "$path"
fi
done
# turn on traps after setting excludes to avoid tracing the exclude setup
trap 'bats_debug_trap "$BASH_SOURCE"' DEBUG
trap 'bats_error_trap' ERR
}
bats_error_trap() {
bats_check_status_from_trap
# If necessary, undo the most recent stack trace captured by bats_debug_trap.
# See bats_debug_trap for details.
if [[ "${BASH_LINENO[*]}" = "${BATS_DEBUG_LAST_LINENO[*]:-}"
&& "${BASH_SOURCE[*]}" = "${BATS_DEBUG_LAST_SOURCE[*]:-}"
&& -z "$BATS_DEBUG_LAST_STACK_TRACE_IS_VALID" ]]; then
BATS_DEBUG_LAST_STACK_TRACE=(
${BATS_DEBUG_LASTLAST_STACK_TRACE[@]+"${BATS_DEBUG_LASTLAST_STACK_TRACE[@]}"}
)
fi
BATS_DEBUG_LAST_STACK_TRACE_IS_VALID=1
}
bats_interrupt_trap() {
# mark the interruption, to handle during exit
BATS_INTERRUPTED=true
BATS_ERROR_STATUS=130
# debug trap fires before interrupt trap but gets wrong linenumber (line 1)
# -> use last stack trace
exit $BATS_ERROR_STATUS
}
# this is used inside run()
bats_interrupt_trap_in_run() {
# mark the interruption, to handle during exit
BATS_INTERRUPTED=true
BATS_ERROR_STATUS=130
BATS_DEBUG_LAST_STACK_TRACE_IS_VALID=true
exit $BATS_ERROR_STATUS
}

View File

@ -0,0 +1,37 @@
#!/usr/bin/env bash
bats_test_count_validator() {
trap '' INT # continue forwarding
header_pattern='[0-9]+\.\.[0-9]+'
IFS= read -r header
# repeat the header
printf "%s\n" "$header"
# if we detect a TAP plan
if [[ "$header" =~ $header_pattern ]]; then
# extract the number of tests ...
local expected_number_of_tests="${header:3}"
# ... count the actual number of [not ] oks...
local actual_number_of_tests=0
while IFS= read -r line; do
# forward line
printf "%s\n" "$line"
case "$line" in
'ok '*)
(( ++actual_number_of_tests ))
;;
'not ok'*)
(( ++actual_number_of_tests ))
;;
esac
done
# ... and error if they are not the same
if [[ "${actual_number_of_tests}" != "${expected_number_of_tests}" ]]; then
printf '# bats warning: Executed %s instead of expected %s tests\n' "$actual_number_of_tests" "$expected_number_of_tests"
return 1
fi
else
# forward output unchanged
cat
fi
}

View File

@ -0,0 +1,35 @@
#!/usr/bin/env bash
# shellcheck source=lib/bats-core/tracing.bash
source "$BATS_ROOT/lib/bats-core/tracing.bash"
BATS_WARNING_SHORT_DESCS=(
# to start with 1
'PADDING'
# see issue #578 for context
"\`run\`'s command \`%s\` exited with code 127, indicating 'Command not found'. Use run's return code checks, e.g. \`run -127\`, to fix this message."
"%s requires at least BATS_VERSION=%s. Use \`bats_require_minimum_version %s\` to fix this message."
)
# generate a warning report for the parent call's call site
bats_generate_warning() { # <warning number> [<printf args for warning string>...]
local warning_number="$1" padding="00"
shift
if [[ $warning_number =~ [0-9]+ ]] && ((warning_number < ${#BATS_WARNING_SHORT_DESCS[@]} )); then
{
printf "BW%s: ${BATS_WARNING_SHORT_DESCS[$warning_number]}\n" "${padding:${#warning_number}}${warning_number}" "$@"
bats_capture_stack_trace
BATS_STACK_TRACE_PREFIX=' ' bats_print_stack_trace "${BATS_DEBUG_LAST_STACK_TRACE[@]}"
} >> "$BATS_WARNING_FILE" 2>&3
else
printf "Invalid Bats warning number '%s'. It must be an integer between 1 and %d." "$warning_number" "$((${#BATS_WARNING_SHORT_DESCS[@]} - 1))" >&2
exit 1
fi
}
# generate a warning if the BATS_GUARANTEED_MINIMUM_VERSION is not high enough
bats_warn_minimum_guaranteed_version() { # <feature> <minimum required version>
if bats_version_lt "$BATS_GUARANTEED_MINIMUM_VERSION" "$2"; then
bats_generate_warning 2 "$1" "$2" "$2"
fi
}

View File

@ -0,0 +1,467 @@
#!/usr/bin/env bash
set -e
export BATS_VERSION='1.7.0'
VALID_FORMATTERS="pretty, junit, tap, tap13"
version() {
printf 'Bats %s\n' "$BATS_VERSION"
}
abort() {
local print_usage=1
if [[ ${1:-} == --no-print-usage ]]; then
print_usage=
shift
fi
printf 'Error: %s\n' "$1" >&2
if [[ -n $print_usage ]]; then
usage >&2
fi
exit 1
}
usage() {
local cmd="${0##*/}"
local line
cat <<HELP_TEXT_HEADER
Usage: ${cmd} [OPTIONS] <tests>
${cmd} [-h | -v]
HELP_TEXT_HEADER
cat <<'HELP_TEXT_BODY'
<tests> is the path to a Bats test file, or the path to a directory
containing Bats test files (ending with ".bats")
-c, --count Count test cases without running any tests
--code-quote-style <style>
A two character string of code quote delimiters
or 'custom' which requires setting $BATS_BEGIN_CODE_QUOTE and
$BATS_END_CODE_QUOTE. Can also be set via $BATS_CODE_QUOTE_STYLE
-f, --filter <regex> Only run tests that match the regular expression
--filter-status <status> Only run tests with the given status in the last completed (no CTRL+C/SIGINT) run.
Valid <status> values are:
failed - runs tests that failed or were not present in the last run
missed - runs tests that were not present in the last run
-F, --formatter <type> Switch between formatters: pretty (default),
tap (default w/o term), tap13, junit, /<absolute path to formatter>
--gather-test-outputs-in <directory>
Gather the output of failing *and* passing tests
as files in directory (if existing, must be empty)
-h, --help Display this help message
-j, --jobs <jobs> Number of parallel jobs (requires GNU parallel)
--no-tempdir-cleanup Preserve test output temporary directory
--no-parallelize-across-files
Serialize test file execution instead of running
them in parallel (requires --jobs >1)
--no-parallelize-within-files
Serialize test execution within files instead of
running them in parallel (requires --jobs >1)
--report-formatter <type> Switch between reporters (same options as --formatter)
-o, --output <dir> Directory to write report files (must exist)
-p, --pretty Shorthand for "--formatter pretty"
--print-output-on-failure Automatically print the value of `$output` on failed tests
-r, --recursive Include tests in subdirectories
--show-output-of-passing-tests
Print output of passing tests
-t, --tap Shorthand for "--formatter tap"
-T, --timing Add timing information to tests
-x, --trace Print test commands as they are executed (like `set -x`)
--verbose-run Make `run` print `$output` by default
-v, --version Display the version number
For more information, see https://github.com/bats-core/bats-core
HELP_TEXT_BODY
}
expand_path() {
local path="${1%/}"
local dirname="${path%/*}"
local result="$2"
if [[ "$dirname" == "$path" ]]; then
dirname="$PWD"
else
cd "$dirname"
dirname="$PWD"
cd "$OLDPWD"
fi
printf -v "$result" '%s/%s' "$dirname" "${path##*/}"
}
BATS_LIBEXEC="$(cd "$(dirname "$(bats_readlinkf "${BASH_SOURCE[0]}")")"; pwd)"
export BATS_LIBEXEC
export BATS_CWD="$PWD"
export BATS_TEST_FILTER=
export PATH="$BATS_LIBEXEC:$PATH"
export BATS_ROOT_PID=$$
export BATS_TMPDIR="${TMPDIR:-/tmp}"
BATS_TMPDIR=${BATS_TMPDIR%/} # chop off trailing / to avoid duplication
export BATS_RUN_TMPDIR=
export BATS_GUARANTEED_MINIMUM_VERSION=0.0.0
if [[ ! -d "${BATS_TMPDIR}" ]];then
printf "Error: BATS_TMPDIR (%s) does not exist or is not a directory" "${BATS_TMPDIR}" >&2
exit 1
elif [[ ! -w "${BATS_TMPDIR}" ]];then
printf "Error: BATS_TMPDIR (%s) is not writable" "${BATS_TMPDIR}" >&2
exit 1
fi
arguments=()
# Unpack single-character options bundled together, e.g. -cr, -pr.
for arg in "$@"; do
if [[ "$arg" =~ ^-[^-]. ]]; then
index=1
while option="${arg:$((index++)):1}"; do
if [[ -z "$option" ]]; then
break
fi
arguments+=("-$option")
done
else
arguments+=("$arg")
fi
shift
done
set -- "${arguments[@]}"
arguments=()
unset flags recursive formatter_flags
flags=('--dummy-flag') # add a dummy flag to prevent unset variable errors on empty array expansion in old bash versions
formatter_flags=('--dummy-flag') # add a dummy flag to prevent unset variable errors on empty array expansion in old bash versions
formatter='tap'
report_formatter=''
recursive=
setup_suite_file=''
export BATS_TEMPDIR_CLEANUP=1
output=
if [[ -z "${CI:-}" && -t 0 && -t 1 ]] && command -v tput >/dev/null; then
formatter='pretty'
fi
while [[ "$#" -ne 0 ]]; do
case "$1" in
-h | --help)
version
usage
exit 0
;;
-v | --version)
version
exit 0
;;
-c | --count)
flags+=('-c')
;;
-f | --filter)
shift
flags+=('-f' "$1")
;;
-F | --formatter)
shift
# allow cat formatter to see extended output but don't advertise to users
if [[ $1 =~ ^(pretty|junit|tap|tap13|cat|/.*)$ ]]; then
formatter="$1"
else
printf "Unknown formatter '%s', valid options are %s\n" "$1" "${VALID_FORMATTERS}"
exit 1
fi
;;
--report-formatter)
shift
if [[ $1 =~ ^(pretty|junit|tap|tap13)$ ]]; then
report_formatter="$1"
else
printf "Unknown report formatter '%s', valid options are %s\n" "$1" "${VALID_FORMATTERS}"
exit 1
fi
;;
-o | --output)
shift
output="$1"
;;
-p | --pretty)
formatter='pretty'
;;
-j | --jobs)
shift
flags+=('-j' "$1")
;;
-r | --recursive)
recursive=1
;;
-t | --tap)
formatter='tap'
;;
-T | --timing)
flags+=('-T')
formatter_flags+=('-T')
;;
# this flag is now a no-op, as it is the parallel default
--parallel-preserve-environment)
;;
--no-parallelize-across-files)
flags+=("--no-parallelize-across-files")
;;
--no-parallelize-within-files)
flags+=("--no-parallelize-within-files")
;;
--no-tempdir-cleanup)
BATS_TEMPDIR_CLEANUP=''
;;
--tempdir) # for internal test consumption only!
BATS_RUN_TMPDIR="$2"
shift
;;
-x | --trace)
flags+=(--trace)
;;
--print-output-on-failure)
flags+=(--print-output-on-failure)
;;
--show-output-of-passing-tests)
flags+=(--show-output-of-passing-tests)
;;
--verbose-run)
flags+=(--verbose-run)
;;
--gather-test-outputs-in)
shift
output_dir="$1"
if [ -d "$output_dir" ]; then
if ! find "$output_dir" -mindepth 1 -exec false {} + 2>/dev/null; then
abort --no-print-usage "Directory '$output_dir' must be empty for --gather-test-outputs-in"
fi
elif ! mkdir "$output_dir" 2>/dev/null; then
abort --no-print-usage "Could not create '$output_dir' for --gather-test-outputs-in"
fi
flags+=(--gather-test-outputs-in "$output_dir")
;;
--setup-suite-file)
shift
setup_suite_file="$1"
;;
--code-quote-style)
shift
BATS_CODE_QUOTE_STYLE="$1"
;;
--filter-status)
shift
flags+=('--filter-status' "$1")
;;
-*)
abort "Bad command line option '$1'"
;;
*)
arguments+=("$1")
;;
esac
shift
done
if [[ -n "${BATS_RUN_TMPDIR:-}" ]];then
if [[ -d "$BATS_RUN_TMPDIR" ]]; then
printf "Error: BATS_RUN_TMPDIR (%s) already exists\n" "$BATS_RUN_TMPDIR" >&2
printf "Reusing old run directories can lead to unexpected results ... aborting!\n" >&2
exit 1
elif ! mkdir -p "$BATS_RUN_TMPDIR" ;then
printf "Error: Failed to create BATS_RUN_TMPDIR (%s)\n" "$BATS_RUN_TMPDIR" >&2
exit 1
fi
elif ! BATS_RUN_TMPDIR=$(mktemp -d "${BATS_TMPDIR}/bats-run-XXXXXX");then
printf "Error: Failed to create BATS_RUN_TMPDIR (%s) with mktemp\n" "${BATS_TMPDIR}/bats-run-XXXXXX" >&2
exit 1
fi
export BATS_WARNING_FILE="${BATS_RUN_TMPDIR}/warnings.log"
bats_exit_trap() {
if [[ -s "$BATS_WARNING_FILE" ]]; then
local pre_cat='' post_cat=''
if [[ $formatter == pretty ]]; then
pre_cat=$'\x1B[31m'
post_cat=$'\x1B[0m'
fi
printf "\nThe following warnings were encountered during tests:\n%s" "$pre_cat"
cat "$BATS_WARNING_FILE"
printf "%s" "$post_cat"
fi >&2
if [[ -n "$BATS_TEMPDIR_CLEANUP" ]]; then
rm -rf "$BATS_RUN_TMPDIR"
else
printf "BATS_RUN_TMPDIR: %s\n" "$BATS_RUN_TMPDIR" >&2
fi
}
trap bats_exit_trap EXIT
if [[ "$formatter" != "tap" ]]; then
flags+=('-x')
fi
if [[ -n "$report_formatter" && "$report_formatter" != "tap" ]]; then
flags+=('-x')
fi
if [[ "$formatter" == "junit" ]]; then
flags+=('-T')
formatter_flags+=('--base-path' "${arguments[0]}")
fi
if [[ "$report_formatter" == "junit" ]]; then
flags+=('-T')
report_formatter_flags+=('--base-path' "${arguments[0]}")
fi
if [[ "$formatter" == "pretty" ]]; then
formatter_flags+=('--base-path' "${arguments[0]}")
fi
# if we don't need to filter extended syntax, use the faster formatter
if [[ "$formatter" == tap && -z "$report_formatter" ]]; then
formatter="cat"
fi
bats_check_formatter() { # <formatter-path>
local -r formatter="$1"
if [[ ! -f "$formatter" ]]; then
printf "ERROR: Formatter '%s' is not readable!\n" "$formatter"
exit 1
elif [[ ! -x "$formatter" ]]; then
printf "ERROR: Formatter '%s' is not executable!\n" "$formatter"
exit 1
fi
}
if [[ $formatter == /* ]]; then # absolute paths are direct references to formatters
bats_check_formatter "$formatter"
interpolated_formatter="$formatter"
else
interpolated_formatter="bats-format-${formatter}"
fi
if [[ "${#arguments[@]}" -eq 0 ]]; then
abort 'Must specify at least one <test>'
fi
if [[ -n "$report_formatter" ]]; then
# default to the current directory for output
if [[ -z "$output" ]]; then
output=.
fi
# only set BATS_REPORT_FILENAME if none was given
if [[ -z "${BATS_REPORT_FILENAME:-}" ]]; then
case "$report_formatter" in
tap|tap13)
BATS_REPORT_FILE_NAME="report.tap"
;;
junit)
BATS_REPORT_FILE_NAME="report.xml"
;;
*)
BATS_REPORT_FILE_NAME="report.log"
;;
esac
fi
fi
if [[ $report_formatter == /* ]]; then # absolute paths are direct references to formatters
bats_check_formatter "$report_formatter"
interpolated_report_formatter="${report_formatter}"
else
interpolated_report_formatter="bats-format-${report_formatter}"
fi
if [[ "${BATS_CODE_QUOTE_STYLE-BATS_CODE_QUOTE_STYLE_UNSET}" == BATS_CODE_QUOTE_STYLE_UNSET ]]; then
BATS_CODE_QUOTE_STYLE="\`'"
fi
case "${BATS_CODE_QUOTE_STYLE}" in
??)
BATS_BEGIN_CODE_QUOTE="${BATS_CODE_QUOTE_STYLE::1}"
BATS_END_CODE_QUOTE="${BATS_CODE_QUOTE_STYLE:1:1}"
export BATS_BEGIN_CODE_QUOTE BATS_END_CODE_QUOTE
;;
custom)
if [[ ${BATS_BEGIN_CODE_QUOTE-BATS_BEGIN_CODE_QUOTE_UNSET} == BATS_BEGIN_CODE_QUOTE_UNSET
|| ${BATS_END_CODE_QUOTE-BATS_BEGIN_CODE_QUOTE_UNSET} == BATS_BEGIN_CODE_QUOTE_UNSET ]]; then
printf "ERROR: BATS_CODE_QUOTE_STYLE=custom requires BATS_BEGIN_CODE_QUOTE and BATS_END_CODE_QUOTE to be set\n" >&2
exit 1
fi
;;
*)
printf "ERROR: Unknown BATS_CODE_QUOTE_STYLE: %s\n" "$BATS_CODE_QUOTE_STYLE" >&2
exit 1
;;
esac
if [[ -n "$output" ]]; then
if [[ ! -w "${output}" ]]; then
abort "Output path ${output} is not writeable"
fi
export BATS_REPORT_OUTPUT_PATH="$output"
fi
if [[ -n "$setup_suite_file" && ! -f "$setup_suite_file" ]]; then
abort "--setup-suite-file $setup_suite_file does not exist!"
fi
filenames=()
for filename in "${arguments[@]}"; do
expand_path "$filename" 'filename'
if [[ -z "$setup_suite_file" ]]; then
if [[ -d "$filename" ]]; then
dirname="$filename"
else
dirname="${filename%/*}"
fi
potential_setup_suite_file="$dirname/setup_suite.bash"
if [[ -e "$potential_setup_suite_file" ]]; then
setup_suite_file="$potential_setup_suite_file"
fi
fi
if [[ -d "$filename" ]]; then
shopt -s nullglob
if [[ "$recursive" -eq 1 ]]; then
while IFS= read -r -d $'\0' file; do
filenames+=("$file")
done < <(find -L "$filename" -type f -name "*.${BATS_FILE_EXTENSION:-bats}" -print0 | sort -z)
else
for suite_filename in "$filename"/*."${BATS_FILE_EXTENSION:-bats}"; do
filenames+=("$suite_filename")
done
fi
shopt -u nullglob
else
filenames+=("$filename")
fi
done
if [[ -n "$setup_suite_file" ]]; then
flags+=("--setup-suite-file" "$setup_suite_file")
fi
# shellcheck source=lib/bats-core/validator.bash
source "$BATS_ROOT/lib/bats-core/validator.bash"
trap 'BATS_INTERRUPTED=true' INT # let the lower levels handle the interruption
set -o pipefail execfail
if [[ -n "$report_formatter" ]]; then
exec bats-exec-suite "${flags[@]}" "${filenames[@]}" | \
tee >("$interpolated_report_formatter" "${report_formatter_flags[@]}" >"${BATS_REPORT_OUTPUT_PATH}/${BATS_REPORT_FILE_NAME}") | \
bats_test_count_validator | \
"$interpolated_formatter" "${formatter_flags[@]}"
else
exec bats-exec-suite "${flags[@]}" "${filenames[@]}" | \
bats_test_count_validator | \
"$interpolated_formatter" "${formatter_flags[@]}"
fi

View File

@ -0,0 +1,338 @@
#!/usr/bin/env bash
set -eET
flags=('--dummy-flag')
num_jobs=${BATS_NUMBER_OF_PARALLEL_JOBS:-1}
extended_syntax=''
BATS_TRACE_LEVEL="${BATS_TRACE_LEVEL:-0}"
declare -r BATS_RETRY_RETURN_CODE=126
export BATS_TEST_RETRIES=0 # no retries by default
while [[ "$#" -ne 0 ]]; do
case "$1" in
-j)
shift
num_jobs="$1"
;;
-T)
flags+=('-T')
;;
-x)
flags+=('-x')
extended_syntax=1
;;
--no-parallelize-within-files)
# use singular to allow for users to override in file
BATS_NO_PARALLELIZE_WITHIN_FILE=1
;;
--dummy-flag)
;;
--trace)
flags+=('--trace')
;;
--print-output-on-failure)
flags+=(--print-output-on-failure)
;;
--show-output-of-passing-tests)
flags+=(--show-output-of-passing-tests)
;;
--verbose-run)
flags+=(--verbose-run)
;;
--gather-test-outputs-in)
shift
flags+=(--gather-test-outputs-in "$1")
;;
*)
break
;;
esac
shift
done
filename="$1"
TESTS_FILE="$2"
if [[ ! -f "$filename" ]]; then
printf 'Testfile "%s" not found\n' "$filename" >&2
exit 1
fi
export BATS_TEST_FILENAME="$filename"
# shellcheck source=lib/bats-core/preprocessing.bash
# shellcheck disable=SC2153
source "$BATS_ROOT/lib/bats-core/preprocessing.bash"
bats_run_setup_file() {
# shellcheck source=lib/bats-core/tracing.bash
# shellcheck disable=SC2153
source "$BATS_ROOT/lib/bats-core/tracing.bash"
# shellcheck source=lib/bats-core/test_functions.bash
# shellcheck disable=SC2153
source "$BATS_ROOT/lib/bats-core/test_functions.bash"
exec 3<&1
# these are defined only to avoid errors when referencing undefined variables down the line
# shellcheck disable=2034
BATS_TEST_NAME= # used in tracing.bash
# shellcheck disable=2034
BATS_TEST_COMPLETED= # used in tracing.bash
BATS_SOURCE_FILE_COMPLETED=
BATS_SETUP_FILE_COMPLETED=
BATS_TEARDOWN_FILE_COMPLETED=
# shellcheck disable=2034
BATS_ERROR_STATUS= # used in tracing.bash
touch "$BATS_OUT"
bats_setup_tracing
trap 'bats_file_teardown_trap' EXIT
local status=0
# get the setup_file/teardown_file functions for this file (if it has them)
# shellcheck disable=SC1090
source "$BATS_TEST_SOURCE" >>"$BATS_OUT" 2>&1
BATS_SOURCE_FILE_COMPLETED=1
setup_file >>"$BATS_OUT" 2>&1
BATS_SETUP_FILE_COMPLETED=1
}
bats_run_teardown_file() {
# avoid running the therdown trap due to errors in teardown_file
trap 'bats_file_exit_trap' EXIT
# rely on bats_error_trap to catch failures
teardown_file >>"$BATS_OUT" 2>&1
BATS_TEARDOWN_FILE_COMPLETED=1
}
bats_file_teardown_trap() {
bats_run_teardown_file
bats_file_exit_trap
}
# shellcheck source=lib/bats-core/common.bash
source "$BATS_ROOT/lib/bats-core/common.bash"
bats_file_exit_trap() {
trap - ERR EXIT
local failure_reason
local -i failure_test_index=$(( BATS_FILE_FIRST_TEST_NUMBER_IN_SUITE + 1 ))
if [[ -z "$BATS_SETUP_FILE_COMPLETED" || -z "$BATS_TEARDOWN_FILE_COMPLETED" ]]; then
if [[ -z "$BATS_SETUP_FILE_COMPLETED" ]]; then
failure_reason='setup_file'
elif [[ -z "$BATS_TEARDOWN_FILE_COMPLETED" ]]; then
failure_reason='teardown_file'
failure_test_index=$(( BATS_FILE_FIRST_TEST_NUMBER_IN_SUITE + ${#tests_to_run[@]} + 1 ))
elif [[ -z "$BATS_SOURCE_FILE_COMPLETED" ]]; then
failure_reason='source'
else
failure_reason='unknown internal'
fi
printf "not ok %d %s\n" "$failure_test_index" "$failure_reason failed" >&3
local stack_trace
bats_get_failure_stack_trace stack_trace
bats_print_stack_trace "${stack_trace[@]}" >&3
bats_print_failed_command "${stack_trace[@]}" >&3
bats_prefix_lines_for_tap_output < "$BATS_OUT" | bats_replace_filename >&3
rm -rf "$BATS_OUT"
bats_exec_file_status=1
fi
exit $bats_exec_file_status
}
function setup_file() {
return 0
}
function teardown_file() {
return 0
}
bats_forward_output_of_parallel_test() {
local test_number_in_suite=$1
local status=0
wait "$(cat "$output_folder/$test_number_in_suite/pid")" || status=1
cat "$output_folder/$test_number_in_suite/stdout"
cat "$output_folder/$test_number_in_suite/stderr" >&2
return $status
}
bats_is_next_parallel_test_finished() {
local PID
# get the pid of the next potentially finished test
PID=$(cat "$output_folder/$(( test_number_in_suite_of_last_finished_test + 1 ))/pid")
# try to send a signal to this process
# if it fails, the process exited,
# if it succeeds, the process is still running
if kill -0 "$PID" 2>/dev/null; then
return 1
fi
}
# prints output from all tests in the order they were started
# $1 == "blocking": wait for a test to finish before printing
# != "blocking": abort printing, when a test has not finished
bats_forward_output_for_parallel_tests() {
local status=0
# was the next test already started?
while (( test_number_in_suite_of_last_finished_test + 1 <= test_number_in_suite )); do
# if we are okay with waiting or if the test has already been finished
if [[ "$1" == "blocking" ]] || bats_is_next_parallel_test_finished ; then
(( ++test_number_in_suite_of_last_finished_test ))
bats_forward_output_of_parallel_test "$test_number_in_suite_of_last_finished_test" || status=$?
else
# non-blocking and the process has not finished -> abort the printing
break
fi
done
return $status
}
bats_run_test_with_retries() { # <args>
local status=0
local should_try_again=1 try_number
for ((try_number=1; should_try_again; ++try_number)); do
if "$BATS_LIBEXEC/bats-exec-test" "$@" "$try_number"; then
should_try_again=0
else
status=$?
if ((status == BATS_RETRY_RETURN_CODE)); then
should_try_again=1
else
should_try_again=0
bats_exec_file_status=$status
fi
fi
done
return $status
}
bats_run_tests_in_parallel() {
local output_folder="$BATS_RUN_TMPDIR/parallel_output"
local status=0
mkdir -p "$output_folder"
# shellcheck source=lib/bats-core/semaphore.bash
source "$BATS_ROOT/lib/bats-core/semaphore.bash"
bats_semaphore_setup
# the test_number_in_file is not yet incremented -> one before the next test to run
local test_number_in_suite_of_last_finished_test="$BATS_FILE_FIRST_TEST_NUMBER_IN_SUITE" # stores which test was printed last
local test_number_in_file=0 test_number_in_suite=$BATS_FILE_FIRST_TEST_NUMBER_IN_SUITE
for test_name in "${tests_to_run[@]}"; do
# Only handle non-empty lines
if [[ $test_name ]]; then
((++test_number_in_suite))
((++test_number_in_file))
mkdir -p "$output_folder/$test_number_in_suite"
bats_semaphore_run "$output_folder/$test_number_in_suite" \
bats_run_test_with_retries "${flags[@]}" "$filename" "$test_name" "$test_number_in_suite" "$test_number_in_file" \
> "$output_folder/$test_number_in_suite/pid"
fi
# print results early to get interactive feedback
bats_forward_output_for_parallel_tests non-blocking || status=1 # ignore if we did not finish yet
done
bats_forward_output_for_parallel_tests blocking || status=1
return $status
}
bats_read_tests_list_file() {
local line_number=0
tests_to_run=()
# the global test number must be visible to traps -> not local
local test_number_in_suite=''
while read -r test_line; do
# check if the line begins with filename
# filename might contain some hard to parse characters,
# use simple string operations to work around that issue
if [[ "$filename" == "${test_line::${#filename}}" ]]; then
# get the rest of the line without the separator \t
test_name=${test_line:$((1 + ${#filename} ))}
tests_to_run+=("$test_name")
# save the first test's number for later iteration
# this assumes that tests for a file are stored consecutive in the file!
if [[ -z "$test_number_in_suite" ]]; then
test_number_in_suite=$line_number
fi
fi
((++line_number))
done <"$TESTS_FILE"
BATS_FILE_FIRST_TEST_NUMBER_IN_SUITE="$test_number_in_suite"
declare -ri BATS_FILE_FIRST_TEST_NUMBER_IN_SUITE # mark readonly (cannot merge assignment, because value would be lost)
}
bats_run_tests() {
bats_exec_file_status=0
if [[ "$num_jobs" != 1 && "${BATS_NO_PARALLELIZE_WITHIN_FILE-False}" == False ]]; then
export BATS_SEMAPHORE_NUMBER_OF_SLOTS="$num_jobs"
bats_run_tests_in_parallel "$BATS_RUN_TMPDIR/parallel_output" || bats_exec_file_status=1
else
local test_number_in_suite=$BATS_FILE_FIRST_TEST_NUMBER_IN_SUITE \
test_number_in_file=0
for test_name in "${tests_to_run[@]}"; do
if [[ "${BATS_INTERRUPTED-NOTSET}" != NOTSET ]]; then
bats_exec_file_status=130 # bash's code for SIGINT exits
break
fi
# Only handle non-empty lines
if [[ $test_name ]]; then
((++test_number_in_suite))
((++test_number_in_file))
bats_run_test_with_retries "${flags[@]}" "$filename" "$test_name" \
"$test_number_in_suite" "$test_number_in_file" || bats_exec_file_status=$?
fi
done
fi
}
bats_create_file_tempdirs() {
local bats_files_tmpdir="${BATS_RUN_TMPDIR}/file"
if ! mkdir -p "$bats_files_tmpdir"; then
printf 'Failed to create %s\n' "$bats_files_tmpdir" >&2
exit 1
fi
BATS_FILE_TMPDIR="$bats_files_tmpdir/${BATS_FILE_FIRST_TEST_NUMBER_IN_SUITE?}"
if ! mkdir "$BATS_FILE_TMPDIR"; then
printf 'Failed to create BATS_FILE_TMPDIR=%s\n' "$BATS_FILE_TMPDIR" >&2
exit 1
fi
ln -s "$BATS_TEST_FILENAME" "$BATS_FILE_TMPDIR-$(basename "$BATS_TEST_FILENAME").source_file"
export BATS_FILE_TMPDIR
}
trap 'BATS_INTERRUPTED=true' INT
if [[ -n "$extended_syntax" ]]; then
printf "suite %s\n" "$filename"
fi
BATS_FILE_FIRST_TEST_NUMBER_IN_SUITE=0 # predeclare as Bash 3.2 does not support declare -g
bats_read_tests_list_file
# don't run potentially expensive setup/teardown_file
# when there are no tests to run
if [[ ${#tests_to_run[@]} -eq 0 ]]; then
exit 0
fi
# requires the test list to be read but not empty
bats_create_file_tempdirs
bats_preprocess_source "$filename"
trap bats_interrupt_trap INT
bats_run_setup_file
# during tests, we don't want to get backtraces from this level
# just wait for the test to be interrupted and display their trace
trap 'BATS_INTERRUPTED=true' INT
bats_run_tests
trap bats_interrupt_trap INT
bats_run_teardown_file
exit $bats_exec_file_status

View File

@ -0,0 +1,359 @@
#!/usr/bin/env bash
set -e
count_only_flag=''
filter=''
num_jobs=${BATS_NUMBER_OF_PARALLEL_JOBS:-1}
bats_no_parallelize_across_files=${BATS_NO_PARALLELIZE_ACROSS_FILES-}
bats_no_parallelize_within_files=
filter_status=''
flags=('--dummy-flag') # add a dummy flag to prevent unset variable errors on empty array expansion in old bash versions
setup_suite_file=''
BATS_TRACE_LEVEL="${BATS_TRACE_LEVEL:-0}"
abort() {
printf 'Error: %s\n' "$1" >&2
exit 1
}
while [[ "$#" -ne 0 ]]; do
case "$1" in
-c)
count_only_flag=1
;;
-f)
shift
filter="$1"
;;
-j)
shift
num_jobs="$1"
flags+=('-j' "$num_jobs")
;;
-T)
flags+=('-T')
;;
-x)
flags+=('-x')
;;
--no-parallelize-across-files)
bats_no_parallelize_across_files=1
;;
--no-parallelize-within-files)
bats_no_parallelize_within_files=1
flags+=("--no-parallelize-within-files")
;;
--filter-status)
shift
filter_status="$1"
;;
--dummy-flag)
;;
--trace)
flags+=('--trace')
(( ++BATS_TRACE_LEVEL )) # avoid returning 0
;;
--print-output-on-failure)
flags+=(--print-output-on-failure)
;;
--show-output-of-passing-tests)
flags+=(--show-output-of-passing-tests)
;;
--verbose-run)
flags+=(--verbose-run)
;;
--gather-test-outputs-in)
shift
flags+=(--gather-test-outputs-in "$1")
;;
--setup-suite-file)
shift
setup_suite_file="$1"
;;
*)
break
;;
esac
shift
done
if [[ "$num_jobs" != 1 ]]; then
if ! type -p parallel >/dev/null && [[ -z "$bats_no_parallelize_across_files" ]]; then
abort "Cannot execute \"${num_jobs}\" jobs without GNU parallel"
exit 1
fi
# shellcheck source=lib/bats-core/semaphore.bash
source "${BATS_ROOT}/lib/bats-core/semaphore.bash"
bats_semaphore_setup
fi
# create a file that contains all (filtered) tests to run from all files
TESTS_LIST_FILE="${BATS_RUN_TMPDIR}/test_list_file.txt"
bats_gather_tests() {
all_tests=()
for filename in "$@"; do
if [[ ! -f "$filename" ]]; then
abort "Test file \"${filename}\" does not exist"
fi
test_names=()
test_dupes=()
while read -r line; do
if [[ ! "$line" =~ ^bats_test_function\ ]]; then
continue
fi
line="${line%$'\r'}"
line="${line#* }"
test_line=$(printf "%s\t%s" "$filename" "$line")
all_tests+=("$test_line")
printf "%s\n" "$test_line" >>"$TESTS_LIST_FILE"
# avoid unbound variable errors on empty array expansion with old bash versions
if [[ ${#test_names[@]} -gt 0 && " ${test_names[*]} " == *" $line "* ]]; then
test_dupes+=("$line")
continue
fi
test_names+=("$line")
done < <(BATS_TEST_FILTER="$filter" bats-preprocess "$filename")
if [[ "${#test_dupes[@]}" -ne 0 ]]; then
abort "Duplicate test name(s) in file \"${filename}\": ${test_dupes[*]}"
fi
done
test_count="${#all_tests[@]}"
}
TEST_ROOT=${1-}
TEST_ROOT=${TEST_ROOT%/*}
BATS_RUN_LOGS_DIRECTORY="$TEST_ROOT/.bats/run-logs"
if [[ ! -d "$BATS_RUN_LOGS_DIRECTORY" ]]; then
if [[ -n "$filter_status" ]]; then
printf "Error: --filter-status needs '%s/' to save failed tests. Please create this folder, add it to .gitignore and try again.\n" "$BATS_RUN_LOGS_DIRECTORY"
exit 1
else
BATS_RUN_LOGS_DIRECTORY=
fi
# discard via sink instead of having a conditional later
export BATS_RUNLOG_FILE='/dev/null'
else
# use UTC (-u) to avoid problems with TZ changes
BATS_RUNLOG_DATE=$(date -u '+%Y-%m-%d %H:%M:%S UTC')
export BATS_RUNLOG_FILE="$BATS_RUN_LOGS_DIRECTORY/${BATS_RUNLOG_DATE}.log"
fi
bats_gather_tests "$@"
if [[ -n "$filter_status" ]]; then
# shellcheck source=lib/bats-core/common.bash
source "$BATS_ROOT/lib/bats-core/common.bash"
case "$filter_status" in
failed)
bats_filter_test_by_status() { # <line>
! bats_binary_search "$1" "passed_tests"
}
;;
passed)
bats_filter_test_by_status() {
! bats_binary_search "$1" "failed_tests"
}
;;
missed)
bats_filter_test_by_status() {
! bats_binary_search "$1" "failed_tests" && ! bats_binary_search "$1" "passed_tests"
}
;;
*)
printf "Error: Unknown value '%s' for --filter-status. Valid values are 'failed' and 'missed'.\n" "$filter_status">&2
exit 1
;;
esac
if IFS='' read -d $'\n' -r BATS_PREVIOUS_RUNLOG_FILE < <(ls -1r "$BATS_RUN_LOGS_DIRECTORY"); then
BATS_PREVIOUS_RUNLOG_FILE="$BATS_RUN_LOGS_DIRECTORY/$BATS_PREVIOUS_RUNLOG_FILE"
if [[ $BATS_PREVIOUS_RUNLOG_FILE == "$BATS_RUNLOG_FILE" ]]; then
count=$(find "$BATS_RUN_LOGS_DIRECTORY" -name "$BATS_RUNLOG_DATE*" | wc -l)
BATS_RUNLOG_FILE="$BATS_RUN_LOGS_DIRECTORY/${BATS_RUNLOG_DATE}-$count.log"
fi
failed_tests=()
passed_tests=()
# store tests that were already filtered out in the last run for the same filter reason
last_filtered_tests=()
i=0
while read -rd $'\n' line; do
((++i))
case "$line" in
"passed "*)
passed_tests+=("${line#passed }")
;;
"failed "*)
failed_tests+=("${line#failed }")
;;
"status-filtered $filter_status"*) # pick up tests that were filtered in the last round for the same status
last_filtered_tests+=("${line#status-filtered "$filter_status" }")
;;
"status-filtered "*) # ignore other status-filtered lines
;;
"#"*) # allow for comments
;;
*)
printf "Error: %s:%d: Invalid format: %s\n" "$BATS_PREVIOUS_RUNLOG_FILE" "$i" "$line" >&2
exit 1
;;
esac
done < <(sort "$BATS_PREVIOUS_RUNLOG_FILE")
filtered_tests=()
for line in "${all_tests[@]}"; do
if bats_filter_test_by_status "$line" && ! bats_binary_search "$line" last_filtered_tests; then
printf "%s\n" "$line"
filtered_tests+=("$line")
else
printf "status-filtered %s %s\n" "$filter_status" "$line" >> "$BATS_RUNLOG_FILE"
fi
done > "$TESTS_LIST_FILE"
# save filtered tests to exclude them again in next round
for test_line in "${last_filtered_tests[@]}"; do
printf "status-filtered %s %s\n" "$filter_status" "$test_line"
done >> "$BATS_RUNLOG_FILE"
test_count="${#filtered_tests[@]}"
if [[ ${#failed_tests[@]} -eq 0 && ${#filtered_tests[@]} -eq 0 ]]; then
printf "There where no failed tests in the last recorded run.\n" >&2
fi
else
printf "No recording of previous runs found. Running all tests!\n" >&2
fi
fi
if [[ -n "$count_only_flag" ]]; then
printf '%d\n' "${test_count}"
exit
fi
if [[ -n "$bats_no_parallelize_across_files" ]] && [[ ! "$num_jobs" -gt 1 ]]; then
abort "The flag --no-parallelize-across-files requires at least --jobs 2"
exit 1
fi
if [[ -n "$bats_no_parallelize_within_files" ]] && [[ ! "$num_jobs" -gt 1 ]]; then
abort "The flag --no-parallelize-across-files requires at least --jobs 2"
exit 1
fi
# only abort on the lowest levels
trap 'BATS_INTERRUPTED=true' INT
bats_exec_suite_status=0
printf '1..%d\n' "${test_count}"
# No point on continuing if there's no tests.
if [[ "${test_count}" == 0 ]]; then
exit
fi
export BATS_SUITE_TMPDIR="${BATS_RUN_TMPDIR}/suite"
if ! mkdir "$BATS_SUITE_TMPDIR"; then
printf '%s\n' "Failed to create BATS_SUITE_TMPDIR" >&2
exit 1
fi
# Deduplicate filenames (without reordering) to avoid running duplicate tests n by n times.
# (see https://github.com/bats-core/bats-core/issues/329)
# If a file was specified multiple times, we already got it repeatedly in our TESTS_LIST_FILE.
# Thus, it suffices to bats-exec-file it once to run all repeated tests on it.
IFS=$'\n' read -d '' -r -a BATS_UNIQUE_TEST_FILENAMES < <(printf "%s\n" "$@"| nl | sort -k 2 | uniq -f 1 | sort -n | cut -f 2-) || true
# shellcheck source=lib/bats-core/tracing.bash
source "$BATS_ROOT/lib/bats-core/tracing.bash"
bats_setup_tracing
trap bats_suite_exit_trap EXIT
bats_suite_exit_trap() {
if [[ -z "${BATS_SETUP_SUITE_COMPLETED}" || -z "${BATS_TEARDOWN_SUITE_COMPLETED}" ]]; then
if [[ -z "${BATS_SETUP_SUITE_COMPLETED}" ]]; then
printf "not ok 1 setup_suite\n"
elif [[ -z "${BATS_TEARDOWN_SUITE_COMPLETED}" ]]; then
printf "not ok %d teardown_suite\n" $((test_count+1))
fi
local stack_trace
bats_get_failure_stack_trace stack_trace
bats_print_stack_trace "${stack_trace[@]}"
bats_print_failed_command "${stack_trace[@]}"
bats_exec_suite_status=1
fi
if [[ ${BATS_INTERRUPTED-NOTSET} != NOTSET ]]; then
printf "\n# Received SIGINT, aborting ...\n\n"
fi
if [[ -d "$BATS_RUN_LOGS_DIRECTORY" && -n "${BATS_INTERRUPTED:-}" ]]; then
# aborting a test run with CTRL+C does not save the runlog file
rm "$BATS_RUNLOG_FILE"
fi
exit "$bats_exec_suite_status"
}
bats_run_teardown_suite() {
# avoid being called twice, in case this is not called through bats_teardown_suite_trap
# but from the end of file
trap bats_suite_exit_trap EXIT
set -eET
BATS_TEARDOWN_SUITE_COMPLETED=
teardown_suite 2>&1
BATS_TEARDOWN_SUITE_COMPLETED=1
set +ET
}
bats_teardown_suite_trap() {
bats_run_teardown_suite
bats_suite_exit_trap
}
setup_suite() {
:
}
teardown_suite() {
:
}
trap bats_teardown_suite_trap EXIT
if [[ -n "$setup_suite_file" ]]; then
setup_suite() {
printf "%s does not define \`setup_suite()\`\n" "$setup_suite_file" >&2
exit 1
}
# shellcheck disable=SC1090
source "$setup_suite_file"
fi
set -eET
BATS_SETUP_SUITE_COMPLETED=
setup_suite 2>&1
BATS_SETUP_SUITE_COMPLETED=1
set +ET
if [[ "$num_jobs" -gt 1 ]] && [[ -z "$bats_no_parallelize_across_files" ]]; then
# run files in parallel to get the maximum pool of parallel tasks
# shellcheck disable=SC2086,SC2068
# we need to handle the quoting of ${flags[@]} ourselves,
# because parallel can only quote it as one
parallel --keep-order --jobs "$num_jobs" bats-exec-file "$(printf "%q " "${flags[@]}")" "{}" "$TESTS_LIST_FILE" ::: "${BATS_UNIQUE_TEST_FILENAMES[@]}" 2>&1 || bats_exec_suite_status=1
else
for filename in "${BATS_UNIQUE_TEST_FILENAMES[@]}"; do
if [[ "${BATS_INTERRUPTED-NOTSET}" != NOTSET ]]; then
bats_exec_suite_status=130 # bash's code for SIGINT exits
break
fi
bats-exec-file "${flags[@]}" "$filename" "${TESTS_LIST_FILE}" || bats_exec_suite_status=1
done
fi
set -eET
bats_run_teardown_suite
exit "$bats_exec_suite_status"

View File

@ -0,0 +1,231 @@
#!/usr/bin/env bash
set -eET
# Variables used in other scripts.
BATS_ENABLE_TIMING=''
BATS_EXTENDED_SYNTAX=''
BATS_TRACE_LEVEL="${BATS_TRACE_LEVEL:-0}"
BATS_PRINT_OUTPUT_ON_FAILURE="${BATS_PRINT_OUTPUT_ON_FAILURE:-}"
BATS_SHOW_OUTPUT_OF_SUCCEEDING_TESTS="${BATS_SHOW_OUTPUT_OF_SUCCEEDING_TESTS:-}"
BATS_VERBOSE_RUN="${BATS_VERBOSE_RUN:-}"
BATS_GATHER_TEST_OUTPUTS_IN="${BATS_GATHER_TEST_OUTPUTS_IN:-}"
BATS_TEST_NAME_PREFIX="${BATS_TEST_NAME_PREFIX:-}"
while [[ "$#" -ne 0 ]]; do
case "$1" in
-T)
BATS_ENABLE_TIMING='-T'
;;
-x)
# shellcheck disable=SC2034
BATS_EXTENDED_SYNTAX='-x'
;;
--dummy-flag)
;;
--trace)
(( ++BATS_TRACE_LEVEL )) # avoid returning 0
;;
--print-output-on-failure)
BATS_PRINT_OUTPUT_ON_FAILURE=1
;;
--show-output-of-passing-tests)
BATS_SHOW_OUTPUT_OF_SUCCEEDING_TESTS=1
;;
--verbose-run)
BATS_VERBOSE_RUN=1
;;
--gather-test-outputs-in)
shift
BATS_GATHER_TEST_OUTPUTS_IN="$1"
;;
*)
break
;;
esac
shift
done
export BATS_TEST_FILENAME="$1"
export BATS_TEST_NAME="$2"
export BATS_SUITE_TEST_NUMBER="$3"
export BATS_TEST_NUMBER="$4"
BATS_TEST_TRY_NUMBER="$5"
if [[ -z "$BATS_TEST_FILENAME" ]]; then
printf 'usage: bats-exec-test <filename>\n' >&2
exit 1
elif [[ ! -f "$BATS_TEST_FILENAME" ]]; then
printf 'bats: %s does not exist\n' "$BATS_TEST_FILENAME" >&2
exit 1
fi
bats_create_test_tmpdirs() {
local tests_tmpdir="${BATS_RUN_TMPDIR}/test"
if ! mkdir -p "$tests_tmpdir"; then
printf 'Failed to create: %s\n' "$tests_tmpdir" >&2
exit 1
fi
BATS_TEST_TMPDIR="$tests_tmpdir/$BATS_SUITE_TEST_NUMBER"
if ! mkdir "$BATS_TEST_TMPDIR"; then
printf 'Failed to create BATS_TEST_TMPDIR%d: %s\n' "$BATS_TEST_TRY_NUMBER" "$BATS_TEST_TMPDIR" >&2
exit 1
fi
printf "%s\n" "$BATS_TEST_NAME" > "$BATS_TEST_TMPDIR.name"
export BATS_TEST_TMPDIR
}
# load the test helper functions like `load` or `run` that are needed to run a (preprocessed) .bats file without bash errors
# shellcheck source=lib/bats-core/test_functions.bash disable=SC2153
source "$BATS_ROOT/lib/bats-core/test_functions.bash"
# shellcheck source=lib/bats-core/tracing.bash disable=SC2153
source "$BATS_ROOT/lib/bats-core/tracing.bash"
bats_teardown_trap() {
bats_check_status_from_trap
local bats_teardown_trap_status=0
# mark the start of this function to distinguish where skip is called
# parameter 1 will signify the reason why this function was called
# this is used to identify when this is called as exit trap function
BATS_TEARDOWN_STARTED=${1:-1}
teardown >>"$BATS_OUT" 2>&1 || bats_teardown_trap_status="$?"
if [[ $bats_teardown_trap_status -eq 0 ]]; then
BATS_TEARDOWN_COMPLETED=1
elif [[ -n "$BATS_TEST_COMPLETED" ]]; then
BATS_DEBUG_LAST_STACK_TRACE_IS_VALID=1
BATS_ERROR_STATUS="$bats_teardown_trap_status"
fi
bats_exit_trap
}
# shellcheck source=lib/bats-core/common.bash
source "$BATS_ROOT/lib/bats-core/common.bash"
bats_exit_trap() {
local status
local skipped=''
trap - ERR EXIT
if [[ -n "$BATS_TEST_SKIPPED" ]]; then
skipped=' # skip'
if [[ "$BATS_TEST_SKIPPED" != '1' ]]; then
skipped+=" $BATS_TEST_SKIPPED"
fi
fi
BATS_TEST_TIME=''
if [[ -z "${skipped}" && -n "$BATS_ENABLE_TIMING" ]]; then
BATS_TEST_TIME=" in "$(( $(get_mills_since_epoch) - BATS_TEST_START_TIME ))"ms"
fi
local print_bats_out="${BATS_SHOW_OUTPUT_OF_SUCCEEDING_TESTS}"
local should_retry=''
if [[ -z "$BATS_TEST_COMPLETED" || -z "$BATS_TEARDOWN_COMPLETED" || "${BATS_INTERRUPTED-NOTSET}" != NOTSET ]]; then
if [[ "$BATS_ERROR_STATUS" -eq 0 ]]; then
# For some versions of bash, `$?` may not be set properly for some error
# conditions before triggering the EXIT trap directly (see #72 and #81).
# Thanks to the `BATS_TEARDOWN_COMPLETED` signal, this will pinpoint such
# errors if they happen during `teardown()` when `bats_perform_test` calls
# `bats_teardown_trap` directly after the test itself passes.
#
# If instead the test fails, and the `teardown()` error happens while
# `bats_teardown_trap` runs as the EXIT trap, the test will fail with no
# output, since there's no way to reach the `bats_exit_trap` call.
BATS_ERROR_STATUS=1
fi
if bats_should_retry_test; then
should_retry=1
status=126 # signify retry
rm -r "$BATS_TEST_TMPDIR" # clean up for retry
else
printf 'not ok %d %s\n' "$BATS_SUITE_TEST_NUMBER" "${BATS_TEST_NAME_PREFIX:-}${BATS_TEST_DESCRIPTION}${BATS_TEST_TIME}" >&3
local stack_trace
bats_get_failure_stack_trace stack_trace
bats_print_stack_trace "${stack_trace[@]}" >&3
bats_print_failed_command "${stack_trace[@]}" >&3
if [[ $BATS_PRINT_OUTPUT_ON_FAILURE && -n "${output:-}" ]]; then
printf "Last output:\n%s\n" "$output" >> "$BATS_OUT"
fi
print_bats_out=1
status=1
local state=failed
fi
else
printf 'ok %d %s%s\n' "$BATS_SUITE_TEST_NUMBER" "${BATS_TEST_NAME_PREFIX:-}${BATS_TEST_DESCRIPTION}${BATS_TEST_TIME}" \
"$skipped" >&3
status=0
local state=passed
fi
if [[ -z "$should_retry" ]]; then
printf "%s %s\t%s\n" "$state" "$BATS_TEST_FILENAME" "$BATS_TEST_NAME" >> "$BATS_RUNLOG_FILE"
if [[ $print_bats_out ]]; then
bats_prefix_lines_for_tap_output < "$BATS_OUT" | bats_replace_filename >&3
fi
fi
if [[ $BATS_GATHER_TEST_OUTPUTS_IN ]]; then
local try_suffix=
if [[ -n "$should_retry" ]]; then
try_suffix="-try$BATS_TEST_TRY_NUMBER"
fi
cp "$BATS_OUT" "$BATS_GATHER_TEST_OUTPUTS_IN/$BATS_SUITE_TEST_NUMBER$try_suffix-$BATS_TEST_DESCRIPTION.log"
fi
rm -f "$BATS_OUT"
exit "$status"
}
get_mills_since_epoch() {
local ms_since_epoch
ms_since_epoch=$(date +%s%N)
if [[ "$ms_since_epoch" == *N || "${#ms_since_epoch}" -lt 19 ]]; then
ms_since_epoch=$(( $(date +%s) * 1000 ))
else
ms_since_epoch=$(( ms_since_epoch / 1000000 ))
fi
printf "%d\n" "$ms_since_epoch"
}
bats_perform_test() {
if ! declare -F "$BATS_TEST_NAME" &>/dev/null; then
local quoted_test_name
bats_quote_code quoted_test_name "$BATS_TEST_NAME"
printf "bats: unknown test name %s\n" "$quoted_test_name" >&2
exit 1
fi
BATS_TEST_COMPLETED=
BATS_TEST_SKIPPED=
BATS_TEARDOWN_COMPLETED=
BATS_ERROR_STATUS=
bats_setup_tracing
# mark this call as trap call
trap 'bats_teardown_trap as-exit-trap' EXIT
BATS_TEST_START_TIME=$(get_mills_since_epoch)
"$BATS_TEST_NAME" >>"$BATS_OUT" 2>&1 4>&1
BATS_TEST_COMPLETED=1
trap 'bats_exit_trap' EXIT
bats_teardown_trap "" # pass empty parameter to signify call outside trap
}
trap bats_interrupt_trap INT
# shellcheck source=lib/bats-core/preprocessing.bash
source "$BATS_ROOT/lib/bats-core/preprocessing.bash"
exec 3<&1
bats_create_test_tmpdirs
# Run the given test.
bats_evaluate_preprocessed_source
bats_perform_test

View File

@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -e
trap '' INT
cat

View File

@ -0,0 +1,251 @@
#!/usr/bin/env bash
set -euo pipefail
# shellcheck source=lib/bats-core/formatter.bash
source "$BATS_ROOT/lib/bats-core/formatter.bash"
BASE_PATH=.
while [[ "$#" -ne 0 ]]; do
case "$1" in
--base-path)
shift
normalize_base_path BASE_PATH "$1"
;;
esac
shift
done
init_suite() {
suite_test_exec_time=0
# since we have to print the suite header before its contents but we don't know the contents before the header,
# we have to buffer the contents
_suite_buffer=""
test_result_state="" # declare for the first flush, when no test has been encountered
}
_buffer_log=
init_file() {
file_count=0
file_failures=0
file_skipped=0
file_exec_time=0
test_exec_time=0
_buffer=""
_buffer_log=""
_system_out_log=""
test_result_state="" # mark that no test has run in this file so far
}
host() {
local hostname="${HOST:-}"
[[ -z "$hostname" ]] && hostname="${HOSTNAME:-}"
[[ -z "$hostname" ]] && hostname="$(uname -n)"
[[ -z "$hostname" ]] && hostname="$(hostname -f)"
echo "$hostname"
}
# convert $1 (time in milliseconds) to seconds
milliseconds_to_seconds() {
# we cannot rely on having bc for this calculation
full_seconds=$(($1 / 1000))
remaining_milliseconds=$(($1 % 1000))
if [[ $remaining_milliseconds -eq 0 ]]; then
printf "%d" "$full_seconds"
else
printf "%d.%03d" "$full_seconds" "$remaining_milliseconds"
fi
}
suite_header() {
printf "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<testsuites time=\"%s\">\n" "$(milliseconds_to_seconds "${suite_test_exec_time}")"
}
file_header() {
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%S")
printf "<testsuite name=\"%s\" tests=\"%s\" failures=\"%s\" errors=\"0\" skipped=\"%s\" time=\"%s\" timestamp=\"%s\" hostname=\"%s\">\n" \
"$(xml_escape "${class}")" "${file_count}" "${file_failures}" "${file_skipped}" "$(milliseconds_to_seconds "${file_exec_time}")" "${timestamp}" "$(host)"
}
file_footer() {
printf "</testsuite>\n"
}
suite_footer() {
printf "</testsuites>\n"
}
print_test_case() {
if [[ "$test_result_state" == ok && -z "$_system_out_log" && -z "$_buffer_log" ]]; then
# pass and no output can be shortened
printf " <testcase classname=\"%s\" name=\"%s\" time=\"%s\" />\n" "$(xml_escape "${class}")" "$(xml_escape "${name}")" "$(milliseconds_to_seconds "${test_exec_time}")"
else
printf " <testcase classname=\"%s\" name=\"%s\" time=\"%s\">\n" "$(xml_escape "${class}")" "$(xml_escape "${name}")" "$(milliseconds_to_seconds "${test_exec_time}")"
if [[ -n "$_system_out_log" ]]; then
printf " <system-out>%s</system-out>\n" "$(xml_escape "${_system_out_log}")"
fi
if [[ -n "$_buffer_log" || "$test_result_state" == not_ok ]]; then
printf " <failure type=\"failure\">%s</failure>\n" "$(xml_escape "${_buffer_log}")"
fi
if [[ "$test_result_state" == skipped ]]; then
printf " <skipped>%s</skipped>\n" "$(xml_escape "$test_skip_message")"
fi
printf " </testcase>\n"
fi
}
xml_escape() {
output=${1//&/&amp;}
output=${output//</&lt;}
output=${output//>/&gt;}
output=${output//'"'/&quot;}
output=${output//\'/&#39;}
local CONTROL_CHAR=$'\033'
output="${output//$CONTROL_CHAR/&#27;}"
printf "%s" "$output"
}
suite_buffer() {
local output
output="$("$@"; printf "x")" # use x marker to avoid losing trailing newlines
_suite_buffer="${_suite_buffer}${output%x}"
}
suite_flush() {
echo -n "${_suite_buffer}"
_suite_buffer=""
}
buffer() {
local output
output="$("$@"; printf "x")" # use x marker to avoid losing trailing newlines
_buffer="${_buffer}${output%x}"
}
flush() {
echo -n "${_buffer}"
_buffer=""
}
log() {
if [[ -n "$_buffer_log" ]]; then
_buffer_log="${_buffer_log}
$1"
else
_buffer_log="$1"
fi
}
flush_log() {
if [[ -n "$test_result_state" ]]; then
buffer print_test_case
fi
_buffer_log=""
_system_out_log=""
}
log_system_out() {
if [[ -n "$_system_out_log" ]]; then
_system_out_log="${_system_out_log}
$1"
else
_system_out_log="$1"
fi
}
finish_file() {
if [[ "${class-JUNIT_FORMATTER_NO_FILE_ENCOUNTERED}" != JUNIT_FORMATTER_NO_FILE_ENCOUNTERED ]]; then
file_header
printf "%s\n" "${_buffer}"
file_footer
fi
}
finish_suite() {
flush_log
suite_header
suite_flush
finish_file # must come after suite flush to not print the last file before the others
suite_footer
}
bats_tap_stream_plan() { # <number of tests>
:
}
init_suite
trap finish_suite EXIT
trap '' INT
bats_tap_stream_begin() { # <test index> <test name>
flush_log
# set after flushing to avoid overriding name of test
name="$2"
}
bats_tap_stream_ok() { # [--duration <milliseconds] <test index> <test name>
if [[ "$1" == "--duration" ]]; then
test_exec_time="${BASH_REMATCH[1]}"
else
test_exec_time=0
fi
((file_count += 1))
test_result_state='ok'
file_exec_time="$((file_exec_time + test_exec_time))"
suite_test_exec_time=$((suite_test_exec_time + test_exec_time))
}
bats_tap_stream_skipped() { # <test index> <test name> <skip reason>
((file_count += 1))
((file_skipped += 1))
test_result_state='skipped'
test_exec_time=0
test_skip_message="$3"
}
bats_tap_stream_not_ok() { # [--duration <milliseconds>] <test index> <test name>
((file_count += 1))
((file_failures += 1))
if [[ "$1" == "--duration" ]]; then
test_exec_time="${BASH_REMATCH[1]}"
else
test_exec_time=0
fi
test_result_state=not_ok
file_exec_time="$((file_exec_time + test_exec_time))"
suite_test_exec_time=$((suite_test_exec_time + test_exec_time))
}
bats_tap_stream_comment() { # <comment text without leading '# '> <scope>
local comment="$1" scope="$2"
case "$scope" in
begin)
# everything that happens between begin and [not] ok is FD3 output from the test
log_system_out "$comment"
;;
ok)
# non failed tests can produce FD3 output
log_system_out "$comment"
;;
*)
# everything else is considered error output
log "$1"
;;
esac
}
bats_tap_stream_suite() { # <file name>
flush_log
suite_buffer finish_file
init_file
class="${1/$BASE_PATH}"
}
bats_tap_stream_unknown() { # <full line>
:
}
bats_parse_internal_extended_tap

View File

@ -0,0 +1,328 @@
#!/usr/bin/env bash
set -e
# shellcheck source=lib/bats-core/formatter.bash
source "$BATS_ROOT/lib/bats-core/formatter.bash"
BASE_PATH=.
BATS_ENABLE_TIMING=
while [[ "$#" -ne 0 ]]; do
case "$1" in
-T)
BATS_ENABLE_TIMING="-T"
;;
--base-path)
shift
normalize_base_path BASE_PATH "$1"
;;
esac
shift
done
update_count_column_width() {
count_column_width=$((${#count} * 2 + 2))
if [[ -n "$BATS_ENABLE_TIMING" ]]; then
# additional space for ' in %s sec'
count_column_width=$((count_column_width + ${#SECONDS} + 8))
fi
# also update dependent value
update_count_column_left
}
update_screen_width() {
screen_width="$(tput cols)"
# also update dependent value
update_count_column_left
}
update_count_column_left() {
count_column_left=$((screen_width - count_column_width))
}
# avoid unset variables
count=0
screen_width=80
update_count_column_width
update_screen_width
test_result=
trap update_screen_width WINCH
begin() {
test_result= # reset to avoid carrying over result state from previous test
line_backoff_count=0
go_to_column 0
update_count_column_width
buffer_with_truncation $((count_column_left - 1)) ' %s' "$name"
clear_to_end_of_line
go_to_column $count_column_left
if [[ -n "$BATS_ENABLE_TIMING" ]]; then
buffer "%${#count}s/${count} in %s sec" "$index" "$SECONDS"
else
buffer "%${#count}s/${count}" "$index"
fi
go_to_column 1
}
finish_test() {
move_up $line_backoff_count
go_to_column 0
buffer "$@"
if [[ -n "$BATS_ENABLE_TIMING" ]]; then
set_color 2
buffer ' [%s]' "$TIMING"
fi
advance
move_down $(( line_backoff_count - 1 ))
}
pass() {
TIMING="${1:-}"
finish_test ' ✓ %s' "$name"
test_result=pass
}
skip() {
local reason="$1"
if [[ -n "$reason" ]]; then
reason=": $reason"
fi
BATS_ENABLE_TIMING='' finish_test ' - %s (skipped%s)' "$name" "$reason"
test_result=skip
}
fail() {
set_color 1 bold
TIMING="${1:-}"
finish_test ' ✗ %s' "$name"
test_result=fail
}
log() {
case ${test_result} in
pass)
clear_color
;;
fail)
set_color 1
;;
esac
buffer ' %s\n' "$1"
clear_color
}
summary() {
if [ "$failures" -eq 0 ] ; then
set_color 2 bold
else
set_color 1 bold
fi
buffer '\n%d test' "$count"
if [[ "$count" -ne 1 ]]; then
buffer 's'
fi
buffer ', %d failure' "$failures"
if [[ "$failures" -ne 1 ]]; then
buffer 's'
fi
if [[ "$skipped" -gt 0 ]]; then
buffer ', %d skipped' "$skipped"
fi
not_run=$((count - passed - failures - skipped))
if [[ "$not_run" -gt 0 ]]; then
buffer ', %d not run' "$not_run"
fi
if [[ -n "$BATS_ENABLE_TIMING" ]]; then
buffer " in $SECONDS seconds"
fi
buffer '\n'
clear_color
}
buffer_with_truncation() {
local width="$1"
shift
local string
# shellcheck disable=SC2059
printf -v 'string' -- "$@"
if [[ "${#string}" -gt "$width" ]]; then
buffer '%s...' "${string:0:$((width - 4))}"
else
buffer '%s' "$string"
fi
}
move_up() {
if [[ $1 -gt 0 ]]; then # avoid moving if we got 0
buffer '\x1B[%dA' "$1"
fi
}
move_down() {
if [[ $1 -gt 0 ]]; then # avoid moving if we got 0
buffer '\x1B[%dB' "$1"
fi
}
go_to_column() {
local column="$1"
buffer '\x1B[%dG' $((column + 1))
}
clear_to_end_of_line() {
buffer '\x1B[K'
}
advance() {
clear_to_end_of_line
buffer '\n'
clear_color
}
set_color() {
local color="$1"
local weight=22
if [[ "${2:-}" == 'bold' ]]; then
weight=1
fi
buffer '\x1B[%d;%dm' "$((30 + color))" "$weight"
}
clear_color() {
buffer '\x1B[0m'
}
_buffer=
buffer() {
local content
# shellcheck disable=SC2059
printf -v content -- "$@"
_buffer+="$content"
}
prefix_buffer_with() {
local old_buffer="$_buffer"
_buffer=''
"$@"
_buffer="$_buffer$old_buffer"
}
flush() {
printf '%s' "$_buffer"
_buffer=
}
finish() {
flush
printf '\n'
}
trap finish EXIT
trap '' INT
bats_tap_stream_plan() {
count="$1"
index=0
passed=0
failures=0
skipped=0
name=
update_count_column_width
}
bats_tap_stream_begin() {
index="$1"
name="$2"
begin
flush
}
bats_tap_stream_ok() {
local duration=
if [[ "$1" == "--duration" ]]; then
duration="$2"
shift 2
fi
index="$1"
name="$2"
((++passed))
pass "$duration"
}
bats_tap_stream_skipped() {
index="$1"
name="$2"
((++skipped))
skip "$3"
}
bats_tap_stream_not_ok() {
local duration=
if [[ "$1" == "--duration" ]]; then
duration="$2"
shift 2
fi
index="$1"
name="$2"
((++failures))
fail "$duration"
}
bats_tap_stream_comment() { # <comment> <scope>
local scope=$2
# count the lines we printed after the begin text,
if [[ $line_backoff_count -eq 0 && $scope == begin ]]; then
# if this is the first line after begin, go down one line
buffer "\n"
(( ++line_backoff_count )) # prefix-increment to avoid "error" due to returning 0
fi
(( ++line_backoff_count ))
(( line_backoff_count += ${#1} / screen_width)) # account for linebreaks due to length
log "$1"
}
bats_tap_stream_suite() {
#test_file="$1"
line_backoff_count=0
index=
# indicate filename for failures
local file_name="${1/$BASE_PATH}"
name="File $file_name"
set_color 4 bold
buffer "%s\n" "$file_name"
clear_color
}
line_backoff_count=0
bats_tap_stream_unknown() { # <full line> <scope>
local scope=$2
# count the lines we printed after the begin text, (or after suite, in case of syntax errors)
if [[ $line_backoff_count -eq 0 && ( $scope == begin || $scope == suite )]]; then
# if this is the first line after begin, go down one line
buffer "\n"
(( ++line_backoff_count )) # prefix-increment to avoid "error" due to returning 0
fi
(( ++line_backoff_count ))
(( line_backoff_count += ${#1} / screen_width)) # account for linebreaks due to length
buffer "%s\n" "$1"
flush
}
bats_parse_internal_extended_tap
summary

View File

@ -0,0 +1,52 @@
#!/usr/bin/env bash
set -e
trap '' INT
# shellcheck source=lib/bats-core/formatter.bash
source "$BATS_ROOT/lib/bats-core/formatter.bash"
bats_tap_stream_plan() {
printf "1..%d\n" "$1"
}
bats_tap_stream_begin() { #<test index> <test name>
:
}
bats_tap_stream_ok() { # [--duration <milliseconds] <test index> <test name>
if [[ "$1" == "--duration" ]]; then
printf "ok %d %s # in %d ms\n" "$3" "$4" "$2"
else
printf "ok %d %s\n" "$1" "$2"
fi
}
bats_tap_stream_not_ok() { # [--duration <milliseconds>] <test index> <test name>
if [[ "$1" == "--duration" ]]; then
printf "not ok %d %s # in %d ms\n" "$3" "$4" "$2"
else
printf "not ok %d %s\n" "$1" "$2"
fi
}
bats_tap_stream_skipped() { # <test index> <test name> <reason>
if [[ -n "$3" ]]; then
printf "ok %d %s # skip %s\n" "$1" "$2" "$3"
else
printf "ok %d %s # skip\n" "$1" "$2"
fi
}
bats_tap_stream_comment() { # <comment text without leading '# '>
printf "# %s\n" "$1"
}
bats_tap_stream_suite() { # <file name>
:
}
bats_tap_stream_unknown() { # <full line>
printf "%s\n" "$1"
}
bats_parse_internal_extended_tap

View File

@ -0,0 +1,91 @@
#!/usr/bin/env bash
set -e
while [[ "$#" -ne 0 ]]; do
case "$1" in
-T)
BATS_ENABLE_TIMING="-T"
;;
esac
shift
done
header_pattern='[0-9]+\.\.[0-9]+'
IFS= read -r header
if [[ "$header" =~ $header_pattern ]]; then
printf "TAP version 13\n"
printf "%s\n" "$header"
else
# If the first line isn't a TAP plan, print it and pass the rest through
printf '%s\n' "$header"
exec cat
fi
yaml_block_open=''
add_yaml_entry() {
if [[ -z "$yaml_block_open" ]]; then
printf " ---\n"
fi
printf " %s: %s\n" "$1" "$2"
yaml_block_open=1
}
close_previous_yaml_block() {
if [[ -n "$yaml_block_open" ]]; then
printf " ...\n"
yaml_block_open=''
fi
}
trap '' INT
number_of_printed_log_lines_for_this_test_so_far=0
while IFS= read -r line; do
case "$line" in
'begin '*) ;;
'ok '*)
close_previous_yaml_block
number_of_printed_log_lines_for_this_test_so_far=0
if [[ -n "${BATS_ENABLE_TIMING-}" ]]; then
timing_expr="(ok [0-9]+ .+) in ([0-9]+)ms$"
if [[ "$line" =~ $timing_expr ]]; then
printf "%s\n" "${BASH_REMATCH[1]}"
add_yaml_entry "duration_ms" "${BASH_REMATCH[2]}"
else
echo "Could not match output line to timing regex: $line" >&2
exit 1
fi
else
printf "%s\n" "${line}"
fi
;;
'not ok '*)
close_previous_yaml_block
number_of_printed_log_lines_for_this_test_so_far=0
timing_expr="(not ok [0-9]+ .+) in ([0-9])+ms$"
if [[ -n "${BATS_ENABLE_TIMING-}" ]]; then
if [[ "$line" =~ $timing_expr ]]; then
printf "%s\n" "${BASH_REMATCH[1]}"
add_yaml_entry "duration_ms" "${BASH_REMATCH[2]}"
else
echo "Could not match failure line to timing regex: $line" >&2
exit 1
fi
else
printf "%s\n" "${line}"
fi
;;
'# '*)
if [[ $number_of_printed_log_lines_for_this_test_so_far -eq 0 ]]; then
add_yaml_entry "message" "|" # use a multiline string for this entry
fi
((++number_of_printed_log_lines_for_this_test_so_far))
printf " %s\n" "${line:2}"
;;
'suite '*) ;;
esac
done
# close the final block if there was one
close_previous_yaml_block

View File

@ -0,0 +1,60 @@
#!/usr/bin/env bash
set -e
bats_encode_test_name() {
local name="$1"
local result='test_'
local hex_code
if [[ ! "$name" =~ [^[:alnum:]\ _-] ]]; then
name="${name//_/-5f}"
name="${name//-/-2d}"
name="${name// /_}"
result+="$name"
else
local length="${#name}"
local char i
for ((i = 0; i < length; i++)); do
char="${name:$i:1}"
if [[ "$char" == ' ' ]]; then
result+='_'
elif [[ "$char" =~ [[:alnum:]] ]]; then
result+="$char"
else
printf -v 'hex_code' -- '-%02x' \'"$char"
result+="$hex_code"
fi
done
fi
printf -v "$2" '%s' "$result"
}
BATS_TEST_PATTERN="^[[:blank:]]*@test[[:blank:]]+(.*[^[:blank:]])[[:blank:]]+\{(.*)\$"
BATS_TEST_PATTERN_COMMENT="[[:blank:]]*([^[:blank:]()]+)[[:blank:]]*\(?\)?[[:blank:]]+\{[[:blank:]]+#[[:blank:]]*@test[[:blank:]]*\$"
test_file="$1"
tests=()
{
while IFS= read -r line; do
line="${line//$'\r'/}"
if [[ "$line" =~ $BATS_TEST_PATTERN ]] || [[ "$line" =~ $BATS_TEST_PATTERN_COMMENT ]]; then
name="${BASH_REMATCH[1]#[\'\"]}"
name="${name%[\'\"]}"
body="${BASH_REMATCH[2]:-}"
bats_encode_test_name "$name" 'encoded_name'
printf '%s() { bats_test_begin "%s"; %s\n' "${encoded_name:?}" "$name" "$body" || :
if [[ -z "$BATS_TEST_FILTER" || "$name" =~ $BATS_TEST_FILTER ]]; then
tests+=("$encoded_name")
fi
else
printf '%s\n' "$line"
fi
done
} <<<"$(<"$test_file")"$'\n'
for test_name in "${tests[@]}"; do
printf 'bats_test_function %s\n' "$test_name"
done

View File

@ -0,0 +1,197 @@
# setup paths for BATS test units
setup() {
[ ! -f ${BATS_PARENT_TMPNAME}.skip ] || skip "skip remaining tests"
bats_require_minimum_version 1.5.0
R="$BATS_TEST_DIRNAME"
# R=`cd "$T"/.. && pwd`
TMP="$BATS_FILE_TMPDIR"
load test_helper/bats-support/load
load test_helper/bats-assert/load
load test_helper/bats-file/load
ZTMP="$BATS_FILE_TMPDIR"
if [ "$ZENROOM_EXECUTABLE" == "" ]; then
ZENROOM_EXECUTABLE="/usr/local/bin/zenroom"
fi
PATH="$PATH:/usr/local/bin:/usr/local/sbin"
MNT="/media/`basename $BATS_TEST_FILENAME`"
PIM=42
# max password stdin size to veracrypt is 128 bytes
PW="91cd69b2caab05cb64f8e1c8ae22a7c5a25564e7fc5116d0303dce1ffd26861ea0483c25bdb85adb216718c19815eb59ac14ec9783bfbbb57786ca7d9038c845"
# `openssl rand -hex 64`"
FS='ext4'
SIZE='20M'
[ "$TOMB" = "" ] && TOMB=test.tomb
# default on freebsd is 1001
user_uid=1000
user_gid=1000
system="`uname -s`"
f_create=""
f_format=""
f_map=""
f_mount=""
f_close=""
case "$system" in
FreeBSD)
f_create=portable_create
f_format=freebsd_format
f_map=portable_map
f_mount=freebsd_mount
f_close=freebsd_close
user_uid=1001
user_gid=1001
;;
Linux)
f_create=portable_create
f_format=linux_format
f_map=portable_map
f_mount=linux_mount
f_close=portable_close
user_uid=1000
user_gid=1000
;;
*)
>&2 echo "Unsupported system: $system"
exit 1
esac
# cd $ZTMP
}
teardown() {
[ -n "$BATS_TEST_COMPLETED" ] || touch ${BATS_PARENT_TMPNAME}.skip
>&3 echo
}
# usage: echo PASSWORD | portable_create file size pim
portable_create() {
local file="$1" # must not exist
local size="$2" # veracrypt format (accepts M or G)
local pim="$3" # any number
# >&2 echo "portable_create $file $size $pim"
veracrypt --non-interactive --text --stdin \
-m nokernelcrypto \
-c ${file} --volume-type normal \
--hash sha512 --encryption serpent-aes \
--filesystem none --size ${size} --pim ${pim} \
--random-source /dev/urandom -k ''
return $?
}
# usage: echo PASSWORD | portable_map file pim
portable_map() {
local file="$1"
local pim="$2"
# >&2 echo "portable_map $file $pim"
veracrypt --non-interactive --text --stdin \
--protect-hidden no -m nokernelcrypto \
-k '' --pim ${pim} --filesystem none \
${file}
local loop=`veracrypt -l "$file" | awk '{print $3}'`
return $?
}
portable_close() {
local file="$1"
veracrypt -d ${file}
return $?
}
linux_format() {
local file="$1"
# loop="`losetup -j ${vera}/volume | cut -d: -f1`"
local loop=`veracrypt -l "$FILE" | awk '{print $3}'`
# losetup -l
>&2 echo "veramap format: ${loop}"
sync
sudo mkfs.ext4 -E root_owner="${user_uid}:${user_gid}" "$loop"
return $?
}
# usage: _mount /tmp/.veracrypt_ mountpoint
linux_mount() {
local file="$1"
local mnt="$2"
# local loop="`losetup -j ${vera}/volume | cut -d: -f1`"
local loop=`veracrypt -l "$file" | awk '{print $3}'`
>&3 echo "fsck $loop"
fsck.ext4 -p -C0 "$loop"
sudo mount ${loop} "$mnt"
return $?
}
freebsd_format() {
file="$1"
local loop=`veracrypt -l "$FILE" | awk '{print $3}'`
mkfs.ext4 -E root_owner="${user_uid}:${user_gid}" "${loop}"
return $?
}
freebsd_mount() {
local file="$1"
local mnt="$2"
>&2 echo `veracrypt -l "$file"`
local loop=`veracrypt -l "$file" | awk '{print $3}'`
>&3 echo "fsck $loop"
fsck.ext4 -p -C0 "$loop"
lklfuse -o type=ext4 "${loop}" "$mnt"
return $?
}
freebsd_close() {
local file="$1"
local md=`veracrypt -l "$file" | awk '{print $3}'`
# umount "$mnt"
>&2 echo "md: $md"
local mnt=`pgrep -lf "lklfuse.*$md" | awk '{print $6}'`
>&2 echo "mnt: $mnt"
lkl=`pgrep -f "lklfuse.*$md" | awk '{print $1}'`
>&3 echo "kill $lkl (lklfuse on $md)"
# trying to deail with lklfuse bug
# https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=239831
renice -20 $lkl
kill $lkl
sync
sleep 1
kill -9 $lkl
# lkl should have really exited now
>&3 echo "veracrypt -d $file"
veracrypt -d "$file"
return $?
}
Z() {
tmptmp=0
if ! [ "$TMP" ]; then
TMP=`mktemp -d`; tmptmp=1
fi
script=$1
if [ "$script" == "-" ]; then
cat > $TMP/script_stdin
script=$TMP/script_stdin
else
if ! [ -r "$script" ]; then
script=$T/$script
fi
fi
if ! [ -r $script ]; then
>&2 echo "Error - script not found: $script"
return 1
fi
shift 1
if [ "${1##*.}" == "zen" ]; then
$ZENROOM_EXECUTABLE $@ -z $script
else
$ZENROOM_EXECUTABLE $@ $script
fi
if [ $tmptmp = 1 ]; then
rm -f $TMP/*
rmdir $TMP
fi
}

View File

@ -0,0 +1,26 @@
load bats_setup
@test "Find the tomb" {
assert_file_exists "$TOMB"
>&3 echo "Found: $TOMB"
}
@test "Open tomb with key" {
mkdir -p "$TOMB".mnt
./tomb open -k "$TOMB".key "$TOMB" "$TOMB".mnt
}
@test "Check integrity of random data" {
sha512sum "$TOMB".mnt/random.data | awk '{print $1}'
cat "$TOMB".hash
>&2 echo $newhash
>&2 echo $oldhash
assert_equal "$newhash" "$oldhash"
if [ -r "$TOMB".uname ]; then
>&3 cat "$TOMB".uname
fi
}
@test "Close tomb" {
./tomb close "$TOMB"
}

View File

@ -0,0 +1,79 @@
load bats_setup
# f_create=""
# f_format=""
# f_map=""
# f_mount=""
# f_close=""
@test "Create a new tomb" {
# >&3 echo $PW
>&3 echo $TOMB
echo -n "$PW" | $f_create "$TOMB" 20M ${PIM}
}
@test "Map the tomb" {
echo -n "$PW" | $f_map "$TOMB" ${PIM}
}
@test "Format the tomb" {
$f_format "/tmp/.veracrypt_aux_mnt1" || {
$f_close "$TOMB"
return false
}
}
@test "Mount the tomb" {
mkdir -p "$MNT"
$f_mount "$TOMB" "$MNT" || {
$f_close "$TOMB"
return false
}
}
@test "Create random.data inside the tomb" {
dd if=/dev/urandom of="$MNT"/random.data bs=1024 count=10000 || {
# sudo umount testmnt
$f_close "$TOMB"
return false
}
>&3 ls -l "$MNT"/random.data
sha512sum "$MNT"/random.data | awk '{print $1}' | >&3 tee "$TOMB".hash
uname -a > "$TOMB".uname
}
@test "Close the test.tomb" {
>&3 veracrypt -l
# sudo umount "$TMP"/testmnt
$f_close "$TOMB"
}
@test "Re-Map the test.tomb" {
echo -n "$PW" | $f_map "$TOMB" ${PIM}
}
@test "Re-Mount the test.tomb" {
mkdir -p "$MNT"
$f_mount "$TOMB" "$MNT" || {
$f_close "$TOMB"
return false
}
}
@test "Re-Check integrity of random data" {
newhash=`sha512sum "$MNT"/random.data | awk '{print $1}'`
oldhash=`cat "$TOMB".hash`
>&2 echo $newhash
>&2 echo $oldhash
assert_equal "$newhash" "$oldhash"
if [ -r "$TOMB".uname ]; then
>&3 cat "$TOMB".uname
fi
}
@test "Re-Close the test.tomb" {
>&3 veracrypt -l
$f_close "$TOMB"
}

View File

@ -0,0 +1,51 @@
load bats_setup
@test "Dig tomb" {
rm -f "$TOMB"
>&3 echo "$TOMB"
./tomb dig -s 20 "$TOMB"
}
@test "Forge key" {
rm -f "$TOMB".key
./tomb forge "$TOMB".key
}
@test "Lock tomb with key" {
./tomb lock -k "$TOMB".key "$TOMB"
}
@test "Open tomb with key" {
mkdir -p "$TOMB".mnt
./tomb open -k "$TOMB".key "$TOMB" "$TOMB".mnt
}
@test "Create random.data inside the tomb" {
dd if=/dev/urandom of="$TOMB".mnt/random.data bs=1024 count=10000
>&3 ls -l "$TOMB".mnt/random.data
sha512sum "$TOMB".mnt/random.data | awk '{print $1}' | >&3 tee "$TOMB".hash
uname -a > "$TOMB".uname
}
@test "Close tomb" {
./tomb close "$TOMB"
}
@test "Re-open tomb with key" {
./tomb open -k "$TOMB".key "$TOMB" "$TOMB".mnt
}
@test "Check integrity of random data" {
newhash=`sha512sum "$TOMB".mnt/random.data | awk '{print $1}'`
oldhash=`cat "$TOMB".hash`
>&2 echo $newhash
>&2 echo $oldhash
assert_equal "$newhash" "$oldhash"
if [ -r "$TOMB".uname ]; then
>&3 cat "$TOMB".uname
fi
}
@test "Close the tomb again" {
./tomb close "$TOMB"
}

View File

@ -0,0 +1,33 @@
# bats-assert - Common assertions for Bats
#
# Written in 2016 by Zoltan Tombol <zoltan dot tombol at gmail dot com>
#
# To the extent possible under law, the author(s) have dedicated all
# copyright and related and neighboring rights to this software to the
# public domain worldwide. This software is distributed without any
# warranty.
#
# You should have received a copy of the CC0 Public Domain Dedication
# along with this software. If not, see
# <http://creativecommons.org/publicdomain/zero/1.0/>.
#
# Assertions are functions that perform a test and output relevant
# information on failure to help debugging. They return 1 on failure
# and 0 otherwise.
#
# All output is formatted for readability using the functions of
# `output.bash' and sent to the standard error.
# shellcheck disable=1090
source "$(dirname "${BASH_SOURCE[0]}")/src/assert.bash"
source "$(dirname "${BASH_SOURCE[0]}")/src/refute.bash"
source "$(dirname "${BASH_SOURCE[0]}")/src/assert_equal.bash"
source "$(dirname "${BASH_SOURCE[0]}")/src/assert_not_equal.bash"
source "$(dirname "${BASH_SOURCE[0]}")/src/assert_success.bash"
source "$(dirname "${BASH_SOURCE[0]}")/src/assert_failure.bash"
source "$(dirname "${BASH_SOURCE[0]}")/src/assert_output.bash"
source "$(dirname "${BASH_SOURCE[0]}")/src/refute_output.bash"
source "$(dirname "${BASH_SOURCE[0]}")/src/assert_line.bash"
source "$(dirname "${BASH_SOURCE[0]}")/src/refute_line.bash"
source "$(dirname "${BASH_SOURCE[0]}")/src/assert_regex.bash"
source "$(dirname "${BASH_SOURCE[0]}")/src/refute_regex.bash"

View File

@ -0,0 +1,42 @@
# assert
# ======
#
# Summary: Fail if the given expression evaluates to false.
#
# Usage: assert <expression>
# Options:
# <expression> The expression to evaluate for truthiness.
# *__Note:__ The expression must be a simple command.
# [Compound commands](https://www.gnu.org/software/bash/manual/bash.html#Compound-Commands),
# such as `[[`, can be used only when executed with `bash -c`.*
#
# IO:
# STDERR - the failed expression, on failure
# Globals:
# none
# Returns:
# 0 - if expression evaluates to true
# 1 - otherwise
#
# ```bash
# @test 'assert()' {
# touch '/var/log/test.log'
# assert [ -e '/var/log/test.log' ]
# }
# ```
#
# On failure, the failed expression is displayed.
#
# ```
# -- assertion failed --
# expression : [ -e /var/log/test.log ]
# --
# ```
assert() {
if ! "$@"; then
batslib_print_kv_single 10 'expression' "$*" \
| batslib_decorate 'assertion failed' \
| fail
fi
}

View File

@ -0,0 +1,42 @@
# assert_equal
# ============
#
# Summary: Fail if the actual and expected values are not equal.
#
# Usage: assert_equal <actual> <expected>
#
# Options:
# <actual> The value being compared.
# <expected> The value to compare against.
#
# ```bash
# @test 'assert_equal()' {
# assert_equal 'have' 'want'
# }
# ```
#
# IO:
# STDERR - expected and actual values, on failure
# Globals:
# none
# Returns:
# 0 - if values equal
# 1 - otherwise
#
# On failure, the expected and actual values are displayed.
#
# ```
# -- values do not equal --
# expected : want
# actual : have
# --
# ```
assert_equal() {
if [[ $1 != "$2" ]]; then
batslib_print_kv_single_or_multi 8 \
'expected' "$2" \
'actual' "$1" \
| batslib_decorate 'values do not equal' \
| fail
fi
}

View File

@ -0,0 +1,78 @@
# assert_failure
# ==============
#
# Summary: Fail if `$status` is 0; or is not equal to the optionally provided status.
#
# Usage: assert_failure [<expected_status>]
#
# Options:
# <expected_status> The specific status code to check against.
# If not provided, simply asserts status is != 0.
#
# IO:
# STDERR - `$output`, on failure;
# - also, `$status` and `expected_status`, if provided
# Globals:
# status
# output
# Returns:
# 0 - if `$status' is 0,
# or if expected_status is provided but does not equal `$status'
# 1 - otherwise
#
# ```bash
# @test 'assert_failure() status only' {
# run echo 'Success!'
# assert_failure
# }
# ```
#
# On failure, `$output` is displayed.
#
# ```
# -- command succeeded, but it was expected to fail --
# output : Success!
# --
# ```
#
# ## Expected status
#
# When `expected_status` is provided, fail if `$status` does not equal the `expected_status`.
#
# ```bash
# @test 'assert_failure() with expected status' {
# run bash -c "echo 'Error!'; exit 1"
# assert_failure 2
# }
# ```
#
# On failure, both the expected and actual statuses, and `$output` are displayed.
#
# ```
# -- command failed as expected, but status differs --
# expected : 2
# actual : 1
# output : Error!
# --
# ```
assert_failure() {
: "${output?}"
: "${status?}"
(( $# > 0 )) && local -r expected="$1"
if (( status == 0 )); then
batslib_print_kv_single_or_multi 6 'output' "$output" \
| batslib_decorate 'command succeeded, but it was expected to fail' \
| fail
elif (( $# > 0 )) && (( status != expected )); then
{ local -ir width=8
batslib_print_kv_single "$width" \
'expected' "$expected" \
'actual' "$status"
batslib_print_kv_single_or_multi "$width" \
'output' "$output"
} \
| batslib_decorate 'command failed as expected, but status differs' \
| fail
fi
}

View File

@ -0,0 +1,248 @@
# assert_line
# ===========
#
# Summary: Fail if the expected line is not found in the output (default) or at a specific line number.
#
# Usage: assert_line [-n index] [-p | -e] [--] <expected>
#
# Options:
# -n, --index <idx> Match the <idx>th line
# -p, --partial Match if `expected` is a substring of `$output` or line <idx>
# -e, --regexp Treat `expected` as an extended regular expression
# <expected> The expected line string, substring, or regular expression
#
# IO:
# STDERR - details, on failure
# error message, on error
# Globals:
# output
# lines
# Returns:
# 0 - if matching line found
# 1 - otherwise
#
# Similarly to `assert_output`, this function verifies that a command or function produces the expected output.
# (It is the logical complement of `refute_line`.)
# It checks that the expected line appears in the output (default) or at a specific line number.
# Matching can be literal (default), partial or regular expression.
#
# *__Warning:__
# Due to a [bug in Bats][bats-93], empty lines are discarded from `${lines[@]}`,
# causing line indices to change and preventing testing for empty lines.*
#
# [bats-93]: https://github.com/sstephenson/bats/pull/93
#
# ## Looking for a line in the output
#
# By default, the entire output is searched for the expected line.
# The assertion fails if the expected line is not found in `${lines[@]}`.
#
# ```bash
# @test 'assert_line() looking for line' {
# run echo $'have-0\nhave-1\nhave-2'
# assert_line 'want'
# }
# ```
#
# On failure, the expected line and the output are displayed.
#
# ```
# -- output does not contain line --
# line : want
# output (3 lines):
# have-0
# have-1
# have-2
# --
# ```
#
# ## Matching a specific line
#
# When the `--index <idx>` option is used (`-n <idx>` for short), the expected line is matched only against the line identified by the given index.
# The assertion fails if the expected line does not equal `${lines[<idx>]}`.
#
# ```bash
# @test 'assert_line() specific line' {
# run echo $'have-0\nhave-1\nhave-2'
# assert_line --index 1 'want-1'
# }
# ```
#
# On failure, the index and the compared lines are displayed.
#
# ```
# -- line differs --
# index : 1
# expected : want-1
# actual : have-1
# --
# ```
#
# ## Partial matching
#
# Partial matching can be enabled with the `--partial` option (`-p` for short).
# When used, a match fails if the expected *substring* is not found in the matched line.
#
# ```bash
# @test 'assert_line() partial matching' {
# run echo $'have 1\nhave 2\nhave 3'
# assert_line --partial 'want'
# }
# ```
#
# On failure, the same details are displayed as for literal matching, except that the substring replaces the expected line.
#
# ```
# -- no output line contains substring --
# substring : want
# output (3 lines):
# have 1
# have 2
# have 3
# --
# ```
#
# ## Regular expression matching
#
# Regular expression matching can be enabled with the `--regexp` option (`-e` for short).
# When used, a match fails if the *extended regular expression* does not match the line being tested.
#
# *__Note__:
# As expected, the anchors `^` and `$` bind to the beginning and the end (respectively) of the matched line.*
#
# ```bash
# @test 'assert_line() regular expression matching' {
# run echo $'have-0\nhave-1\nhave-2'
# assert_line --index 1 --regexp '^want-[0-9]$'
# }
# ```
#
# On failure, the same details are displayed as for literal matching, except that the regular expression replaces the expected line.
#
# ```
# -- regular expression does not match line --
# index : 1
# regexp : ^want-[0-9]$
# line : have-1
# --
# ```
# FIXME(ztombol): Display `${lines[@]}' instead of `$output'!
assert_line() {
local -i is_match_line=0
local -i is_mode_partial=0
local -i is_mode_regexp=0
: "${lines?}"
# Handle options.
while (( $# > 0 )); do
case "$1" in
-n|--index)
if (( $# < 2 )) || ! [[ $2 =~ ^-?([0-9]|[1-9][0-9]+)$ ]]; then
echo "\`--index' requires an integer argument: \`$2'" \
| batslib_decorate 'ERROR: assert_line' \
| fail
return $?
fi
is_match_line=1
local -ri idx="$2"
shift 2
;;
-p|--partial) is_mode_partial=1; shift ;;
-e|--regexp) is_mode_regexp=1; shift ;;
--) shift; break ;;
*) break ;;
esac
done
if (( is_mode_partial )) && (( is_mode_regexp )); then
echo "\`--partial' and \`--regexp' are mutually exclusive" \
| batslib_decorate 'ERROR: assert_line' \
| fail
return $?
fi
# Arguments.
local -r expected="$1"
if (( is_mode_regexp == 1 )) && [[ '' =~ $expected ]] || (( $? == 2 )); then
echo "Invalid extended regular expression: \`$expected'" \
| batslib_decorate 'ERROR: assert_line' \
| fail
return $?
fi
# Matching.
if (( is_match_line )); then
# Specific line.
if (( is_mode_regexp )); then
if ! [[ ${lines[$idx]} =~ $expected ]]; then
batslib_print_kv_single 6 \
'index' "$idx" \
'regexp' "$expected" \
'line' "${lines[$idx]}" \
| batslib_decorate 'regular expression does not match line' \
| fail
fi
elif (( is_mode_partial )); then
if [[ ${lines[$idx]} != *"$expected"* ]]; then
batslib_print_kv_single 9 \
'index' "$idx" \
'substring' "$expected" \
'line' "${lines[$idx]}" \
| batslib_decorate 'line does not contain substring' \
| fail
fi
else
if [[ ${lines[$idx]} != "$expected" ]]; then
batslib_print_kv_single 8 \
'index' "$idx" \
'expected' "$expected" \
'actual' "${lines[$idx]}" \
| batslib_decorate 'line differs' \
| fail
fi
fi
else
# Contained in output.
if (( is_mode_regexp )); then
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
[[ ${lines[$idx]} =~ $expected ]] && return 0
done
{ local -ar single=( 'regexp' "$expected" )
local -ar may_be_multi=( 'output' "$output" )
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
batslib_print_kv_single "$width" "${single[@]}"
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
} \
| batslib_decorate 'no output line matches regular expression' \
| fail
elif (( is_mode_partial )); then
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
[[ ${lines[$idx]} == *"$expected"* ]] && return 0
done
{ local -ar single=( 'substring' "$expected" )
local -ar may_be_multi=( 'output' "$output" )
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
batslib_print_kv_single "$width" "${single[@]}"
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
} \
| batslib_decorate 'no output line contains substring' \
| fail
else
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
[[ ${lines[$idx]} == "$expected" ]] && return 0
done
{ local -ar single=( 'line' "$expected" )
local -ar may_be_multi=( 'output' "$output" )
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
batslib_print_kv_single "$width" "${single[@]}"
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
} \
| batslib_decorate 'output does not contain line' \
| fail
fi
fi
}

View File

@ -0,0 +1,42 @@
# assert_not_equal
# ============
#
# Summary: Fail if the actual and unexpected values are equal.
#
# Usage: assert_not_equal <actual> <unexpected>
#
# Options:
# <actual> The value being compared.
# <unexpected> The value to compare against.
#
# ```bash
# @test 'assert_not_equal()' {
# assert_not_equal 'foo' 'foo'
# }
# ```
#
# IO:
# STDERR - expected and actual values, on failure
# Globals:
# none
# Returns:
# 0 - if actual does not equal unexpected
# 1 - otherwise
#
# On failure, the unexpected and actual values are displayed.
#
# ```
# -- values should not be equal --
# unexpected : foo
# actual : foo
# --
# ```
assert_not_equal() {
if [[ "$1" == "$2" ]]; then
batslib_print_kv_single_or_multi 10 \
'unexpected' "$2" \
'actual' "$1" \
| batslib_decorate 'values should not be equal' \
| fail
fi
}

View File

@ -0,0 +1,197 @@
# assert_output
# =============
#
# Summary: Fail if `$output' does not match the expected output.
#
# Usage: assert_output [-p | -e] [- | [--] <expected>]
#
# Options:
# -p, --partial Match if `expected` is a substring of `$output`
# -e, --regexp Treat `expected` as an extended regular expression
# -, --stdin Read `expected` value from STDIN
# <expected> The expected value, substring or regular expression
#
# IO:
# STDIN - [=$1] expected output
# STDERR - details, on failure
# error message, on error
# Globals:
# output
# Returns:
# 0 - if output matches the expected value/partial/regexp
# 1 - otherwise
#
# This function verifies that a command or function produces the expected output.
# (It is the logical complement of `refute_output`.)
# Output matching can be literal (the default), partial or by regular expression.
# The expected output can be specified either by positional argument or read from STDIN by passing the `-`/`--stdin` flag.
#
# ## Literal matching
#
# By default, literal matching is performed.
# The assertion fails if `$output` does not equal the expected output.
#
# ```bash
# @test 'assert_output()' {
# run echo 'have'
# assert_output 'want'
# }
#
# @test 'assert_output() with pipe' {
# run echo 'hello'
# echo 'hello' | assert_output -
# }
#
# @test 'assert_output() with herestring' {
# run echo 'hello'
# assert_output - <<< hello
# }
# ```
#
# On failure, the expected and actual output are displayed.
#
# ```
# -- output differs --
# expected : want
# actual : have
# --
# ```
#
# ## Existence
#
# To assert that any output exists at all, omit the `expected` argument.
#
# ```bash
# @test 'assert_output()' {
# run echo 'have'
# assert_output
# }
# ```
#
# On failure, an error message is displayed.
#
# ```
# -- no output --
# expected non-empty output, but output was empty
# --
# ```
#
# ## Partial matching
#
# Partial matching can be enabled with the `--partial` option (`-p` for short).
# When used, the assertion fails if the expected _substring_ is not found in `$output`.
#
# ```bash
# @test 'assert_output() partial matching' {
# run echo 'ERROR: no such file or directory'
# assert_output --partial 'SUCCESS'
# }
# ```
#
# On failure, the substring and the output are displayed.
#
# ```
# -- output does not contain substring --
# substring : SUCCESS
# output : ERROR: no such file or directory
# --
# ```
#
# ## Regular expression matching
#
# Regular expression matching can be enabled with the `--regexp` option (`-e` for short).
# When used, the assertion fails if the *extended regular expression* does not match `$output`.
#
# *__Note__:
# The anchors `^` and `$` bind to the beginning and the end (respectively) of the entire output;
# not individual lines.*
#
# ```bash
# @test 'assert_output() regular expression matching' {
# run echo 'Foobar 0.1.0'
# assert_output --regexp '^Foobar v[0-9]+\.[0-9]+\.[0-9]$'
# }
# ```
#
# On failure, the regular expression and the output are displayed.
#
# ```
# -- regular expression does not match output --
# regexp : ^Foobar v[0-9]+\.[0-9]+\.[0-9]$
# output : Foobar 0.1.0
# --
# ```
assert_output() {
local -i is_mode_partial=0
local -i is_mode_regexp=0
local -i is_mode_nonempty=0
local -i use_stdin=0
: "${output?}"
# Handle options.
if (( $# == 0 )); then
is_mode_nonempty=1
fi
while (( $# > 0 )); do
case "$1" in
-p|--partial) is_mode_partial=1; shift ;;
-e|--regexp) is_mode_regexp=1; shift ;;
-|--stdin) use_stdin=1; shift ;;
--) shift; break ;;
*) break ;;
esac
done
if (( is_mode_partial )) && (( is_mode_regexp )); then
echo "\`--partial' and \`--regexp' are mutually exclusive" \
| batslib_decorate 'ERROR: assert_output' \
| fail
return $?
fi
# Arguments.
local expected
if (( use_stdin )); then
expected="$(cat -)"
else
expected="${1-}"
fi
# Matching.
if (( is_mode_nonempty )); then
if [ -z "$output" ]; then
echo 'expected non-empty output, but output was empty' \
| batslib_decorate 'no output' \
| fail
fi
elif (( is_mode_regexp )); then
if [[ '' =~ $expected ]] || (( $? == 2 )); then
echo "Invalid extended regular expression: \`$expected'" \
| batslib_decorate 'ERROR: assert_output' \
| fail
elif ! [[ $output =~ $expected ]]; then
batslib_print_kv_single_or_multi 6 \
'regexp' "$expected" \
'output' "$output" \
| batslib_decorate 'regular expression does not match output' \
| fail
fi
elif (( is_mode_partial )); then
if [[ $output != *"$expected"* ]]; then
batslib_print_kv_single_or_multi 9 \
'substring' "$expected" \
'output' "$output" \
| batslib_decorate 'output does not contain substring' \
| fail
fi
else
if [[ $output != "$expected" ]]; then
batslib_print_kv_single_or_multi 8 \
'expected' "$expected" \
'actual' "$output" \
| batslib_decorate 'output differs' \
| fail
fi
fi
}

View File

@ -0,0 +1,56 @@
# `assert_regex`
#
# This function is similar to `assert_equal` but uses pattern matching instead
# of equality, by wrapping `[[ value =~ pattern ]]`.
#
# Fail if the value (first parameter) does not match the pattern (second
# parameter).
#
# ```bash
# @test 'assert_regex()' {
# assert_regex 'what' 'x$'
# }
# ```
#
# On failure, the value and the pattern are displayed.
#
# ```
# -- values does not match regular expression --
# value : what
# pattern : x$
# --
# ```
#
# If the value is longer than one line then it is displayed in *multi-line*
# format.
#
# An error is displayed if the specified extended regular expression is invalid.
#
# For description of the matching behavior, refer to the documentation of the
# `=~` operator in the
# [Bash manual]: https://www.gnu.org/software/bash/manual/html_node/Conditional-Constructs.html.
# Note that the `BASH_REMATCH` array is available immediately after the
# assertion succeeds but is fragile, i.e. prone to being overwritten as a side
# effect of other actions.
assert_regex() {
local -r value="${1}"
local -r pattern="${2}"
if [[ '' =~ ${pattern} ]] || (( ${?} == 2 )); then
echo "Invalid extended regular expression: \`${pattern}'" \
| batslib_decorate 'ERROR: assert_regex' \
| fail
elif ! [[ "${value}" =~ ${pattern} ]]; then
if shopt -p nocasematch &>/dev/null; then
local case_sensitive=insensitive
else
local case_sensitive=sensitive
fi
batslib_print_kv_single_or_multi 8 \
'value' "${value}" \
'pattern' "${pattern}" \
'case' "${case_sensitive}" \
| batslib_decorate 'value does not match regular expression' \
| fail
fi
}

View File

@ -0,0 +1,44 @@
# assert_success
# ==============
#
# Summary: Fail if `$status` is not 0.
#
# Usage: assert_success
#
# IO:
# STDERR - `$status` and `$output`, on failure
# Globals:
# status
# output
# Returns:
# 0 - if `$status' is 0
# 1 - otherwise
#
# ```bash
# @test 'assert_success() status only' {
# run bash -c "echo 'Error!'; exit 1"
# assert_success
# }
# ```
#
# On failure, `$status` and `$output` are displayed.
#
# ```
# -- command failed --
# status : 1
# output : Error!
# --
# ```
assert_success() {
: "${output?}"
: "${status?}"
if (( status != 0 )); then
{ local -ir width=6
batslib_print_kv_single "$width" 'status' "$status"
batslib_print_kv_single_or_multi "$width" 'output' "$output"
} \
| batslib_decorate 'command failed' \
| fail
fi
}

View File

@ -0,0 +1,42 @@
# refute
# ======
#
# Summary: Fail if the given expression evaluates to true.
#
# Usage: refute <expression>
#
# Options:
# <expression> The expression to evaluate for falsiness.
# *__Note:__ The expression must be a simple command.
# [Compound commands](https://www.gnu.org/software/bash/manual/bash.html#Compound-Commands),
# such as `[[`, can be used only when executed with `bash -c`.*
#
# IO:
# STDERR - the successful expression, on failure
# Globals:
# none
# Returns:
# 0 - if expression evaluates to false
# 1 - otherwise
#
# ```bash
# @test 'refute()' {
# rm -f '/var/log/test.log'
# refute [ -e '/var/log/test.log' ]
# }
# ```
#
# On failure, the successful expression is displayed.
#
# ```
# -- assertion succeeded, but it was expected to fail --
# expression : [ -e /var/log/test.log ]
# --
# ```
refute() {
if "$@"; then
batslib_print_kv_single 10 'expression' "$*" \
| batslib_decorate 'assertion succeeded, but it was expected to fail' \
| fail
fi
}

View File

@ -0,0 +1,271 @@
# refute_line
# ===========
#
# Summary: Fail if the unexpected line is found in the output (default) or at a specific line number.
#
# Usage: refute_line [-n index] [-p | -e] [--] <unexpected>
#
# Options:
# -n, --index <idx> Match the <idx>th line
# -p, --partial Match if `unexpected` is a substring of `$output` or line <idx>
# -e, --regexp Treat `unexpected` as an extended regular expression
# <unexpected> The unexpected line string, substring, or regular expression.
#
# IO:
# STDERR - details, on failure
# error message, on error
# Globals:
# output
# lines
# Returns:
# 0 - if match not found
# 1 - otherwise
#
# Similarly to `refute_output`, this function verifies that a command or function does not produce the unexpected output.
# (It is the logical complement of `assert_line`.)
# It checks that the unexpected line does not appear in the output (default) or at a specific line number.
# Matching can be literal (default), partial or regular expression.
#
# *__Warning:__
# Due to a [bug in Bats][bats-93], empty lines are discarded from `${lines[@]}`,
# causing line indices to change and preventing testing for empty lines.*
#
# [bats-93]: https://github.com/sstephenson/bats/pull/93
#
# ## Looking for a line in the output
#
# By default, the entire output is searched for the unexpected line.
# The assertion fails if the unexpected line is found in `${lines[@]}`.
#
# ```bash
# @test 'refute_line() looking for line' {
# run echo $'have-0\nwant\nhave-2'
# refute_line 'want'
# }
# ```
#
# On failure, the unexpected line, the index of its first match and the output with the matching line highlighted are displayed.
#
# ```
# -- line should not be in output --
# line : want
# index : 1
# output (3 lines):
# have-0
# > want
# have-2
# --
# ```
#
# ## Matching a specific line
#
# When the `--index <idx>` option is used (`-n <idx>` for short), the unexpected line is matched only against the line identified by the given index.
# The assertion fails if the unexpected line equals `${lines[<idx>]}`.
#
# ```bash
# @test 'refute_line() specific line' {
# run echo $'have-0\nwant-1\nhave-2'
# refute_line --index 1 'want-1'
# }
# ```
#
# On failure, the index and the unexpected line are displayed.
#
# ```
# -- line should differ --
# index : 1
# line : want-1
# --
# ```
#
# ## Partial matching
#
# Partial matching can be enabled with the `--partial` option (`-p` for short).
# When used, a match fails if the unexpected *substring* is found in the matched line.
#
# ```bash
# @test 'refute_line() partial matching' {
# run echo $'have 1\nwant 2\nhave 3'
# refute_line --partial 'want'
# }
# ```
#
# On failure, in addition to the details of literal matching, the substring is also displayed.
# When used with `--index <idx>` the substring replaces the unexpected line.
#
# ```
# -- no line should contain substring --
# substring : want
# index : 1
# output (3 lines):
# have 1
# > want 2
# have 3
# --
# ```
#
# ## Regular expression matching
#
# Regular expression matching can be enabled with the `--regexp` option (`-e` for short).
# When used, a match fails if the *extended regular expression* matches the line being tested.
#
# *__Note__:
# As expected, the anchors `^` and `$` bind to the beginning and the end (respectively) of the matched line.*
#
# ```bash
# @test 'refute_line() regular expression matching' {
# run echo $'Foobar v0.1.0\nRelease date: 2015-11-29'
# refute_line --index 0 --regexp '^Foobar v[0-9]+\.[0-9]+\.[0-9]$'
# }
# ```
#
# On failure, in addition to the details of literal matching, the regular expression is also displayed.
# When used with `--index <idx>` the regular expression replaces the unexpected line.
#
# ```
# -- regular expression should not match line --
# index : 0
# regexp : ^Foobar v[0-9]+\.[0-9]+\.[0-9]$
# line : Foobar v0.1.0
# --
# ```
# FIXME(ztombol): Display `${lines[@]}' instead of `$output'!
refute_line() {
local -i is_match_line=0
local -i is_mode_partial=0
local -i is_mode_regexp=0
: "${lines?}"
# Handle options.
while (( $# > 0 )); do
case "$1" in
-n|--index)
if (( $# < 2 )) || ! [[ $2 =~ ^-?([0-9]|[1-9][0-9]+)$ ]]; then
echo "\`--index' requires an integer argument: \`$2'" \
| batslib_decorate 'ERROR: refute_line' \
| fail
return $?
fi
is_match_line=1
local -ri idx="$2"
shift 2
;;
-p|--partial) is_mode_partial=1; shift ;;
-e|--regexp) is_mode_regexp=1; shift ;;
--) shift; break ;;
*) break ;;
esac
done
if (( is_mode_partial )) && (( is_mode_regexp )); then
echo "\`--partial' and \`--regexp' are mutually exclusive" \
| batslib_decorate 'ERROR: refute_line' \
| fail
return $?
fi
# Arguments.
local -r unexpected="$1"
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
echo "Invalid extended regular expression: \`$unexpected'" \
| batslib_decorate 'ERROR: refute_line' \
| fail
return $?
fi
# Matching.
if (( is_match_line )); then
# Specific line.
if (( is_mode_regexp )); then
if [[ ${lines[$idx]} =~ $unexpected ]]; then
batslib_print_kv_single 6 \
'index' "$idx" \
'regexp' "$unexpected" \
'line' "${lines[$idx]}" \
| batslib_decorate 'regular expression should not match line' \
| fail
fi
elif (( is_mode_partial )); then
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
batslib_print_kv_single 9 \
'index' "$idx" \
'substring' "$unexpected" \
'line' "${lines[$idx]}" \
| batslib_decorate 'line should not contain substring' \
| fail
fi
else
if [[ ${lines[$idx]} == "$unexpected" ]]; then
batslib_print_kv_single 5 \
'index' "$idx" \
'line' "${lines[$idx]}" \
| batslib_decorate 'line should differ' \
| fail
fi
fi
else
# Line contained in output.
if (( is_mode_regexp )); then
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
if [[ ${lines[$idx]} =~ $unexpected ]]; then
{ local -ar single=( 'regexp' "$unexpected" 'index' "$idx" )
local -a may_be_multi=( 'output' "$output" )
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
batslib_print_kv_single "$width" "${single[@]}"
if batslib_is_single_line "${may_be_multi[1]}"; then
batslib_print_kv_single "$width" "${may_be_multi[@]}"
else
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
batslib_print_kv_multi "${may_be_multi[@]}"
fi
} \
| batslib_decorate 'no line should match the regular expression' \
| fail
return $?
fi
done
elif (( is_mode_partial )); then
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
{ local -ar single=( 'substring' "$unexpected" 'index' "$idx" )
local -a may_be_multi=( 'output' "$output" )
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
batslib_print_kv_single "$width" "${single[@]}"
if batslib_is_single_line "${may_be_multi[1]}"; then
batslib_print_kv_single "$width" "${may_be_multi[@]}"
else
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
batslib_print_kv_multi "${may_be_multi[@]}"
fi
} \
| batslib_decorate 'no line should contain substring' \
| fail
return $?
fi
done
else
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
if [[ ${lines[$idx]} == "$unexpected" ]]; then
{ local -ar single=( 'line' "$unexpected" 'index' "$idx" )
local -a may_be_multi=( 'output' "$output" )
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
batslib_print_kv_single "$width" "${single[@]}"
if batslib_is_single_line "${may_be_multi[1]}"; then
batslib_print_kv_single "$width" "${may_be_multi[@]}"
else
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
batslib_print_kv_multi "${may_be_multi[@]}"
fi
} \
| batslib_decorate 'line should not be in output' \
| fail
return $?
fi
done
fi
fi
}

View File

@ -0,0 +1,199 @@
# refute_output
# =============
#
# Summary: Fail if `$output' matches the unexpected output.
#
# Usage: refute_output [-p | -e] [- | [--] <unexpected>]
#
# Options:
# -p, --partial Match if `unexpected` is a substring of `$output`
# -e, --regexp Treat `unexpected` as an extended regular expression
# -, --stdin Read `unexpected` value from STDIN
# <unexpected> The unexpected value, substring, or regular expression
#
# IO:
# STDIN - [=$1] unexpected output
# STDERR - details, on failure
# error message, on error
# Globals:
# output
# Returns:
# 0 - if output matches the unexpected value/partial/regexp
# 1 - otherwise
#
# This function verifies that a command or function does not produce the unexpected output.
# (It is the logical complement of `assert_output`.)
# Output matching can be literal (the default), partial or by regular expression.
# The unexpected output can be specified either by positional argument or read from STDIN by passing the `-`/`--stdin` flag.
#
# ## Literal matching
#
# By default, literal matching is performed.
# The assertion fails if `$output` equals the unexpected output.
#
# ```bash
# @test 'refute_output()' {
# run echo 'want'
# refute_output 'want'
# }
#
# @test 'refute_output() with pipe' {
# run echo 'hello'
# echo 'world' | refute_output -
# }
#
# @test 'refute_output() with herestring' {
# run echo 'hello'
# refute_output - <<< world
# }
# ```
#
# On failure, the output is displayed.
#
# ```
# -- output equals, but it was expected to differ --
# output : want
# --
# ```
#
# ## Existence
#
# To assert that there is no output at all, omit the matching argument.
#
# ```bash
# @test 'refute_output()' {
# run foo --silent
# refute_output
# }
# ```
#
# On failure, an error message is displayed.
#
# ```
# -- unexpected output --
# expected no output, but output was non-empty
# --
# ```
#
# ## Partial matching
#
# Partial matching can be enabled with the `--partial` option (`-p` for short).
# When used, the assertion fails if the unexpected _substring_ is found in `$output`.
#
# ```bash
# @test 'refute_output() partial matching' {
# run echo 'ERROR: no such file or directory'
# refute_output --partial 'ERROR'
# }
# ```
#
# On failure, the substring and the output are displayed.
#
# ```
# -- output should not contain substring --
# substring : ERROR
# output : ERROR: no such file or directory
# --
# ```
#
# ## Regular expression matching
#
# Regular expression matching can be enabled with the `--regexp` option (`-e` for short).
# When used, the assertion fails if the *extended regular expression* matches `$output`.
#
# *__Note__:
# The anchors `^` and `$` bind to the beginning and the end (respectively) of the entire output;
# not individual lines.*
#
# ```bash
# @test 'refute_output() regular expression matching' {
# run echo 'Foobar v0.1.0'
# refute_output --regexp '^Foobar v[0-9]+\.[0-9]+\.[0-9]$'
# }
# ```
#
# On failure, the regular expression and the output are displayed.
#
# ```
# -- regular expression should not match output --
# regexp : ^Foobar v[0-9]+\.[0-9]+\.[0-9]$
# output : Foobar v0.1.0
# --
# ```
refute_output() {
local -i is_mode_partial=0
local -i is_mode_regexp=0
local -i is_mode_empty=0
local -i use_stdin=0
: "${output?}"
# Handle options.
if (( $# == 0 )); then
is_mode_empty=1
fi
while (( $# > 0 )); do
case "$1" in
-p|--partial) is_mode_partial=1; shift ;;
-e|--regexp) is_mode_regexp=1; shift ;;
-|--stdin) use_stdin=1; shift ;;
--) shift; break ;;
*) break ;;
esac
done
if (( is_mode_partial )) && (( is_mode_regexp )); then
echo "\`--partial' and \`--regexp' are mutually exclusive" \
| batslib_decorate 'ERROR: refute_output' \
| fail
return $?
fi
# Arguments.
local unexpected
if (( use_stdin )); then
unexpected="$(cat -)"
else
unexpected="${1-}"
fi
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
echo "Invalid extended regular expression: \`$unexpected'" \
| batslib_decorate 'ERROR: refute_output' \
| fail
return $?
fi
# Matching.
if (( is_mode_empty )); then
if [ -n "$output" ]; then
batslib_print_kv_single_or_multi 6 \
'output' "$output" \
| batslib_decorate 'output non-empty, but expected no output' \
| fail
fi
elif (( is_mode_regexp )); then
if [[ $output =~ $unexpected ]]; then
batslib_print_kv_single_or_multi 6 \
'regexp' "$unexpected" \
'output' "$output" \
| batslib_decorate 'regular expression should not match output' \
| fail
fi
elif (( is_mode_partial )); then
if [[ $output == *"$unexpected"* ]]; then
batslib_print_kv_single_or_multi 9 \
'substring' "$unexpected" \
'output' "$output" \
| batslib_decorate 'output should not contain substring' \
| fail
fi
else
if [[ $output == "$unexpected" ]]; then
batslib_print_kv_single_or_multi 6 \
'output' "$output" \
| batslib_decorate 'output equals, but it was expected to differ' \
| fail
fi
fi
}

View File

@ -0,0 +1,66 @@
# `refute_regex`
#
# This function is similar to `refute_equal` but uses pattern matching instead
# of equality, by wrapping `! [[ value =~ pattern ]]`.
#
# Fail if the value (first parameter) matches the pattern (second parameter).
#
# ```bash
# @test 'refute_regex()' {
# refute_regex 'WhatsApp' 'Threema'
# }
# ```
#
# On failure, the value, the pattern and the match are displayed.
#
# ```
# @test 'refute_regex()' {
# refute_regex 'WhatsApp' 'What.'
# }
#
# -- value matches regular expression --
# value : WhatsApp
# pattern : What.
# match : Whats
# case : sensitive
# --
# ```
#
# If the value or pattern is longer than one line then it is displayed in
# *multi-line* format.
#
# An error is displayed if the specified extended regular expression is invalid.
#
# For description of the matching behavior, refer to the documentation of the
# `=~` operator in the
# [Bash manual]: https://www.gnu.org/software/bash/manual/html_node/Conditional-Constructs.html.
#
# Note that the `BASH_REMATCH` array is available immediately after the
# assertion fails but is fragile, i.e. prone to being overwritten as a side
# effect of other actions like calling `run`. Thus, it's good practice to avoid
# using `BASH_REMATCH` in conjunction with `refute_regex()`. The valuable
# information the array contains is the matching part of the value which is
# printed in the failing test log, as mentioned above.
refute_regex() {
local -r value="${1}"
local -r pattern="${2}"
if [[ '' =~ ${pattern} ]] || (( ${?} == 2 )); then
echo "Invalid extended regular expression: \`${pattern}'" \
| batslib_decorate 'ERROR: refute_regex' \
| fail
elif [[ "${value}" =~ ${pattern} ]]; then
if shopt -p nocasematch &>/dev/null; then
local case_sensitive=insensitive
else
local case_sensitive=sensitive
fi
batslib_print_kv_single_or_multi 8 \
'value' "${value}" \
'pattern' "${pattern}" \
'match' "${BASH_REMATCH[0]}" \
'case' "${case_sensitive}" \
| batslib_decorate 'value matches regular expression' \
| fail
fi
}

View File

@ -0,0 +1,2 @@
source "$(dirname "${BASH_SOURCE[0]}")/src/file.bash"
source "$(dirname "${BASH_SOURCE[0]}")/src/temp.bash"

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,182 @@
#
# bats-file - Common filesystem assertions and helpers for Bats
#
# Written in 2016 by Zoltan Tombol <zoltan dot tombol at gmail dot com>
#
# To the extent possible under law, the author(s) have dedicated all
# copyright and related and neighboring rights to this software to the
# public domain worldwide. This software is distributed without any
# warranty.
#
# You should have received a copy of the CC0 Public Domain Dedication
# along with this software. If not, see
# <http://creativecommons.org/publicdomain/zero/1.0/>.
#
#
# temp.bash
# ---------
#
# Functions for handling temporary directories.
#
# Create a temporary directory for the current test in `BATS_TMPDIR`,
# and display its path on the standard output.
#
# The directory name is derived from the test's filename and number, and
# a random string for uniqueness.
#
# <test-filename>-<test-number>-<random-string>
#
# When `--prefix <prefix>' is specified, `<prefix>' is prepended to the
# directory name.
#
# <prefix><test-filename>-<test-number>-<random-string>
#
# Must be called from `setup', `@test' or `teardown'.
#
# Example:
#
# setup() {
# TEST_TEMP_DIR="$(temp_make --prefix 'myapp-')"
# }
#
# teardown() {
# temp_del "$TEST_TEMP_DIR"
# }
#
# Globals:
# BATS_TEST_NAME
# BATS_TEST_FILENAME
# BATS_TEST_NUMBER
# BATS_TMPDIR
# Arguments:
# none
# Options:
# -p, --prefix <prefix> - prefix the directory name with `<prefix>'
# Returns:
# 0 - on success
# 1 - otherwise
# Outputs:
# STDOUT - path of temporary directory
# STDERR - error messages
temp_make() {
# Check caller.
if ! ( batslib_is_caller --indirect 'setup' \
|| batslib_is_caller --indirect 'setup_file' \
|| batslib_is_caller --indirect "$BATS_TEST_NAME" \
|| batslib_is_caller --indirect 'teardown' \
|| batslib_is_caller --indirect 'teardown_file' )
then
echo "Must be called from \`setup', \`@test' or \`teardown'" \
| batslib_decorate 'ERROR: temp_make' \
| fail
return $?
fi
# Handle options.
local prefix=''
while (( $# > 0 )); do
case "$1" in
-p|--prefix)
if (( $# < 2 )); then
echo "\`--prefix' requires an argument" \
| batslib_decorate 'ERROR: temp_make' \
| fail
return $?
fi
prefix="$2"
shift 2
;;
--) shift; break ;;
*) break ;;
esac
done
# Create directory.
local template="$prefix"
template+="${BATS_TEST_FILENAME##*/}"
template+="-${BATS_TEST_NUMBER}"
template+='-XXXXXXXXXX'
local path
path="$(mktemp -d -- "${BATS_TMPDIR}/${template}" 2>&1)"
if (( $? )); then
echo "$path" \
| batslib_decorate 'ERROR: temp_make' \
| fail
return $?
fi
echo "$path"
}
# Delete a temporary directory, typically created with `temp_make', and
# its contents.
#
# Note: Actually, this function can be used to delete any file or
# directory. However, it is most useful in deleting temporary
# directories created with `temp_make', hence the naming.
#
# For development and debugging, deletion can be prevented using
# environment variables.
#
# When `BATSLIB_TEMP_PRESERVE' is set to 1, the function succeeds but
# the directory is not deleted.
#
# When `BATSLIB_TEMP_PRESERVE_ON_FAILURE' is set to 1 and `temp_del' is
# called, directly or indirectly, from `teardown', the function succeeds
# but the directory is not deleted if the test has failed.
#
# Example:
#
# setup() {
# TEST_TEMP_DIR="$(temp_make --prefix 'myapp-')"
# }
#
# teardown() {
# temp_del "$TEST_TEMP_DIR"
# }
#
# Globals:
# BATSLIB_TEMP_PRESERVE
# BATSLIB_TEMP_PRESERVE_ON_FAILURE
# BATS_TEST_COMPLETED
# Arguments:
# $1 - path of directory
# Returns:
# 0 - on success
# 1 - otherwise
# Outputs:
# STDERR - error messages
temp_del() {
local -r path="$1"
# Environment variables.
if [[ ${BATSLIB_TEMP_PRESERVE-} == '1' ]]; then
return 0
elif [[ ${BATSLIB_TEMP_PRESERVE_ON_FAILURE-} == '1' ]]; then
# Check caller.
if ! ( batslib_is_caller --indirect 'teardown' \
|| batslib_is_caller --indirect 'teardown_file' )
then
echo "Must be called from \`teardown' or \`teardown_file' when using \`BATSLIB_TEMP_PRESERVE_ON_FAILURE'" \
| batslib_decorate 'ERROR: temp_del' \
| fail
return $?
fi
(( ${BATS_TEST_COMPLETED:-0} != 1 )) && return 0
fi
# Delete directory.
local result
result="$(rm -r -- "$path" 2>&1 </dev/null)"
if (( $? )); then
echo "$result" \
| batslib_decorate 'ERROR: temp_del' \
| fail
return $?
fi
}

View File

@ -0,0 +1,3 @@
source "$(dirname "${BASH_SOURCE[0]}")/src/output.bash"
source "$(dirname "${BASH_SOURCE[0]}")/src/error.bash"
source "$(dirname "${BASH_SOURCE[0]}")/src/lang.bash"

View File

@ -0,0 +1,41 @@
#
# bats-support - Supporting library for Bats test helpers
#
# Written in 2016 by Zoltan Tombol <zoltan dot tombol at gmail dot com>
#
# To the extent possible under law, the author(s) have dedicated all
# copyright and related and neighboring rights to this software to the
# public domain worldwide. This software is distributed without any
# warranty.
#
# You should have received a copy of the CC0 Public Domain Dedication
# along with this software. If not, see
# <http://creativecommons.org/publicdomain/zero/1.0/>.
#
#
# error.bash
# ----------
#
# Functions implementing error reporting. Used by public helper
# functions or test suits directly.
#
# Fail and display a message. When no parameters are specified, the
# message is read from the standard input. Other functions use this to
# report failure.
#
# Globals:
# none
# Arguments:
# $@ - [=STDIN] message
# Returns:
# 1 - always
# Inputs:
# STDIN - [=$@] message
# Outputs:
# STDERR - message
fail() {
(( $# == 0 )) && batslib_err || batslib_err "$@"
return 1
}

View File

@ -0,0 +1,73 @@
#
# bats-util - Various auxiliary functions for Bats
#
# Written in 2016 by Zoltan Tombol <zoltan dot tombol at gmail dot com>
#
# To the extent possible under law, the author(s) have dedicated all
# copyright and related and neighboring rights to this software to the
# public domain worldwide. This software is distributed without any
# warranty.
#
# You should have received a copy of the CC0 Public Domain Dedication
# along with this software. If not, see
# <http://creativecommons.org/publicdomain/zero/1.0/>.
#
#
# lang.bash
# ---------
#
# Bash language and execution related functions. Used by public helper
# functions.
#
# Check whether the calling function was called from a given function.
#
# By default, direct invocation is checked. The function succeeds if the
# calling function was called directly from the given function. In other
# words, if the given function is the next element on the call stack.
#
# When `--indirect' is specified, indirect invocation is checked. The
# function succeeds if the calling function was called from the given
# function with any number of intermediate calls. In other words, if the
# given function can be found somewhere on the call stack.
#
# Direct invocation is a form of indirect invocation with zero
# intermediate calls.
#
# Globals:
# FUNCNAME
# Options:
# -i, --indirect - check indirect invocation
# Arguments:
# $1 - calling function's name
# Returns:
# 0 - current function was called from the given function
# 1 - otherwise
batslib_is_caller() {
local -i is_mode_direct=1
# Handle options.
while (( $# > 0 )); do
case "$1" in
-i|--indirect) is_mode_direct=0; shift ;;
--) shift; break ;;
*) break ;;
esac
done
# Arguments.
local -r func="$1"
# Check call stack.
if (( is_mode_direct )); then
[[ $func == "${FUNCNAME[2]}" ]] && return 0
else
local -i depth
for (( depth=2; depth<${#FUNCNAME[@]}; ++depth )); do
[[ $func == "${FUNCNAME[$depth]}" ]] && return 0
done
fi
return 1
}

View File

@ -0,0 +1,279 @@
#
# bats-support - Supporting library for Bats test helpers
#
# Written in 2016 by Zoltan Tombol <zoltan dot tombol at gmail dot com>
#
# To the extent possible under law, the author(s) have dedicated all
# copyright and related and neighboring rights to this software to the
# public domain worldwide. This software is distributed without any
# warranty.
#
# You should have received a copy of the CC0 Public Domain Dedication
# along with this software. If not, see
# <http://creativecommons.org/publicdomain/zero/1.0/>.
#
#
# output.bash
# -----------
#
# Private functions implementing output formatting. Used by public
# helper functions.
#
# Print a message to the standard error. When no parameters are
# specified, the message is read from the standard input.
#
# Globals:
# none
# Arguments:
# $@ - [=STDIN] message
# Returns:
# none
# Inputs:
# STDIN - [=$@] message
# Outputs:
# STDERR - message
batslib_err() {
{ if (( $# > 0 )); then
echo "$@"
else
cat -
fi
} >&2
}
# Count the number of lines in the given string.
#
# TODO(ztombol): Fix tests and remove this note after #93 is resolved!
# NOTE: Due to a bug in Bats, `batslib_count_lines "$output"' does not
# give the same result as `${#lines[@]}' when the output contains
# empty lines.
# See PR #93 (https://github.com/sstephenson/bats/pull/93).
#
# Globals:
# none
# Arguments:
# $1 - string
# Returns:
# none
# Outputs:
# STDOUT - number of lines
batslib_count_lines() {
local -i n_lines=0
local line
while IFS='' read -r line || [[ -n $line ]]; do
(( ++n_lines ))
done < <(printf '%s' "$1")
echo "$n_lines"
}
# Determine whether all strings are single-line.
#
# Globals:
# none
# Arguments:
# $@ - strings
# Returns:
# 0 - all strings are single-line
# 1 - otherwise
batslib_is_single_line() {
for string in "$@"; do
(( $(batslib_count_lines "$string") > 1 )) && return 1
done
return 0
}
# Determine the length of the longest key that has a single-line value.
#
# This function is useful in determining the correct width of the key
# column in two-column format when some keys may have multi-line values
# and thus should be excluded.
#
# Globals:
# none
# Arguments:
# $odd - key
# $even - value of the previous key
# Returns:
# none
# Outputs:
# STDOUT - length of longest key
batslib_get_max_single_line_key_width() {
local -i max_len=-1
while (( $# != 0 )); do
local -i key_len="${#1}"
batslib_is_single_line "$2" && (( key_len > max_len )) && max_len="$key_len"
shift 2
done
echo "$max_len"
}
# Print key-value pairs in two-column format.
#
# Keys are displayed in the first column, and their corresponding values
# in the second. To evenly line up values, the key column is fixed-width
# and its width is specified with the first parameter (possibly computed
# using `batslib_get_max_single_line_key_width').
#
# Globals:
# none
# Arguments:
# $1 - width of key column
# $even - key
# $odd - value of the previous key
# Returns:
# none
# Outputs:
# STDOUT - formatted key-value pairs
batslib_print_kv_single() {
local -ir col_width="$1"; shift
while (( $# != 0 )); do
printf '%-*s : %s\n' "$col_width" "$1" "$2"
shift 2
done
}
# Print key-value pairs in multi-line format.
#
# The key is displayed first with the number of lines of its
# corresponding value in parenthesis. Next, starting on the next line,
# the value is displayed. For better readability, it is recommended to
# indent values using `batslib_prefix'.
#
# Globals:
# none
# Arguments:
# $odd - key
# $even - value of the previous key
# Returns:
# none
# Outputs:
# STDOUT - formatted key-value pairs
batslib_print_kv_multi() {
while (( $# != 0 )); do
printf '%s (%d lines):\n' "$1" "$( batslib_count_lines "$2" )"
printf '%s\n' "$2"
shift 2
done
}
# Print all key-value pairs in either two-column or multi-line format
# depending on whether all values are single-line.
#
# If all values are single-line, print all pairs in two-column format
# with the specified key column width (identical to using
# `batslib_print_kv_single').
#
# Otherwise, print all pairs in multi-line format after indenting values
# with two spaces for readability (identical to using `batslib_prefix'
# and `batslib_print_kv_multi')
#
# Globals:
# none
# Arguments:
# $1 - width of key column (for two-column format)
# $even - key
# $odd - value of the previous key
# Returns:
# none
# Outputs:
# STDOUT - formatted key-value pairs
batslib_print_kv_single_or_multi() {
local -ir width="$1"; shift
local -a pairs=( "$@" )
local -a values=()
local -i i
for (( i=1; i < ${#pairs[@]}; i+=2 )); do
values+=( "${pairs[$i]}" )
done
if batslib_is_single_line "${values[@]}"; then
batslib_print_kv_single "$width" "${pairs[@]}"
else
local -i i
for (( i=1; i < ${#pairs[@]}; i+=2 )); do
pairs[$i]="$( batslib_prefix < <(printf '%s' "${pairs[$i]}") )"
done
batslib_print_kv_multi "${pairs[@]}"
fi
}
# Prefix each line read from the standard input with the given string.
#
# Globals:
# none
# Arguments:
# $1 - [= ] prefix string
# Returns:
# none
# Inputs:
# STDIN - lines
# Outputs:
# STDOUT - prefixed lines
batslib_prefix() {
local -r prefix="${1:- }"
local line
while IFS='' read -r line || [[ -n $line ]]; do
printf '%s%s\n' "$prefix" "$line"
done
}
# Mark select lines of the text read from the standard input by
# overwriting their beginning with the given string.
#
# Usually the input is indented by a few spaces using `batslib_prefix'
# first.
#
# Globals:
# none
# Arguments:
# $1 - marking string
# $@ - indices (zero-based) of lines to mark
# Returns:
# none
# Inputs:
# STDIN - lines
# Outputs:
# STDOUT - lines after marking
batslib_mark() {
local -r symbol="$1"; shift
# Sort line numbers.
set -- $( sort -nu <<< "$( printf '%d\n' "$@" )" )
local line
local -i idx=0
while IFS='' read -r line || [[ -n $line ]]; do
if (( ${1:--1} == idx )); then
printf '%s\n' "${symbol}${line:${#symbol}}"
shift
else
printf '%s\n' "$line"
fi
(( ++idx ))
done
}
# Enclose the input text in header and footer lines.
#
# The header contains the given string as title. The output is preceded
# and followed by an additional newline to make it stand out more.
#
# Globals:
# none
# Arguments:
# $1 - title
# Returns:
# none
# Inputs:
# STDIN - text
# Outputs:
# STDOUT - decorated text
batslib_decorate() {
echo
echo "-- $1 --"
cat -
echo '--'
echo
}

403
extras/portable/tomb Executable file
View File

@ -0,0 +1,403 @@
#!/bin/sh
#
# Tomb v3, the Crypto Undertaker portable version 3
#
# A commandline tool to easily operate encryption of secret data
#
# {{{ License
# Copyright (C) 2007-2022 Dyne.org Foundation
#
# Tomb is designed, written and maintained by Denis Roio <jaromil@dyne.org>
#
# Please refer to the AUTHORS file for more information.
#
# This source code is free software; you can redistribute it and/or
# modify it under the terms of the GNU Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This source code is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Please refer
# to the GNU Public License for more details.
#
# You should have received a copy of the GNU Public License along with
# this source code; if not, , see <https://www.gnu.org/licenses/>.
# }}} - License
# {{{ Global variables
VERSION="3.1.0"
DATE="Nov/2022"
TOMBEXEC="$0"
TMPDIR="${TMP:-/tmp}"
TOMBTMPFILES=""
PATH="${PATH}:.:/usr/local/bin"
exitcode=0
# }}}
# {{{ Logging functions
_success() {
echo "[*] " "$1" "$2" "$3" "$4" 1>&2
}
_message() {
echo " . " "$1" "$2" "$3" "$4" 1>&2
}
_warning() {
echo "[W] " "$1" "$2" "$3" "$4" 1>&2
}
_verbose() {
echo "[D] " "$1" "$2" "$3" "$4" 1>&2
}
_error() {
echo "[!] " "$1" "$2" "$3" "$4" 1>&2
exitcode=1
}
_failure() {
echo "[!!] " "$1" "$2" "$3" "$4" 1>&2
exitcode=1
exit 1
}
# }}}
_success "Starting Tomb v$VERSION"
# {{{ Internal functions
_random_string() {
len=${1:-32}
[ "$1" != "" ] && {
echo "print(O.random($len):base58())" | zenroom 2>/dev/null
}
return $?
}
_tmp_create() {
[ -d "$TMPDIR" ] || {
# we create the tempdir with the sticky bit on
mkdir -m 1777 "$TMPDIR"
[ $? = 0 ] || {
_failure "Fatal error creating the temporary directory: %s" "$TMPDIR"
}
}
tfile="${TMPDIR}/`_random_string 64`" # Temporary file
umask 066
[ $? = 0 ] || {
_failure "Fatal error setting the permission umask for temporary files"
}
[ -r "$tfile" ] && {
_failure "Someone is messing up with us trying to hijack temporary files."
}
touch "$tfile"
[ $? = 0 ] || {
_failure "Fatal error creating a temporary file: %s" "$tfile"
}
_verbose "Created tempfile: ::1 temp file::" "$tfile"
TOMBTMP="$tfile"
TOMBTMPFILES="$TOMBTMPFILES:$tfile"
return 0
}
# }}}
# {{{ FreeBSD
freebsd_mount() {
file="$1"
mnt="$2"
_verbose `veracrypt -l "$file"`
loop=`veracrypt -l "$file" | awk '{print $3}'`
_verbose "fsck $loop"
fsck.ext4 -p -C0 "$loop"
lklfuse -o type=ext4 "${loop}" "$mnt"
return $?
}
freebsd_close() {
file="$1"
md=`veracrypt -l "$file" | awk '{print $3}'`
# umount "$mnt"
_verbose "md: $md"
mnt=`pgrep -lf "lklfuse.*$md" | awk '{print $6}'`
_verbose "mnt: $mnt"
lkl=`pgrep -f "lklfuse.*$md" | awk '{print $1}'`
_verbose "kill $lkl (lklfuse on $md)"
# trying to deail with lklfuse bug
# https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=239831
renice -20 $lkl
kill $lkl
sync
sleep 1
kill -9 $lkl
# lkl should have really exited now
_verbose "veracrypt -d $file"
veracrypt --text --non-interactive -d "$file"
return $?
}
# }}}
# {{{ Linux
# usage: _mount /tmp/.veracrypt_ mountpoint
linux_mount() {
file="$1"
mnt="$2"
veralist=`veracrypt -l "$file" | awk '{print($2,":",$3,":",$4)}'`
[ "$veralist" = "" ] && {
_error "Cannot mount tomb not yet mapped " "$file"
return 1
}
loop=`echo $veralist | cut -d: -f2 | xargs`
[ "`echo $veralist | cut -d: -f3 | xargs`" != "-" ] && {
_error "Tomb already mounted " "$file on $loop"
return 1
}
_verbose "fsck $loop"
fsck.ext4 -p -C0 "$loop" 1>&2
_verbose "linux_mount $mnt"
mount "$loop" "$mnt"
return $?
}
# }}}
# {{{ POSIX portable
# usage: echo PASSWORD | posix_create file size pim
posix_create() {
file="$1" # must not exist
size="$2" # size in bytes
pim="$3" # any number
_verbose "posix_create $file $size $pim"
veracrypt --text --non-interactive --stdin \
-m nokernelcrypto \
-c "$file" --volume-type normal \
--hash sha512 --encryption serpent-aes \
--filesystem none --size "${size}" --pim "$pim" \
--random-source /dev/urandom -k ''
return $?
}
posix_format() {
file="$1"
loop=`veracrypt -l "$file" | awk '{print $3}'`
_verbose "posix_format: ${loop}"
mkfs.ext4 -L "`basename $file`" "$loop" # -E root_owner="${user_uid}:${user_gid}" "$loop"
return $?
}
# usage: echo PASSWORD | posix_map file pim
posix_map() {
file="$1"
pim="$2"
_verbose "posix_map $file $pim"
veracrypt --text --non-interactive --stdin \
--protect-hidden no -m nokernelcrypto \
-k '' --pim "$pim" --filesystem none \
"$file"
return $?
}
posix_close() {
file="$1"
_verbose "posix_close $file"
veracrypt --text --non-interactive -d "$file"
return $?
}
# }}}
# {{{ Initialization
system="unknown"
create=""
format=""
map=""
mount=""
close=""
tomb_init() {
system="`uname -s`"
case "$system" in
FreeBSD)
cat <<EOF
create=posix_create
format=posix_format
map=posix_map
mount=freebsd_mount
close=freebsd_close
EOF
;;
Linux)
cat <<EOF
create=posix_create
format=posix_format
map=posix_map
mount=linux_mount
close=posix_close
EOF
;;
*)
_failure "Unsupported system: %s" "$system"
esac
echo "system=$system"
# _verbose "system detected: %s" $system
}
# }}}
# {{{ Main
[ "$1" = "source" ] && return 0
eval "`tomb_init`"
tombfile=""
tombsize=""
PIM=69
cmd="$1"
shift 1
case "$cmd" in
dig)
args=2
while [ $args -gt 0 ]; do
[ "$1" = '-s' ] && { tombsize="$2"; shift 2; }
[ "$1" != "" ] && { tombfile="$1"; shift 1; }
args=$(( $args - 1 ))
done
[ "$tombfile" = "" ] && _failure "Missing path to tomb"
[ "$tombsize" = "" ] && _failure "Size argument missing, use -s"
# TODO: check if size is integer
# [ "$tombsize" -ge 10 ] || _failure "Tombs can't be smaller than 10 mebibytes"
[ -e "$tombfile" ] && {
_error "A tomb exists already. I'm not digging here:"
ls -l "$tombfile"; exit 1
}
_message "Commanded to dig tomb " "$tombfile" " sized $tombsize MiB"
touch "$tombfile" || _failure "Error creating the tomb " "$tombfile"
# dd if=/dev/urandom bs=1048576 count=$tombsize of="$tombfile" || {
# _failure "Error creating the tomb " "$tombfile"
# exit 1
# }
bytesize="$(( $tombsize * 1048576 ))"
[ "$bytesize" = "" ] &&
_failure "Error reading tomb size " "$tombsize"
if [ "$system" = "Linux" ]; then
fallocate -x -l "$bytesize" "$tombfile" ||
_failure "Error creating the tomb " "$tombfile"
else
dd if=/dev/zero of="$tombfile" count="$tombsize" bs=1048576 ||
_failure "Error creating the tomb " "$tombfile of $tombsize MiB"
fi
;;
forge)
args=1
[ "$1" = "" ] && _failure "Missing argument in command " "forge"
while [ $args -gt 0 ]; do
case "$1" in
'-k') tombkey="$2"; shift 2 ;;
*) tombkey="$1"; shift 1 ;;
esac
args=$(( $args - 1 ))
done
[ "$tombkey" = "" ] && _failure "Missing path to tomb key"
[ -e "$tombkey" ] && {
_error "A tomb key exists already. I'm not forging here:"
ls -l "$tombkey"; exit 1
}
_message "Commanded to forge tomb key " "$tombkey"
touch "$tombkey" || _failure "Error creating the tomb key " "$tombkey"
cat <<EOF | zenroom -z | cut -d\" -f4 > "$tombkey"
rule check version 2.0.0
Given nothing
When I create the random object of '64' bytes
Then print the 'random object' as 'hex'
EOF
;;
lock)
args=2
while [ $args -gt 0 ]; do
case "$1" in
'-k') tombkey="$2"; shift 2 ;;
*) tombfile="$1"; shift 1 ;;
esac
args=$(( $args - 1 ))
done
case "$system" in
Linux)
bytesize=`stat "$tombfile" | awk '/Size:/ {print $2; exit}'`
;;
*)
bytesize=`stat -f '%z' "$tombfile"`
;;
esac
_message "Commanded to lock tomb " "$tombfile with key $tombkey"
[ "$tombfile" = "" ] && _failure "Missing path to tomb"
[ -r "$tombfile" ] || _failure "Tomb file not readable"
[ "$tombkey" = "" ] && _failure "Missing path to key"
[ -r "$tombkey" ] || _failure "Key file not readable"
"$create" "$tombfile" "$bytesize" "$PIM" < "$tombkey" || {
_failure "Error creating the tomb " "$tombfile"
exit 1
}
"$map" "$tombfile" "$PIM" < "$tombkey" ||
_failure "Error mapping the tomb " "$tombfile with key $tombkey"
"$format" "$tombfile" ||
_error "Error formatting tomb " "$tombfile"
"$close" "$tombfile"
[ $exitcode = 0 ] &&
_success "Success locking tomb " "$tombfile with key $tombkey"
;;
open)
args=3
while [ $args -gt 0 ]; do
case "$1" in
'-k') tombkey="$2"; shift 2 ;;
*) if [ "$tombfile" = "" ]; then
tombfile="$1"
else
tombmount="$1"
fi
shift 1 ;;
esac
args=$(( $args - 1 ))
done
_message "Commanded to open tomb " "$tombfile with key $tombkey on $tombmount"
[ "$tombfile" = "" ] && _failure "Missing path to tomb"
[ -r "$tombfile" ] || _failure "Tomb file not readable"
[ "$tombkey" = "" ] && _failure "Missing path to key"
[ -r "$tombkey" ] || _failure "Key file not readable"
[ "$tombmount" = "" ] && tombmount="`basename $tombfile`"
"$map" "$tombfile" "$PIM" < "$tombkey"
[ $? != 0 ] &&
_failure "Error mapping the tomb " "$tombfile with key $tombkey"
"$mount" "$tombfile" "$tombmount" || {
"$close" "$tombfile"
_failure "Error mounting the tomb " "$tombfile on $tombmount"
}
;;
close)
# args=1
if [ "$1" = "all" ]; then
"$close"
elif [ -e "$1" ]; then
"$close" "`realpath $1`" ||
_failure "Error closing " "$1"
elif [ "$1" = "" ]; then
_failure "Missing argument: tomb file or mountpoint"
else
_failure "Wrong argument: $1"
fi
;;
list)
veracrypt -l
;;
esac
# }}}
exit 0

View File

@ -3,3 +3,9 @@ Build with 'qmake' and then 'make'
Requires QT version 5.4 or above
To install copy the binary in your PATH
then all contents of pixmaps to /usr/share/pixmaps
and all contents of i18n to /usr/share/i18n
The prefix /usr/local may be preferred if ever packaged.

View File

@ -11,20 +11,32 @@ test_expect_success 'Testing tomb creation: dig, forge and lock' '
tt_forge --tomb-pwd $DUMMYPASS &&
print $DUMMYPASS \
| gpg --batch --passphrase-fd 0 --no-tty --no-options -d $tomb_key \
| hexdump -C &&
| xxd &&
tt_lock --tomb-pwd $DUMMYPASS
'
if test_have_prereq SPHINX ORACLE; then
test_export "sphinx_test"
test_expect_success 'Testing tomb creation: dig, forge and lock (sphinx password handling)' '
tt_dig -s 20 &&
tt_forge --tomb-pwd $DUMMYPASS --sphx-user $DUMMYUSER --sphx-host $DUMMYHOST &&
print $(echo $DUMMYPASS | sphinx get $DUMMYUSER $DUMMYHOST) \
| gpg --batch --passphrase-fd 0 --no-tty --no-options -d $tomb_key \
| hexdump -C &&
tt_lock --tomb-pwd $DUMMYPASS --sphx-user $DUMMYUSER --sphx-host $DUMMYHOST
'
tt_dig -s 20 &&
tt_forge --tomb-pwd $DUMMYPASS --sphx-user $DUMMYUSER --sphx-host $DUMMYHOST &&
print $(echo $DUMMYPASS | sphinx get $DUMMYUSER $DUMMYHOST) \
| gpg --batch --passphrase-fd 0 --no-tty --no-options -d $tomb_key \
| xxd &&
tt_lock --tomb-pwd $DUMMYPASS --sphx-user $DUMMYUSER --sphx-host $DUMMYHOST
'
fi
if test_have_prereq DOAS; then
test_export "doas_test"
test_expect_success 'Testing tomb creation: dig, forge and lock (using doas instead of sudo)' '
tt_dig --sudo doas -s 20 &&
tt_forge --sudo doas --tomb-pwd $DUMMYPASS &&
print $DUMMYPASS \
| gpg --batch --passphrase-fd 0 --no-tty --no-options -d $tomb_key \
| xxd &&
tt_lock --sudo doas --tomb-pwd $DUMMYPASS
'
fi
test_done

View File

@ -52,5 +52,12 @@ if test_have_prereq SPHINX ORACLE; then
'
fi
if test_have_prereq DOAS; then
test_export "doas_test" # Using already generated tomb
test_expect_success 'Testing open with good password (using doas instead of sudo)' '
tt_open --sudo doas --tomb-pwd $DUMMYPASS &&
tt_close --sudo doas
'
fi
test_done

View File

@ -4,6 +4,8 @@ export test_description="Testing tomb with GnuPG keys"
source ./setup
if test_have_prereq GPGRCPT; then
test_export "recipient"
test_expect_success 'Testing tomb with GnuPG keys: creation' '
tt_dig -s 20 &&
@ -54,4 +56,6 @@ test_expect_success 'Testing tomb creation with untrusted GnuPG keys' '
test_must_fail tt_forge -g -r $KEY_UNTRUSTED
'
fi
test_done

View File

@ -11,7 +11,7 @@ if test_have_prereq KDF; then
tt_forge --tomb-pwd $DUMMYPASS --kdf 1 &&
print $DUMMYPASS \
| gpg --batch --passphrase-fd 0 --no-tty --no-options -d $tomb_key \
| hexdump -C &&
| xxd &&
tt_lock --tomb-pwd $DUMMYPASS --kdf 1
'

View File

@ -9,13 +9,15 @@ if test_have_prereq RESIZER; then
test_expect_success 'Testing resize to 30 MB tomb' '
tt resize -s 30 $tomb -k $tomb_key --unsafe --tomb-pwd $DUMMYPASS
'
if test_have_prereq GPGRCPT; then
test_export "recipient" # Using already generated tomb
test_expect_success 'Testing resize to 30 MB tomb with GnuPG keys' '
tt resize -s 30 $tomb -k $tomb_key -g -r $KEY2
'
fi
fi # RESIZER
if test_have_prereq RESIZER SPHINX ORACLE; then
test_export "sphinx_test" # Using already generated tomb
test_expect_success 'Testing resize to 30 MB tomb (sphinx)' '

View File

@ -6,24 +6,28 @@ source ./setup
test_export "test" # Using already generated tomb
test_expect_success 'Testing tomb with GnuPG keys: passwd' '
tt passwd -k $tomb_key --unsafe \
tt passwd -f -k $tomb_key --unsafe \
--tomb-old-pwd $DUMMYPASS --tomb-pwd $DUMMYPASSNEW &&
tt passwd -k $tomb_key --unsafe \
tt passwd -f -k $tomb_key --unsafe \
--tomb-old-pwd $DUMMYPASSNEW --tomb-pwd $DUMMYPASS
'
if test_have_prereq GPGRCPT; then
test_export "recipient" # Using already generated tomb
test_expect_success 'Testing tomb with GnuPG keys: passwd' '
tt passwd -k $tomb_key -g -r $KEY2
tt passwd -f -k $tomb_key -g -r $KEY2
'
fi
if test_have_prereq SPHINX ORACLE; then
test_export "sphinx_test" # Using already generated tomb
test_expect_success 'Testing changing tomb password with sphinx' '
tt passwd -k $tomb_key --unsafe \
tt passwd -f -k $tomb_key --unsafe \
--tomb-old-pwd $DUMMYPASS --tomb-pwd $DUMMYPASSNEW \
--sphx-user $DUMMYUSER --sphx-host $DUMMYHOST &&
tt passwd -k $tomb_key --unsafe \
tt passwd -f -k $tomb_key --unsafe \
--tomb-old-pwd $DUMMYPASSNEW --tomb-pwd $DUMMYPASS \
--sphx-user $DUMMYUSER --sphx-host $DUMMYHOST
'

View File

@ -18,10 +18,13 @@ test_expect_success 'Testing bind hooks' '
tt_close &&
touch "$TEMPHOME/$bindtest" &&
tt_open --tomb-pwd $DUMMYPASS &&
RND2=$(cat "$TEMPHOME/$bindtest") &&
[[ "$RND" = "$RND2" ]] &&
echo "$RND" &&
cat "$TEMPHOME/$bindtest" &&
tt list $testname &&
tt_close
'
# RND2=$(cat "$TEMPHOME/$bindtest") &&
# [[ "$RND" = "$RND2" ]] &&
test_done

View File

@ -33,6 +33,7 @@ if test_have_prereq STEGHIDE; then
tt_close
'
if test_have_prereq GPGRCPT; then
test_export "recipient" # Using already generated tomb
cp -f "$TEST_HOME/arditi.jpg" "$tomb_img"
test_expect_success 'Testing tomb with GnuPG keys and steganographic: bury' '
@ -60,7 +61,9 @@ if test_have_prereq STEGHIDE; then
tt open -k $tomb_img $tomb -g --unsafe --tomb-pwd $DUMMYPASS &&
tt_close
'
fi
fi # GPGRCPT
fi # STEGHIDE
if test_have_prereq PYTHON2 CLOAKIFY DECLOAKIFY; then
test_expect_success 'Testing tomb and steganographic: cloak' '

View File

@ -6,41 +6,43 @@ source ./setup
test_export "test" # Using already generated tomb
test_expect_success 'Testing set key' '
tt forge -k $tomb_key_new --tomb-pwd $DUMMYPASS \
tt forge -f -k $tomb_key_new --tomb-pwd $DUMMYPASS \
--ignore-swap --unsafe --force &&
tt setkey -k $tomb_key_new $tomb_key $tomb \
tt setkey -f -k $tomb_key_new $tomb_key $tomb \
--unsafe --tomb-pwd $DUMMYPASS --tomb-old-pwd $DUMMYPASS &&
tt open -k $tomb_key_new $tomb \
tt open -f -k $tomb_key_new $tomb \
--unsafe --tomb-pwd $DUMMYPASS &&
print $DUMMYPASS \
| gpg --batch --passphrase-fd 0 --no-tty --no-options -d $tomb_key_new \
| hexdump -C &&
| xxd &&
tt_close
'
if test_have_prereq GPGRCPT; then
test_export "recipient" # Using already generated tomb
test_expect_success 'Testing tomb with GnuPG keys: setkey' '
tt forge $tomb_key_new -g -r $KEY2 --ignore-swap --unsafe &&
tt setkey -k $tomb_key_new $tomb_key $tomb -g -r $KEY2 &&
tt open -k $tomb_key_new $tomb -g &&
tt forge -f $tomb_key_new -g -r $KEY2 --ignore-swap --unsafe &&
tt setkey -f -k $tomb_key_new $tomb_key $tomb -g -r $KEY2 &&
tt open -f -k $tomb_key_new $tomb -g &&
tt_close
'
fi
if test_have_prereq SPHINX ORACLE; then
test_export "sphinx_test" # Using already generated tomb
test_expect_success 'Testing set key (sphinx)' '
tt forge -k $tomb_key_new --tomb-pwd $DUMMYPASS \
tt forge -f -k $tomb_key_new --tomb-pwd $DUMMYPASS \
--ignore-swap --unsafe --force \
--sphx-user $DUMMYUSER --sphx-host $DUMMYHOST &&
tt setkey -k $tomb_key_new $tomb_key $tomb \
tt setkey -f -k $tomb_key_new $tomb_key $tomb \
--unsafe --tomb-pwd $DUMMYPASS --tomb-old-pwd $DUMMYPASS \
--sphx-user $DUMMYUSER --sphx-host $DUMMYHOST &&
tt open -k $tomb_key_new $tomb \
tt open -f -k $tomb_key_new $tomb \
--unsafe --tomb-pwd $DUMMYPASS \
--sphx-user $DUMMYUSER --sphx-host $DUMMYHOST &&
print $DUMMYPASS \
| gpg --batch --passphrase-fd 0 --no-tty --no-options -d $tomb_key_new \
| hexdump -C &&
| xxd &&
tt_close
'
fi

View File

@ -1,13 +1,14 @@
FROM dyne/devuan:beowulf
FROM dyne/devuan:chimaera
RUN apt-get update -y -q --allow-releaseinfo-change && apt-get install -y -q zsh cryptsetup gawk libgcrypt20-dev steghide qrencode python python2.7 python3-pip python3-dev libsodium-dev libssl-dev make gcc g++ sudo gettext file bsdmainutils
RUN echo "deb http://deb.devuan.org/merged chimaera main" >> /etc/apt/sources.list
RUN apt-get update -y -q --allow-releaseinfo-change
RUN apt-get install -y -q zsh cryptsetup gpg gawk libgcrypt20-dev steghide qrencode python python2.7 python3-pip python3-dev libssl-dev make gcc sudo gettext bsdmainutils file pinentry-curses xxd libsodium23 libsodium-dev doas
RUN pip3 install setuptools wheel
COPY . /Tomb/
# WORKDIR /Tomb/extras
# RUN ./install_sphinx.sh
# RUN cp test/sphinx.cfg /etc/sphinx/config
COPY extras/test/doas.conf /etc/doas.conf
RUN chmod 400 /etc/doas.conf
WORKDIR /Tomb
RUN make --directory=extras/kdf-keys

20
extras/test/doas.conf Normal file
View File

@ -0,0 +1,20 @@
permit nopass root cmd losetup
permit nopass root cmd lsblk
permit nopass root cmd mkfs.ext3
permit nopass root cmd mkfs.ext4
permit nopass root cmd mkfs.btrfs
permit nopass root cmd touch
permit nopass root cmd fsck
permit nopass root cmd btrfs
permit nopass root cmd tune2fs
permit nopass root cmd mkdir
permit nopass root cmd mount
permit nopass root cmd rmdir
permit nopass root cmd chown
permit nopass root cmd umount
permit nopass root cmd findmnt
permit nopass root cmd e2fsck
permit nopass root cmd resize2fs
permit nopass root cmd lsof
permit nopass root cmd kill
permit nopass root cmd cryptsetup

View File

@ -100,7 +100,7 @@ test-tomb-create() {
notice "Dump of clear key contents to examine them:"
print ${dummypass} \
| gpg --batch --passphrase-fd 0 --no-tty --no-options -d /tmp/test.tomb.key \
| hexdump -C
| xxd
echo --
}
@ -309,7 +309,7 @@ test-set-key() {
notice "Dump of clear key contents to examine them:"
print ${dummypass} \
| gpg --batch --passphrase-fd 0 --no-tty --no-options -d /tmp/test.tomb.new.key \
| hexdump -C
| xxd
echo --
mv /tmp/test.tomb.new.key /tmp/test.tomb.key
tt close test

View File

@ -57,9 +57,12 @@ command -v cloakify > /dev/null && test_set_prereq CLOAKIFY
command -v decloakify > /dev/null && test_set_prereq DECLOAKIFY
command -v sphinx > /dev/null && test_set_prereq SPHINX
command -v oracle > /dev/null && test_set_prereq ORACLE
command -v doas > /dev/null && test_set_prereq DOAS
# GnuPG config
#test_set_prereq GPGRCPT
if test_have_prereq GPGRCPT; then
unset GNUPGHOME
unset GPG_AGENT_INFO
export GNUPGHOME="$TEST_HOME/gnupg/"
@ -68,8 +71,8 @@ export KEY2="0B2235E660753AB0475FB3E23DC836481F44B31E"
export SUBKEY1="D89BE71A935779961C130E50D9D7ACED39D3991C!"
export SUBKEY2="843077BF7FD4A9C7BBFC3A69F065568B4F7D6CA9!"
export KEY_UNTRUSTED="E6195F61F5EBA81FE4B1565AAC844B92004240CD"
chmod 700 "$GNUPGHOME"
fi
# Dummy passwords used in the tests suite

View File

@ -3,6 +3,7 @@ verbose = False
address = 127.0.0.1
port = 2355
datadir = /tmp/.sphinx/
ssl_cert = /etc/sphinx/server.crt
[server]
verbose = False
@ -10,7 +11,5 @@ address = 127.0.0.1
port = 2355
datadir = /tmp/.sphinx/
keydir = /tmp/.sphinx/
[websphinx]
pinentry=/usr/bin/pinentry
log=
ssl_cert = /etc/sphinx/server.crt
ssl_key = /etc/sphinx/server.key

View File

@ -8,13 +8,14 @@ all:
@echo "Strings generated in tomb.pot"
.PHONY: install
install: es.mo ru.mo fr.mo de.mo sv.mo it.mo
install -Dm644 es.mo ${DESTDIR}${LOCALEDIR}/es_ES/${TOMBFILE}
install -Dm644 ru.mo ${DESTDIR}${LOCALEDIR}/ru_RU/${TOMBFILE}
install -Dm644 fr.mo ${DESTDIR}${LOCALEDIR}/fr_FR/${TOMBFILE}
install: de.mo es.mo fr.mo it.mo pt_BR.mo ru.mo sv.mo
install -Dm644 de.mo ${DESTDIR}${LOCALEDIR}/de_DE/${TOMBFILE}
install -Dm644 sv.mo ${DESTDIR}${LOCALEDIR}/sv_SV/${TOMBFILE}
install -Dm644 es.mo ${DESTDIR}${LOCALEDIR}/es_ES/${TOMBFILE}
install -Dm644 fr.mo ${DESTDIR}${LOCALEDIR}/fr_FR/${TOMBFILE}
install -Dm644 it.mo ${DESTDIR}${LOCALEDIR}/it_IT/${TOMBFILE}
install -Dm644 pt_BR.mo ${DESTDIR}${LOCALEDIR}/pt_BR/${TOMBFILE}
install -Dm644 ru.mo ${DESTDIR}${LOCALEDIR}/ru_RU/${TOMBFILE}
install -Dm644 sv.mo ${DESTDIR}${LOCALEDIR}/sv_SV/${TOMBFILE}
@echo "Translations installed."
%.mo: %.po

View File

@ -0,0 +1,27 @@
# TRANSLATIONS
## 1. How to translate
Tomb uses the [Weblate](https://hosted.weblate.org/projects/tomb/tomb/) platform to manage the translation efforts of it's user community.
In Weblate, an user can click "Start new translation", choose a language and click "Request new translation". Additionally, just to reinforce your request, we recommend opening an [issue](https://github.com/dyne/Tomb/issues/new) requesting the development team to open a new translation for your language.
## 2. About files in this folder
All translation files in this folder (those with a ".po" extension) are based on the `tomb.pot` file.
POT files are just templates and they don't contain any translations. To do a translation, create a new PO file based on the template.
The `tomb.pot` template must be created using the perl script `generate_translatable_strings.pl` **for each new version of Tomb**:
```sh
$ perl generate_translatable_strings.pl > tomb.pot
```
After that, just open the `tomb.pot` file in the [poedit](https://poedit.net/) program, and click on "Start new translation" (bottom left button), select your language, save and start translating.
## 3. Updating translation
In your favorite shell, make a backup of old file adding it's version, then update with `msgmerge`:
```sh
$ msgmerge --update <language>.po tomb.pot
```
Open the new updated PO translation file and start reviewing the translation. Poedit will highlight what has changed and what needs revision.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@ my $STRINGPATTERN = '(".*?[^\\\]")';
my $date = localtime;
print '
# Tomb - The Crypto Undertaker.
# Copyright (C) 2007-2014 Dyne.org Foundation
# Copyright (C) 2007-2024 Dyne.org Foundation
# Denis Roio <jaromil@dyne.org>, 2013.
#
#, fuzzy

View File

@ -2,7 +2,7 @@
cat <<EOF
# Tomb - The Crypto Undertaker.
# Copyright (C) 2007-2014 Dyne.org Foundation
# Copyright (C) 2007-2024 Dyne.org Foundation
# Denis Roio <jaromil@dyne.org>, 2013.
#
#, fuzzy

File diff suppressed because it is too large Load Diff

1528
extras/translations/pt_BR.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

742
tomb

File diff suppressed because it is too large Load Diff