Home > Software engineering >  fake/mock/background terminal for testing an ncurses application
fake/mock/background terminal for testing an ncurses application

Time:12-13

I am working with an(other) legacy C application which has a text user interface written using ncurses.

I would like to script some tests of the program running but it will only run in a terminal. There is no way to remove the user interface.

Is there a way to fake or background a terminal (for the application) without ever having to bring the screen to the foreground?


The best I have so far uses screen and timeout as below. This example performs a timed run of the program. I've omitted machinery to manage the workspace for brevity.

#!/bin/bash

cat - >runmyprogram <<EOF
echo running 
timeout --foreground --preserve-status --signal=HUP 3s cursesprogramcommandlinehere
STATUS=\$?
echo \$STATUS >workspace/exit_status
stty sane
clear
exit \$STATUS
EOF
chmod u x runmyprogram

screen -dmS foobar /bin/bash

(
    sleep 3
    sleep 1
    screen -S foobar -p 0 -X quit
) &

screen -S foobar -p 0 -X stuff "runmyprogram^M"
screen -r

This sort of works but:

  • The ncurses application does not start until it is brought to the foreground with screen -r.

I would like to achieve something similar without ever having to use screen -r.

  • I have also tried tmux but so far have not got this as close to working as screen. It also requires a terminal to be attached for the GUI (i.e. tmux attach) to start.

  • When tests are run in an Azure build pipeline I get "Must be connected to a terminal"

(Adding script /dev/null as suggested here causes a hang. I don't see why that would create a terminal anyway)

  • This also happens if I run the test with "nohup" which of course does not have a terminal.

  • When run from ctest screen also seems to alter the topology of the terminal this runs in when really I want it to be fully isolated.

  • There is also at least one race condition as exit_status is not always populated however much time I add between timeout timing out and sending the quit message to screen.

The timeout is necessary to cover (failing) test cases where the TUI does not respond.

I am able to make changes to the legacy application to support this better but rewriting the entire user interface is not an option.

Some related questions:


The difficulty with screen is caused by a subtle interaction between screen and ncurses. I have tried quite a few apps from https://www.etcwiki.org/wiki/Best_ncurses_linux_console_programs as examples and can get them all to work with screen. The legacy app is the only one that requires screen to be foregrounded. It fails because it makes a call to subwin() which fails and is subsequently treated as a fatal error. (note this is different behaviour to what I described above. That is, that the app does not start until foregrounded. I have been unable to reproduce this thus far). The curious bit is that if I attach the screen before I run the application it works. Both the call to subwin() and the rest of the application. I would like to understand why.

  • I have tried tmux and now got this working. It does not require the window to be attached.

CodePudding user response:

My solution using tmux from bash includes elements as below.

wrapper script to perform a timed run of a program and capture its exit status:

cat - >$WSDIR/runprog <<EOF
#!/bin/bash
echo running program >$WSDIR/stdout
cd $WSDIR
echo 0 >$WSDIR/exit_status
timeout --foreground --preserve-status --signal=HUP $RUNTIME runprogramhere 2>$WSDIR/stderr
STATUS=\$?
echo \$STATUS >$WSDIR/exit_status
stty sane
clear
echo done STATUS=\$STATUS >>$WSDIR/stdout
exit \$STATUS
EOF
chmod u x $WSDIR/runprog

script to run the program using tmux:

cat - >$WSDIR/exercise <<EOF
tmux new-session -d -s $SESSIONNAME -x 132 -y 80 "bash -l"
tmux send-keys "cd $WSDIR"
tmux send-keys Enter
tmux send-keys ./runprog
tmux send-keys Enter

# watch for interesting things to happen...

EOF
chmod u x $WSDIR/exercise

Perform a timed run using the script above allowing for things to not work.

SESSIONNAME=testFoobar-case1-$$

cleanUp () {
   tmux kill-session $SESSIONNAME 2>/dev/null
}

trap cleanUp 0 TERM

timeout 10s $WSDIR/exercise

How this works:

  • tmux new-session - starts a new session

  • -d - starts it detached (so it will work in a script without a 'real' terminal)

  • -s - gives us a named session to refer to - so our scripts can tidy up

  • tmux send-keys - sends key strokes to the session whether it is in the background or foreground

  • timeout is useful to forcing programs to exit if something goes wrong

  • --preserve-status forces timeout to preserve the status of the program its running.

  • we store the exit status in a file for easy access

  • trap invokes the cleanUp routine when we exit

  • thus kill-session kills the tmux session on exit

  • we generate a unique session name per test script


A similar scheme is possible with screen:

screen -S sessionName bash -l
screen -d   - detach the current session
screen -r   - resume a session
screen -S sessionName -X stuff "echo hello^M"  - send keys

The differences are minor except screen does not work for the app I am testing. Tmux has names for special keys. Screen uses control characters instead.

It can be useful to script communications with the tmux (or screen) session using expect or similar.

Both tmux and screen have commands to let you take screen shots if that is useful for your tests:

  • Related