tsh -- quick and easy testing shell

why

I got tired of: bash/sh portability issues; writing long, complicated, test code; weird shell constructs for simple things; constantly putting in &>/dev/null; and many more.

Sure you can write test scripts in any language, but for the kind of code I write, I have to run lots of shell commands, capture the output, and check that.

aim

Less typing, less output.

Simple examples here.

The test program for 'gitpod' is TAP compliant and can be found here. A syntax colored version is here.

requirements

perl 5.10.0 or later, and any posix shell that understands very simple redirection.

input

I assume you have read the examples already.

syntax

Input is one or more lines of text, obtained as follows:

This input is separated into tsh commands by

(Side effect: this means newlines can be embedded within an argument also if you find that more convenient, as you can see in several examples).

The only way to escape a semicolon is to precede it with a backslash. Any quotes are taken literally so don't try to use those to escape semicolons. TSH_VERBOSE=3 (or higher) will show the commands actually being run.

Comments are allowed but not in the middle of a line (i.e., the whole line needs to be a comment). Leading whitespace is always thrown away, but not elsewhere. There are no continuation lines or include files.

hashhash comments (testnames)

Comment lines that start with optional whitespace, then ## (two hash marks) then whitespace, are treated as supplying the "name" of that test; see the error_list function below. This also serves as a progress report, getting printed (prefixed by a "# ") to STDOUT as the test runs.

functions available to perl

For perl, you call a function called try(). A lot can be done by just passing tsh commands (see later) to it, but there are some more functions available if you need them.

The following functions get you more information:

The following convenience functions are also available. Their purpose is to help you replace shell scripts as much as possible.

output

The return value fits the language used so you can use it as a boolean as usual. This means the actual value is different in perl and in shell, since they have opposite notions of what is "true" and what is "false".

STDOUT is only used for TAP related output (see TAP section later).

The 'try()' function prints nothing by default. The 'tsh' command prints nothing on success, but on failure it prints a very brief error summary (like "3 error(s)") to STDERR.

In both cases, test-specific messages (see later) are printed to STDERR.

Also in both cases, the TSH_VERBOSE env var changes the output:

(Setting it to 4 produces too much output to be useful most of the time).

TSH_ERREXIT (named after the long name of bash's '-e' option) forces an exit on the first error, which helps when developing scripts.

tsh commands in detail

macros

Every command is first checked if a macro by that name exists. Your current list of macros is shown if you run 'tsh' without any arguments. With the default set of macros, for example, a command of empty will run git commit --allow-empty -m empty.

The rest of the commands described below are checked only after this macro expansion. Macro expansion is attempted even on the result of the previous macro expansion, so be careful you don't recurse.

environment commands

testing commands

(Also see the TAP section below for the plan command).

A note about the patterns: if you need to use ^ or $, please see the "perl code" section later.

test-specific messages

In addition, each of these can be followed by the word 'or' followed by a test-specific message, like so:

ok or hey the last command failed

which results in the specific message being printed to STDERR, as described in the "output" section above.

A variation of the test-specific message is:

ok or die hey the last command failed so I am dying...

i.e., if the first word of the message is "die" then that test will cause an immediate exit. (If you're calling tsh from a shell script, your shell script will of course still run -- it is upto you to detect the death of 'tsh' using the exit status and do whatever you need to).

perl code

NOTE: Avoid the temptation to put in all sorts of stuff there. It's meant to help do quick little tasks that would be horribly cumbersome in shell. If you find yourself doing more, you've misunderstood the purpose of tsh.

You can run arbitrary perl code; e.g.:

tsh 'uname; perl $_ = lc; !/macos/ or we dont do Macs, mac!'

Perl's eval function is used. Before starting, $_ is set to the output of the last external command. The perl code should leave its own output in the same variable, which will then in turn become the "output" of this command.

The return value, on the other hand, reflects the success or failure of the command being eval'd.

For this reason, note that perl $_ = lc; and perl lc; are quite different.

Here's another example. Say you wanted to check that some line in the input starts with "foo":

# this WON'T work
tsh 'cat file; /^foo/ or die bad file'

The code above won't work because the input is all in one variable, so you need a /m flag to make ^ and $ work. However, the simple code for normal testing does not accept flags.

So if you really need that, try this:

# this will work
tsh 'cat file; perl /^foo/m; ok or die bad file'

This replaces the /pattern/ test with a 'perl /pattern/m' test, and then the 'ok' is used to test the success or failure of the perl code.

git specific commands

You can use 'test-tick' and 'test-commit' in scripts for clarity if you don't like the short forms.

miscellaneous commands

external commands

Any command that is not one of the above will be treated as an external command.

TAP compliance

'tsh' is will produce ok / not ok lines if you run it from 'prove', or indeed any harness that sets the HARNESS_ACTIVE env var.

You will need to supply a plan before the first test. Just use the plan N tsh command (where N is the number of tests you plan to run).

You may have guessed that the plan command is merely a convenience; if you wish, you can check that variable and print the plan yourself.

Note: the ok/not ok messages do not contain the actual, correct, test number, because tsh can be invoked multiple times from an outer shell script, and we have no clean way to convey the running test number from one invocation to the next. This does not affect TAP compliance; at least it works fine with prove.

"hashhash comments" (see above) are printed to STDOUT, prefixed by a "# ". This is useful, for example, when running under 'prove -v', to serve as a more user-friendly progress indicator than a series of 'ok' or 'not ok' messages, while not affecting "prove" (without "-v").