Skip to content

hroptatyr/clitoris

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

clitoris

A command-line tool to run automated tests for command-line applications. Much like expect but with a syntax that actually resembles the command-lines you'd be otherwise executing in the shell.

Why?

Code snippets for command-line tools of the form below appear quite frequently; in the docs, in bug reports, on Q&A sites, in examples on blogs, etc. They are easy to grasp, easy to produce and easily interchangeable via copy'n'paste.

The idea to somehow parse those snippets and evaluate them automatically isn't far-fetched. This toolchain does exactly that, be it with a slightly less free syntax, and turns those snippets into regression tests.

How?

Tests are organised in files. One test, one file. The basic syntax is:

$ COMMANDLINE
[EXPECTED OUTPUT]
$

where COMMANDLINE is anything you'd normally type in the shell. And EXPECTED OUTPUT is either free text that would normally appear on stdout, or of the form

< FILE

which means the output of the test run is to be compared with the contents of FILE. This is useful when the reference output is already in a file, or is otherwise generated and stored in file form.

The concluding $ on its own demarks the end of the test file. Anything beyond will be blissfully ignored. Also anything before the initial $ line will be ignored.

One test file can issue more than one command line. They will be processed from top to bottom, just like in a real shell:

$ COMMANDLINE1
[OUTPUT1]
$ COMMANDLINE2
[OUTPUT2]
$

and the whole test file is considered passing when every single command line returns successfully.

While therefore technically it's possible to stuff several regressions into one file, the idea was rather to allow for preparation and clean-up work in the same file (where it actually belongs).

Practical examples

The following example, when run through clitoris, would produce a return code of 0 and no output:

$ echo "2 + 3 = $((2 + 3))"
2 + 3 = 5
$

whereas

$ echo -n | md5sum
68b329da9893e34099c7d8ad5cb9c940  -
$

would produce a return code of 1 and the following output:

--- expected
+++ actual
@@ -1 +1 @@
-68b329da9893e34099c7d8ad5cb9c940  -
+d41d8cd98f00b204e9800998ecf8427e  -

The diff-like output is only presented in case the output differs (much like many diff(1) tools). If run in test harnesses (like the automake parallel tests), this instantly provides valuable feedback as to what happened and what the author intended to happen.

If the < FILE syntax is chosen the diff output, if any, is of course with respect to the contents FILE:

$ cat shopping
butter
cheese
marmite
white bread
$ sort shopping
< shopping
$

Return code would be 0 and no output, as the file shopping is already sorted. However,

$ cat shopping
butter
cheese
marmite
brown bread
$ sort shopping
< shopping
$

would return 1 and produce:

--- expected
+++ actual
@@ -1,4 +1,4 @@
+brown bread
 butter
 cheese
 marmite
-brown bread

as now the shopping list isn't in sorted order.

Controlling behaviour

Like make clitoris breaks upon the first failing command, or if the output differs (unless --keep-going is passed).

To fine-tune what breakage means, clitoris has some built-in operators:

$ ignore FAILING_COMMAND
$

will run FAILING_COMMAND but simply ignore the return code and/or the fact that FAILING_COMMAND's output might differ. Alternatively, if only one of the two is meant to be suppressed ignore-return or ignore-output should be used.

To really have a fine-grained control over what return code should be considered successful use the built-in ! (negate) or ?n (expect) operators:

 ## succeeds iff COMMAND fails
 $ ! COMMAND
 $

 ## succeeds iff COMMAND returns with code 2
 $ ?2 COMMAND
 $

Controlling output

The ignore operator as introduced in the Controlling behaviour section above will still compare the output to the reference and print a diff. To gain a more subtle control over what appears in the output use the (make-inspired) @ built-in afront the command to bypass the diffing altogether (this implies ignore-output of course).

A fabulous usecase is the included hxdiff(1) tool which itself prints a diff (of the hexdumps of two files), and thus avoids the confusion of a diff-of-diffs.

Imagine a binary file produced in the process of the regression test and a reference file that contains zeroes only:

$ @ hxdiff reference.bin actual.bin
--- expected
+++ actual
@@ -1,2 +1,2 @@
 00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   |................|
-00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   |................|
+00000010  00 01 00 00 00 00 00 00  00 00 00 00 00 00 00 00   |................|

There is one more operator to aid generated prototypical output (the stuff that is compared against the dynamic output). For instance, if the prototypical output, for some reason, has to contain values that change from run to run, user to user, or machine to machine (e.g. the current date, a user's home directory, or the hostname) you'll have no choice but to materialise these beforehand into a file and use that file as proto-output, as in:

$ echo "${HOST}" > hn.file
$ hostname-output-tool
< hn.file
$ -rm hn.file
$

It would be easier if the proto-output could be expanded somehow like POSIX shell here-documents. This is where the expander built-in ($) comes in handy, now you can write:

$ $ hostname-output-tool
${HOST}
$

and even though this looks slightly ugly it saves the hassle of temporary files (along with races should test files be executed in parallel) and pre-test/post-test actions that don't actually test functionality.

The proto-output of a test that is flagged with the expander built-in ($) is treatest as though you passed a here-document to the diff process.