Skip to content

Commit

Permalink
Introduce automated UI testing based on asciinema
Browse files Browse the repository at this point in the history
  • Loading branch information
hartwork committed Nov 25, 2023
1 parent 92cce29 commit 0cd5138
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 0 deletions.
57 changes: 57 additions & 0 deletions .github/workflows/linux_and_macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,18 @@ jobs:
libncurses-dev \
pkg-config
- name: Install build dependencies
if: "${{ runner.os == 'macOS' }}"
run: |-
brew tap homebrew/cask-fonts
brew install \
agg \
asciinema \
coreutils \
fontconfig \
font-liberation \
imagemagick
- name: Install build dependency Clang ${{ matrix.clang_major_version }}
if: "${{ runner.os == 'Linux' && contains(matrix.cxx, 'clang') }}"
run: |-
Expand Down Expand Up @@ -99,6 +111,51 @@ jobs:
make uninstall DESTDIR="${PWD}"/ROOT/
[[ "$(find ROOT/ -not -type d | tee /dev/stderr)" == '' ]] # i.e. fail CI if leftover files
- name: 'Run UI tests'
if: "${{ runner.os == 'macOS' }}"
run: |-
./recordings/record.sh
- name: 'Upload UI test renderings for inspection'
if: "${{ runner.os == 'macOS' }}"
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with:
name: ttyplot_ui_test_${{ matrix.runs-on }}_${{ github.sha }}
path: recordings/actual*
if-no-files-found: error

- name: 'Evaluate UI test results'
if: "${{ runner.os == 'macOS' }}"
run: |-
set -o pipefail
assert_images_equal_enough() {
local a="${1}"
local b="${2}"
local diff_output="${3}"
local dissimilarity="$(compare -metric DSSIM "${a}" "${b}" "${diff_output}" 2>&1)"
if ! python3 <<<"import sys; sys.exit(int(${dissimilarity} > 0.01))"; then
echo "Image \"${a}\" is not close enough of a match to image \"${b}\", dissimilarity is ${dissimilarity}." >&2
return 1
fi
true
}
cd recordings/
error=0
for expected in expected*.png; do
actual=${expected/expected/actual}
diff=${expected/expected/diff}
assert_images_equal_enough ${actual} ${expected} ${diff} || error=1
done
rm -fv actual*.* diff*.*
exit ${error}
- name: 'Clean'
run: |-
set -x -o pipefail
Expand Down
2 changes: 2 additions & 0 deletions recordings/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/actual*
/diff*.png
Binary file added recordings/expected-0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added recordings/expected-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added recordings/expected-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added recordings/expected-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
44 changes: 44 additions & 0 deletions recordings/headless.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#! /usr/bin/env python3
# Copyright (c) 2015 by pyte authors and contributors
# Copyright (c) 2023 by Sebastian Pipping <[email protected]>
#
# Licensed under LGPL v3, see pyte's LICENSE file for more details.
#
# Based on pyte's example "capture.py"
# https://raw.githubusercontent.com/selectel/pyte/master/examples/capture.py

import os
import pty
import signal
import select
import sys


if __name__ == "__main__":
if len(sys.argv) < 2:
sys.exit(0)

p_pid, master_fd = pty.fork()
if p_pid == 0: # Child.
env = os.environ.copy()
env['TERM'] = 'xterm-256color'
os.execvpe(sys.argv[1], sys.argv[1:], env=env)
assert False # never gets here

# Parent.
while True:
try:
[_master_fd], _w, _x = select.select([master_fd], [], [])
except (KeyboardInterrupt, # Stop right now!
ValueError): # Nothing to read.
break

try:
data = os.read(master_fd, 1024)
except OSError:
break

if not data:
break

os.kill(p_pid, signal.SIGTERM)
45 changes: 45 additions & 0 deletions recordings/record.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#! /usr/bin/env bash
##
## Copyright (c) 2023 by Sebastian Pipping
## Apache License 2.0
##

set -e -u

self_dir="$(dirname "$(realpath "$(which "$0")")")"
ttyplot_bin_dir="${self_dir}/.." # i.e. the local build
agg_bin_dir="${HOME}/.cargo/bin" # if a local build

export PATH="${ttyplot_bin_dir}:${PATH}:${agg_bin_dir}"

# Consistent clock display for reproducibility
export FAKETIME=yesplease


cd "${self_dir}"

# Check and report on runtime requirements
which agg asciinema convert realpath timeout ttyplot

# Enforce a diff on failure
rm -f actual*.*

asciinema_args=(
--cols 90
--rows 20
-c 'timeout -s INT 3s sh -c "{ sleep 0.5; echo \"1 2 3 4\"; sleep 0.5; } | ttyplot -2 -c X"'
-t 'ttyplot waiting, drawing, and shutting down'
)

./headless.py asciinema rec "${asciinema_args[@]}" actual.cast

agg_args=(
--fps-cap 2
--font-family 'Liberation Mono'
)

agg "${agg_args[@]}" actual.cast actual.gif

convert -coalesce actual.gif PNG8:actual.png

ls -lh actual*.*

0 comments on commit 0cd5138

Please sign in to comment.