Description

test(1) equivalence procedures (and extensions)

Author

elf

Version

Requires

Usage

(require-extension unix-test)

Download

unix-test.egg

Documentation

Introduction

The test(1) program is part of the POSIX specification. It provides a set of conditionals for comparing simple values and examining file attributes. Almost every non-trivial shell script uses test(1) extensively, as there is no other effective means of organising program logic solely within a command-line interpreter.

Motivation

The eINIT developers are attempting to write a scheme-based init(8) handler, rather than the traditional shell-based handler. They requested test(1) functionality to help migrate to Chicken from sh and Guile. This is intended to support their efforts, as well as (hopefully!) being of more general use.

Why unix-test is better than test(1)

The test(1) specification is both messy and ambiguous. It also constrains comparisons to only three types (integers, strings, and files), with strange and often unpredicatable behaviour when given flonums. It is needlessly verbose (being constrained by infix notation) and is often counterintuitive in its choice of symbol semantics.

We scrapped all that. unix-test implements a superset of the standard (as well as a superset of each platform's implementation) in friendly s-expr format with meaningful symbols. The old familiar symbols we all know and 'love' are, of course, supported for maximal backward compatibility (as ridiculous as that sounds).

Compatibility with test(1)

Incompatibilities

As far as I am aware, all POSIX (and GNU and BSD) flags and symbols are supported, with the following exceptions:

1. The -a flag ONLY means 'and', not 'file exists'.

2. The GNU -o option flag is entirely unsupported, as is it is patently ridiculous. -o denotes 'or' only.

3. Most flags check all of their operands for sanity before actually performing the operation, which differs from most implementations (and possibly the specification). This helps optimise flow for unlimited argument lists, however, and should rarely, if ever, give differing results. (The arguments are not actually evaluated; they are tested to ensure that all containing clauses could evaluate into the correct type(s).)

4. All expressions are properly parenthesised and use proper (prefix) notation.

Extensions

1. All real numbers are supported by the comparison operators.

2. Any flag for which it would make sense to allow unlimited operands will accept unlimited operands (see below for details).

3. Sane flagnames and mnemonics are supported.

4. Many, many more operations are supported. Reasonable feature-add requests will be granted.

5. 'Internal clauses' (clauses that may only appear within other clauses, rather than at top-level) provide some extra functionality for common uses of test (such as string append, path resolution, file size, string length, etc.).

6. It works on Win32. Certain types of operations will always fail, due to lack of support by the operating system, but it does work.

Clauses and Combinations

Clause Syntax

There are two types of clauses that may be passed to unix-test :

1. Lists of the form (OPFLAG ARG...), where OPFLAG denotes the operation to perform, with ARG... as input(s).

2. Strings. This form is for backwards compatibility only.

All top-level clauses return a boolean value. String clauses return the value of (not (string-null? STR)). The return value of a list clause depends on its combination.

Combinations

The extension of opflags to unlimited arguments requires implicit combiners in order to receive meaningful truth values. The order of arguments within a clause is always preserved. There are four distinct cases:

1. Flags that traditionally take in a single argument (such as file type flags) combine their values using and. This ensures immediate failure when any of the arguments does not satisfy the predicate.

2. Flags that test equality (or other comparisons where all values may be checked against exactly one static value). This combines values merely by iterating down the list, comparing each following element against the first, and failing immediately if not satisfied.

3. Flags that test an ordering or ranking of values (such as less-than) combine by comparing each ARG(N) with ARG(N+1).

4. Flags that test INequality (or other comparisons where all values must be checked against all other values) combine via pair-fold to iterate down each sublist.

At the moment, there is no way to change the combiners to use anything but and/fail-immediately. If other behaviours are desired, contact me.

Command Syntax

Invocation

macro: (unix-test CLAUSE...)

Parses CLAUSE and performs all tests specified therein. Returns #t if the clause(s) were satisfied, and #f otherwise. See below for a table of all opflags. NOTE: Arguments to unix-test should NOT be quoted! The macro will quote it automatically.

Argument Types

Type Description
CLAUSE
any valid top-level clause list (or string). null is not a valid clause. CLAUSE is always processed by unix-test and returns a boolean.
STRING
string (including null string) or string-valued internal clause
REAL
integer or floating point number or number-valued internal clause
BIT
positive exact integer between 0 and C_WORD_SIZE - 1, inclusive, or integer-valued internal clause
INT
exact integer or integer-valued internal clause
FILE
string (including null string) naming a file or string-valued internal clause
FD
positive exact integer corresponding to an open file descriptor.

Top-level Flags/Operations

The below table is a key for the table of operations.

Heading Description
Op
general syntax for the given operation
Category
loose classification of operation domain
Test
equivalent test(1) flag (or N/A)
Flags
list of all equivalent opflags
Min
minimum number of args allowed
Max
maximum number of args (0 == no limit)
Description
description of the operation

The table below only describes top-level list clauses. For internal clauses, see the Internal Clauses section, below.

Op Category Test Flags Min Max Description
(and CLAUSE...)
combiner
-a and a -a 1 0
each CLAUSE is evaluated in order. true only if all clauses succeed are true. returns immediately on false.
(or CLAUSE...)
combiner
-o or o -o 1 0
each CLAUSE is evaluated in order. true if any clause is true. returns immediately on true.
(xor CLAUSE...)
combiner
N/A xor 1 0
each CLAUSE is evaluated in order. true if an odd number of clauses are true. this is the only multi-clause combiner guaranteed to evaluate all of its arguments.
(not CLAUSE)
combiner
! not ! 1 1
true when CLAUSE fails, and vice versa.
(length=0 STRING...)
string-predicate
-z length=0 z -z 1 0
true when every STRING is the null string.
(length>0 STRING...)
string-predicate
-n length>0 n -n 1 0
true when every STRING is not the null string.
(string= STRING1 STRING...)
string-comparator
= string= = == 2 0
true when every STRING has identical contents.
(string!= STRING1 STRING...)
string-comparator
!= string!= != 2 0
true when no STRING has identical contents to any other.
(string< STRING1 STRING...)
string-comparator
< string< < 2 0
true when STRING1 ... STRINGN are lexically ordered, strictly increasing.
(string<= STRING1 STRING...)
string-comparator
<= string<= <= 2 0
true when STRING1 ... STRINGN are lexically ordered, strictly nondecreasing.
(string>= STRING1 STRING...)
string-comparator
>= string>= >= 2 0
true when STRING1 ... STRINGN are lexically ordered, strictly nonincreasing.
(string> STRING1 STRING...)
string-comparator
> string> > 2 0
true when STRING1 ... STRINGN are lexically ordered, strictly decreasing.
(num-bit-set BIT INT...)
num-predicate
N/A num-bit-set num-bit bit-set 2 0
true when every INT has BIT set.
(num= REAL1 REAL...)
num-comparator
-eq num= eq -eq 2 0
true when every REAL has identical value.
(num!= REAL1 REAL...)
num-comparator
-ne num!= ne -ne 2 0
true when no REAL has identical value to any other.
(num< REAL1 REAL...)
num-comparator
-lt num< lt -lt 2 0
true when REAL1 ... REALN are in strictly increasing order.
(num<= REAL1 REAL...)
num-comparator
-le num<= le -le 2 0
true when REAL1 ... REALN are in strictly nondecreasing order.
(num>= REAL1 REAL...)
num-comparator
-ge num>= ge -ge 2 0
true when REAL1 ... REALN are in strictly nonincreasing order.
(num> REAL1 REAL...)
num-comparator
-gt num> gt -gt 2 0
true when REAL1 ... REALN are in strictly decreasing order.
(exists FILE...)
file-type-predicate
-e exists exist e -e 1 0
true when every FILE is the name of an extant file.
(blockdev FILE...)
file-type-predicate
-b blockdev block-dev b -b 1 0
true when every FILE exists and is a block device.
(chardev FILE...)
file-type-predicate
-c chardev char-dev c -c 1 0
true when every FILE exists and is a character device.
(directory FILE...)
file-type-predicate
-d directory dir d -d 1 0
true when every FILE exists and is a directory.
(regular FILE...)
file-type-predicate
-f regular regfile regular-file reg-file reg f -f 1 0
true when every FILE exists and is a regular file.
(symlink FILE...)
file-type-predicate
-h / -L symlink sym-link h -h L -L 1 0
true when every FILE exists and is a symbolic link. note that this is the only test predicate that does not dereference symlinks.
(pipe FILE...)
file-type-predicate
-p pipe fifo p -p 1 0
true when every FILE exists and is a named pipe (FIFO).
(socket FILE...)
file-type-predicate
-S socket S -S 1 0
true when every FILE exists and is a socket.
(setuid FILE...)
file-type-predicate
-u setuid set-uid u -u 1 0
true when every FILE exists and has the setuid bit set.
(setgid FILE...)
file-type-predicate
-g setgid set-gid g -g 1 0
true when every FILE exists and has the setgid bit set.
(stickybit FILE...)
file-type-predicate
-k stickybit sticky-bit sticky k -k 1 0
true when every FILE exists and has the sticky bit set.
(readable FILE...)
file-type-predicate
-r readable read-perm r -r 1 0
true when every FILE exists and is readable by the current user.
(writable FILE...)
file-type-predicate
-w writable writeable write-perm w -w 1 0
true when every FILE exists and is writable by the current user.
(executable FILE...)
file-type-predicate
-x executable searchable exec-perm search-perm x -x 1 0
true when every FILE exists and is executable/searchable by the current user.
(size=0 FILE...)
file-predicate
N/A size=0 1 0
true when every FILE exists and is empty (zero bytes).
(size>0 FILE...)
file-predicate
-s size>0 s -s 1 0
true when every FILE exists and is not empty.
(userowned FILE...)
file-predicate
-O userowned user-owned user-is-owner owned-by-user O -O 1 0
true when every FILE exists and the current effective uid is equivalent to the owner uid.
(groupowned FILE...)
file-predicate
-G groupowned group-owned group-is-owner owned-by-group G -G 1 0
true when every FILE exists and the current effective gid is equivalent to the owner gid.
(modified FILE...)
file-predicate
-N modified N -N 1 0
true when every FILE exists and has an access time identical to or older than its modification time.
(equal FILE1 FILE...)
file-comparator
-ef equal equals same ef -ef 2 0
true when every FILE is identical (has the same device and inode numbers).
(newer FILE1 FILE...)
file-comparator
-nt newer nt -nt 2 0
true when the modification times for each FILE are in strictly increasing order(where time value is measured from some zero in the past).
(older FILE1 FILE...)
file-comparator
-ot older ot -ot 2 0
true when the modification times for each FILE are in strictly decreasing order (measured as above).
(terminal FD...)
misc-predicate
-t terminal term term-port t -t 1 0
true when every FD is an open file descriptor on a terminal.

Internal Clauses

The table below is a key for the internal clauses operations table.

Heading Description
Op
general syntax for the given operation
Return Type
type returned (determines where valid
Test
equivalent test(1) flag (or N/A)
Flags
list of all equivalent opflags
Min
minimum number of args allowed
Max
maximum number of args (0 == no limit)
Description
description of the operation

Note especially the return values. Any clause with a return type of FILE or STRING may occur within any clause expecting either of these types. Likewise, INT return types may be used within any clause expecting any type of number. (The exception to this are clauses expecting file descriptors. Internal clauses cannot be used to generate FD types, at least at the moment.)

Op Return Type Test Flags Min Max Description
(size FILE) INT N/A size 1 1
returns the size of FILE in bytes if it exists and is stat'able, or -1 on error.
(length STRING) INT -l length l -l 1 1
returns the length of STRING in bytes.
(path FILE) STRING N/A path 1 1
returns the canonical absolute path (on the local platform) of FILE. note that this does not check for existence; it merely resolves partial or relative paths.
(append STRING...) STRING N/A append 1 0
returns a new string comprised of each STRING appended in order.
(!or INT...) INT N/A !or 1 0
returns the bitwise-or of all INTs.
(!and INT...) INT N/A !and 1 0
returns the bitwise-and of all INTs.
(!xor INT...) INT N/A !xor 1 0
returns the bitwise-xor of all INTs.
(!1c INT) INT N/A !1c 1 1
returns the ones-complement representation of INT.
(!2c INT) INT N/A !2c 1 1
returns the twos-complement representation of INT.

Note that internal clauses may be used as arguments to internal clauses, provided they are of the appropriate return type.

Bugs

None known

Licence

Copyright (C) 2008, Lenny Frank.  All rights reserved.

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the Software),
to deal in the Software without restriction, including without limitation 
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the 
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED AS-IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.