From 611438a8920299e5bae3098e0719921893384c2c Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 10 Jul 2015 13:17:50 +0200 Subject: [PATCH] Add functional tests via Xephyr/Xvfb/xdotool Closes https://github.com/awesomeWM/awesome/pull/133 --- .travis.yml | 5 +- tests/_runner.lua | 84 +++++++++++++++++++++++++++ tests/run.sh | 130 ++++++++++++++++++++++++++++++++++++++++++ tests/test-focus.lua | 49 ++++++++++++++++ tests/test-urgent.lua | 106 ++++++++++++++++++++++++++++++++++ 5 files changed, 373 insertions(+), 1 deletion(-) create mode 100644 tests/_runner.lua create mode 100755 tests/run.sh create mode 100644 tests/test-focus.lua create mode 100644 tests/test-urgent.lua diff --git a/.travis.yml b/.travis.yml index cded0d7b6..bd5276f35 100644 --- a/.travis.yml +++ b/.travis.yml @@ -90,6 +90,9 @@ install: - tar xf cmake-3.2.3-Linux-x86_64.tar.Z - PATH=$PWD/cmake-3.2.3-Linux-x86_64/bin:$PATH + # Deps for functional tests. + - sudo apt-get install -qq dbus-x11 xterm xdotool xterm xvfb + script: - export CMAKE_ARGS="-DLUA_LIBRARY=${LUALIB} -DLUA_INCLUDE_DIR=/usr/include/lua${LUAPKG}" - - make && sudo env PATH=$PATH make install && awesome --version + - make && sudo env PATH=$PATH make install && awesome --version && tests/run.sh diff --git a/tests/_runner.lua b/tests/_runner.lua new file mode 100644 index 000000000..adfb2d893 --- /dev/null +++ b/tests/_runner.lua @@ -0,0 +1,84 @@ +timer = require("gears.timer") + +runner = { + quit_awesome_on_error = os.getenv('TEST_PAUSE_ON_ERRORS') ~= '1', + -- quit-on-timeout defaults to false: indicates some problem with the test itself. + quit_awesome_on_timeout = os.getenv('TEST_QUIT_ON_TIMEOUT') ~= '1', +} + +-- Helpers. + +--- Add some rules to awful.rules.rules, after the defaults. +local default_rules = awful.util.table.clone(awful.rules.rules) +runner.add_to_default_rules = function(r) + awful.rules.rules = awful.util.table.clone(default_rules) + table.insert(awful.rules.rules, r) +end + + +runner.run_steps = function(steps) + -- Setup timer/timeout to limit waiting for signal and quitting awesome. + -- This would be common for all tests. + local t = timer({timeout=0.1}) + local wait=20 + local step=1 + local step_count=0 + t:connect_signal("timeout", function() timer.delayed_call(function() + io.flush() -- for "tail -f". + step_count = step_count + 1 + local step_as_string = step..'/'..#steps..' (@'..step_count..')' + + -- Call the current step's function. + local success, result = pcall(steps[step], step_count) + + if not success then + io.stderr:write('Error: running function for step ' + ..step_as_string..': '..tostring(result)..'!\n') + t:stop() + if not runner.quit_awesome_on_error then + io.stderr:write("Keeping awesome open...\n") + return -- keep awesome open on error. + end + + elseif result then + -- true: test succeeded. + if step < #steps then + -- Next step. + step = step+1 + step_count = 0 + wait = 5 + t:again() + return + end + + elseif result == false then + io.stderr:write("Step "..step_as_string.." failed (returned false).") + if not runner.quit_awesome_on_error then + io.stderr:write("Keeping awesome open...\n") + return + end + + else + wait = wait-1 + if wait > 0 then + t:again() + return + else + io.stderr:write("Error: timeout waiting for signal in step " + ..step_as_string..".\n") + t:stop() + if not runner.quit_awesome_on_timeout then + return + end + end + end + -- Remove any clients. + for _,c in ipairs(client.get()) do + c:kill() + end + awesome.quit() + end) end) + t:start() +end + +return runner diff --git a/tests/run.sh b/tests/run.sh new file mode 100755 index 000000000..adb9da6c6 --- /dev/null +++ b/tests/run.sh @@ -0,0 +1,130 @@ +#!/bin/sh + +set -e +set -x + +# Change to file's dir (POSIXly). +cd -P -- "$(dirname -- "$0")" +this_dir=$PWD + +# Get test files: test*, or the ones provided as args (relative to tests/). +if [ $# != 0 ]; then + tests="$@" +else + tests=$this_dir/test*.lua +fi + +# Use a separate D-Bus session; sets $DBUS_SESSION_BUS_PID. +eval $(dbus-launch --sh-syntax) + +root_dir=$PWD/.. + +# Travis. +if [ "$CI" = true ]; then + HEADLESS=1 + TEST_PAUSE_ON_ERRORS=0 + TEST_QUIT_ON_TIMEOUT=1 +else + HEADLESS=0 + TEST_PAUSE_ON_ERRORS=0 + TEST_QUIT_ON_TIMEOUT=1 +fi +export TEST_PAUSE_ON_ERRORS + +XEPHYR=Xephyr +XVFB=Xvfb +AWESOME=$root_dir/build/awesome +RC_FILE=$root_dir/build/awesomerc.lua +D=:5 +SIZE=1024x768 + +if [ $HEADLESS = 1 ]; then + "$XVFB" $D -screen 0 ${SIZE}x24 & + sleep 1 + xserver_pid=$(pgrep -n Xvfb) +else + # export XEPHYR_PAUSE=1000 + # if [ -f /tmp/.X5-lock ]; then + # echo "Xephyr is already running for display $D.. aborting." >&2 + # exit 1 + # fi + "$XEPHYR" $D -ac -name xephyr_$D -noreset -screen "$SIZE" $XEPHYR_OPTIONS & + sleep 1 + xserver_pid=$(pgrep -n Xephyr) +fi +# Toggles debugging mode, using XEPHYR_PAUSE. +# pkill -USR1 Xephyr + + +cd $root_dir/build + +LUA_PATH="$(lua -e 'print(package.path)');lib/?.lua;lib/?/init.lua" +# Add test dir (for _runner.lua). +LUA_PATH="$LUA_PATH;$this_dir/?.lua" +XDG_CONFIG_HOME="./" +export LUA_PATH +export XDG_CONFIG_HOME + +# awesome_log=$(mktemp) +awesome_log=/tmp/_awesome_test.log +echo "awesome_log: $awesome_log" + +cd - + + +kill_childs() { + for p in $awesome_pid $xserver_pid $DBUS_SESSION_BUS_PID; do + kill -TERM $p 2>/dev/null || true + done +} +# Cleanup on errors / aborting. +set_trap() { + trap "kill_childs" 2 3 15 +} +set_trap + +# Start awesome. +start_awesome() { + (cd $root_dir/build; \ + DISPLAY=$D "$AWESOME" -c "$RC_FILE" $AWESOME_OPTIONS > $awesome_log 2>&1 || true &) + sleep 1 + awesome_pid=$(pgrep -nf "awesome -c $RC_FILE" || true) + + if [ -z $awesome_pid ]; then + echo "Error: Failed to start awesome (-c $RC_FILE)!" + echo "Log:" + cat "$awesome_log" + kill_childs + exit 1 + fi + set_trap +} + +# Count errors. +errors=0 + +for f in $tests; do + echo "== Running $f ==" + start_awesome + + # Send the test file to awesome. + cat $f | DISPLAY=$D $root_dir/utils/awesome-client 2>&1 + + # Tail the log and quit, when awesome quits. + tail -f --pid $awesome_pid $awesome_log + + if grep -q -E '^Error|assertion failed' $awesome_log; then + echo "===> ERROR running $f! <===" + grep --color -o --binary-files=text -E '^Error.*|.*assertion failed.*' $awesome_log + errors=$(expr $errors + 1) + + if [ "$TEST_PAUSE_ON_ERRORS" = 1 ]; then + echo "Pausing... press Enter to continue." + read enter + fi + fi +done + +kill_childs + +[ $errors = 0 ] && exit 0 || exit 1 diff --git a/tests/test-focus.lua b/tests/test-focus.lua new file mode 100644 index 000000000..8ebd3919f --- /dev/null +++ b/tests/test-focus.lua @@ -0,0 +1,49 @@ +--- Tests for focus signals / property. +-- Test for https://github.com/awesomeWM/awesome/issues/134. + +awful = require("awful") +timer = require("gears.timer") + +beautiful = require("beautiful") +beautiful.border_normal = "#0000ff" +beautiful.border_focus = "#00ff00" + +client.connect_signal("focus", function(c) + c.border_color = "#ff0000" +end) + + +local steps = { + -- border_color should get applied via focus signal for first client on tag. + function(count) + if count == 1 then + awful.util.spawn("xterm") + else + local c = client.get()[1] + if c then + print(c.border_color) + assert(c.border_color == "#ff0000") + return true + end + end + end, + + -- border_color should get applied via focus signal for second client on tag. + function(count) + if count == 1 then + awful.util.spawn("xterm") + else + if #client.get() == 2 then + local c = client.get()[1] + assert(c == client.focus) + if c then + assert(c.border_color == "#ff0000") + return true + end + + end + end + end +} + +require("_runner").run_steps(steps) diff --git a/tests/test-urgent.lua b/tests/test-urgent.lua new file mode 100644 index 000000000..c33f01c0b --- /dev/null +++ b/tests/test-urgent.lua @@ -0,0 +1,106 @@ +--- Tests for urgent property. + +awful = require("awful") + +-- Some basic assertion that the tag is not marked "urgent" already. +assert(awful.tag.getproperty(tags[1][2], "urgent") == nil) + + +-- Setup signal handler which should be called. +-- TODO: generalize and move to runner. +local urgent_cb_done +client.connect_signal("property::urgent", function (c) + urgent_cb_done = true + assert(c.class == "XTerm", "Client should be xterm!") +end) + +local manage_cb_done +client.connect_signal("manage", function (c) + manage_cb_done = true + assert(c.class == "XTerm", "Client should be xterm!") +end) + + +-- Steps to do for this test. +local steps = { + -- Step 1: tag 2 should become urgent, when a client gets tagged via rules. + function(count) + if count == 1 then -- Setup. + urgent_cb_done = false + -- Select first tag. + awful.tag.viewonly(tags[1][1]) + + runner.add_to_default_rules({ rule = { class = "XTerm" }, + properties = { tag = tags[1][2], focus = true } }) + + awful.util.spawn("xterm") + end + if urgent_cb_done then + assert(awful.tag.getproperty(tags[1][2], "urgent") == true) + assert(awful.tag.getproperty(tags[1][2], "urgent_count") == 1) + return true + end + end, + + -- Step 2: when switching to tag 2, it should not be urgent anymore. + function(count) + if count == 1 then + -- Setup: switch to tag. + os.execute('xdotool key super+2') + + elseif awful.tag.selectedlist()[1] == tags[1][2] then + assert(#client.get() == 1) + c = client.get()[1] + assert(not c.urgent, "Client is not urgent anymore.") + assert(c == client.focus, "Client is focused.") + assert(awful.tag.getproperty(tags[1][2], "urgent") == false) + assert(awful.tag.getproperty(tags[1][2], "urgent_count") == 0) + return true + end + end, + + -- Step 3: tag 2 should not be urgent, but switched to. + function(count) + if count == 1 then -- Setup. + local urgent_cb_done = false + + -- Select first tag. + awful.tag.viewonly(tags[1][1]) + + runner.add_to_default_rules({ rule = { class = "XTerm" }, + properties = { tag = tags[1][2], focus = true, switchtotag = true }}) + + awful.util.spawn("xterm") + + elseif awful.tag.selectedlist()[1] == tags[1][2] then + assert(urgent_cb_done) + assert(awful.tag.getproperty(tags[1][2], "urgent") == false) + assert(awful.tag.getproperty(tags[1][2], "urgent_count") == 0) + assert(awful.tag.selectedlist()[2] == nil) + return true + end + end, + + + -- Step 4: tag 2 should not become urgent, when a client gets tagged via + -- rules with focus=false. + function(count) + if count == 1 then -- Setup. + client.get()[1]:kill() + manage_cb_done = false + + runner.add_to_default_rules({rule = { class = "XTerm" }, + properties = { tag = tags[1][2], focus = false }}) + + awful.util.spawn("xterm") + end + if manage_cb_done then + assert(client.get()[1].urgent == false) + assert(awful.tag.getproperty(tags[1][2], "urgent") == false) + assert(awful.tag.getproperty(tags[1][2], "urgent_count") == 0) + return true + end + end, +} + +require("_runner").run_steps(steps)