Unittesting

When making stuff for others to use it is important to verify that it works as intended. It is important to make sure fixes to one issue don’t break another. For this purpose we’ve created very nice developer-experiences around success/failure counters!

Expect

Expect is one such example built around the TCL interpretor. Presumably Expect’s the main reason Linux From Scratch considers TCL to be part of the core OS? Several other OS components use it to ensure they’re built correctly.

Once it initializes the TCL interpreter Expect performs version checks, saves some existing APIs aside, registers some new ones, upon first-run initializes various systems including OS-specific terminal handling & cleanup routines, registers the bulk of new APIs & variables in a semi-specific order, & runs some TCL code to register the new library. I’ll go over those APIs tomorrow.


Then it parses commandline flags deferring to TCL where appropriate, followed by trailing args.

Finally Expect uses 1 of 3 codepaths to run the given TCL code depending on its input source. Before running a TCL exit command with appropriate error code, ensuring any attached exit-handlers get called.

Expect TCL Commands

Expect consists mostly of a relatively-small API exposed to the TCL interpreter it runs.

There’s an interact TCL command, which with extensive state evaluates some expression & parses commandline flags + keywords before outputting a prompt, reading from a TCL channel into a linkedlist, & evaluates them. Followed by richer timestamped-input mainloops & cleanup. Including a filedescriptor hashtable & subprocesses.

Glossing over a lot…


Expect provides a couple teletype TCL commands. stty redirects the standard filedescriptors parsing redirected input then its options then the output. Finally this gets rewritten into an exec command to evaluate.

And the system command parses its commandline flags running a tcsetattr syscall or equivalent IOCTL reporting any errors, concatenates remaining args into a buffer, calls system ignoring SIGCHLD signals, calls tcgetattr with error reporting, outputs results, & cleans up.

There’s also a few utilities in that file for configuring the terminal called by initialization & other modules.


Another file defines the bulk of APIs exposed to TCL testscripts. exp_open parses its arguments, opens the TCL channel in 1 of 2 ways, attaching a PID & wrapping in a “file channel”.inter_return defers to return with adjusted exit codes.exp_continue determines an appropriate exit code to return. interpreter parses flags then recurses back to entrypoint.

overlay with parsed arguments gathers a subcommand multistring with default SIGINT & SIGQUIT handlers execvps it before cleaning up & outputting the error. disconnect with validation & ignoring SIGHUP configures terminal I/O specially & reconnects the standard filedescriptors to /dev/null before forking and/or configuring cgroups & exitting. fork wraps the corresponding syscall with bookkeeping. strace creates & possibly destroys Trace objects.

wait parses args looking up the channel, checks its atomic state looking up a pid to waitpid on or spinlocks as appropriate, converts results to TCL data, & cleans up. close wraps the corresponding syscall with argument parsing & channel lookup. exp_configure wraps TCL’s typesystem. exit runs exit handlers, some cleanup TCL code, & closes the interpreter. exp_internal wraps the TCL datamodel & the “diag” channels. debug, if supported, reconfigures some globals.

log_user also sets a global, as per log_file. send, send_error, send_tty, & send_user all parses their arguments, retrieves the property to send, & with logging writes it in the appropriate format to the channel before cleaning up. send_log wraps the Diag subsystem. sleep wraps some system/event-loop specific code. getpid wraps the corresponding syscall. exp_pid retrieves a channel property. spawn operates on channels & its implementation heavily depends on build flags.

Apart from some support functions (a few of which are only called from outside), that pretty well covers the bulk of the Expect-specific commands!


trap parses flags, possibly examines current signal to get the data to return, once validated & with the list retrieved iterates over said list converting to an alternate TCL datastructure. Possibly configuring a signal handler to get it this data next time it recieves the signal in the TCL datamodel, via a background tasking checking its counters. Includes lookuptable conversions between signals & strings.


As for the main attractions of Expect commands…

expect, expect_user, & expect_tty all (sharing the same code) interprets their args based on quantity evaluating an innerexpression, retrieves results, performs more complex argument parsing, iterates over commands & their states for deduplication, validates states, considers preparing a timeout, repeatedly reads results with handling & traversing the statemachines, compares against expected patterns of various types, & cleans up.

expect_after, expect_before, & expect_background all, sharing the same code, does a lot of similar stuff. Just without checking the output stream. match_max sets & validates a global (or channel-property) having retrieved channels state; as for remove_nulls, parity, & close_on_eof. timestamp puts more effort into argument parsing, retrieves the time, formats it according to the given format-string, & returns result to TCL.


That file also defines an optional debugging command stringifying the current state for output disabled in production builds. There’s a background handler to retrieve the output data for the expect* commands, though a seperate file adds more infrastructure around it. Those commands also have several other support functions, including for carefully reading the resulting stream. There’s a couple obsolete functions for running subcommands.

Miscallanea

What else do I see in Expect’s codebase?

I’m not entirely sure why TCL includes all this code, beyond there appearing to be a traditional distrust in the standardization of UNIX”s standard library.

DejaGNU

DejaGNU is an extended standard library for the barebones TCL-based Expect test-runner. As such it'll inevitably be a toolbox, best summarized in list form:

DejaGNU provides TCL functions for:

Remote filesystem backends:

Reading through the more complex parts of DejaGNU I see:

DejaGNU looks more like a proper testrunner, & more, than Expect!