aboutsummaryrefslogtreecommitdiffstats
path: root/test_spec.lua
diff options
context:
space:
mode:
authorDaniel Lange <DLange@git.local>2020-08-27 07:48:10 +0200
committerDaniel Lange <DLange@git.local>2020-08-27 07:48:10 +0200
commitf3147ea2d1598914c2db53e8cfb34c8ff81e2ff4 (patch)
tree3ee82b2af2ab3d38b6e4b07f3994516aac72f742 /test_spec.lua
parentdf568a576f7b44ac5a2b9b7222c7f39d9932f626 (diff)
downloaddebian_htop-f3147ea2d1598914c2db53e8cfb34c8ff81e2ff4.tar.gz
debian_htop-f3147ea2d1598914c2db53e8cfb34c8ff81e2ff4.tar.bz2
debian_htop-f3147ea2d1598914c2db53e8cfb34c8ff81e2ff4.zip
New upstream version 3.0.0upstream/3.0.0
Diffstat (limited to 'test_spec.lua')
-rwxr-xr-xtest_spec.lua687
1 files changed, 687 insertions, 0 deletions
diff --git a/test_spec.lua b/test_spec.lua
new file mode 100755
index 0000000..2ae8015
--- /dev/null
+++ b/test_spec.lua
@@ -0,0 +1,687 @@
+#!/usr/bin/env lua
+
+local VISUALDELAY = os.getenv("VISUALDELAY")
+
+local visual = VISUALDELAY or false
+local visual_delay = VISUALDELAY and (tonumber(VISUALDELAY)) or 0.1
+local short_delay = 0.3
+local long_delay = 1
+
+local unistd = require("posix.unistd")
+local time = require("posix.time")
+local curses = require("posix.curses")
+local rote = require("rote")
+
+local rt = rote.RoteTerm(24, 80)
+
+--[[
+local function os_execread(cmd)
+ local fd = io.popen(cmd, "r")
+ local out = fd:read("*a")
+ fd:close()
+ return (out:gsub("\n$", ""))
+end
+]]
+--local branch = os_execread("git branch | grep '*'"):sub(3)
+--print("Running in branch "..branch)
+
+os.execute("make coverage")
+os.execute("rm -f *.gcda */*.gcda")
+os.execute("rm -f coverage.info test.htoprc")
+os.execute("rm -rf lcov")
+os.execute("killall htop")
+os.execute("ps aux | grep '[s]leep 12345' | awk '{print $2}' | xargs kill 2> /dev/null")
+
+os.execute("cp ./default.htoprc ./test.htoprc")
+rt:forkPty("LC_ALL=C HTOPRC=./test.htoprc ./htop 2> htop-valgrind.txt")
+
+local stdscr, term_win
+-- Curses initialization needed even when not in visual mode
+-- because luaposix only initializes KEY_* constants after initscr().
+stdscr = curses.initscr()
+if visual then
+ curses.echo(false)
+ curses.start_color()
+ curses.raw(true)
+ curses.halfdelay(1)
+ stdscr:keypad(true)
+ term_win = curses.newwin(24, 80, 0, 0)
+ local function makePair(foreground, background)
+ return background * 8 + 7 - foreground
+ end
+ -- initialize the color pairs the way rt:draw() expects it
+ for foreground = 0, 7 do
+ for background = 0, 7 do
+ if foreground ~= 7 or background ~= 0 then
+ local pair = makePair(foreground, background)
+ curses.init_pair(pair, foreground, background)
+ end
+ end
+ end
+else
+ curses.endwin()
+end
+
+local function show(key)
+ rt:update()
+ if visual then
+ rt:draw(term_win, 0, 0)
+ if key then
+ term_win:mvaddstr(0, 0, tostring(key))
+ end
+ term_win:refresh()
+
+ delay(visual_delay)
+ end
+end
+
+local function send(key, times, quick)
+ if times == 0 then return end
+ for _ = 1, times or 1 do
+ delay(0.003) -- 30ms delay to avoid clobbering Esc sequences
+ if type(key) == "string" then
+ for c in key:gmatch('.') do
+ rt:keyPress(string.byte(c))
+ end
+ else
+ rt:keyPress(key)
+ end
+ if not quick then
+ show(key)
+ end
+ end
+ if quick then
+ show(key)
+ end
+end
+
+local function string_at(x, y, len)
+ rt:update()
+ local out = {}
+ for i = 1, len do
+ out[#out+1] = rt:cellChar(y-1, x+i-2)
+ end
+ return table.concat(out)
+end
+
+local function is_string_at(x, y, str)
+ return string_at(x, y, #str) == str
+end
+
+local function check_string_at(x, y, str)
+ return { str, string_at(x, y, #str) }
+end
+
+local ESC = "\27\27"
+
+function delay(t)
+ time.nanosleep({ tv_sec = math.floor(t), tv_nsec = (t - math.floor(t)) * 1000000000 })
+end
+
+delay(2) -- give some time for htop to initialize.
+rt:update()
+
+local y_panelhdr = (function()
+ for y = 1, 24 do
+ if is_string_at(3, y, "PID") then
+ return y
+ end
+ end
+end)() or 1
+
+assert.not_equal(y_panelhdr, 1)
+
+local x_metercol2 = 41
+
+show()
+
+os.execute("sleep 12345 &")
+
+local function terminated()
+ return not os.execute("ps aux | grep -q '\\./[h]top'")
+end
+
+local function running_it(desc, fn)
+ it(desc, function()
+ assert(not terminated())
+ show()
+ fn()
+ assert(not terminated())
+ end)
+end
+
+local function check(t)
+ return t[1], t[2]
+end
+
+local attrs = {
+ black_on_cyan = 6,
+ red_on_cyan = 22,
+ white_on_black = 176,
+ yellow_on_black = 112,
+}
+
+local function find_selected_y(from)
+ rt:update()
+ for y = from or (y_panelhdr + 1), rt:rows() - 1 do
+ local attr = rt:cellAttr(y-1, 1)
+ if attr == attrs.black_on_cyan then
+ return y
+ end
+ end
+ return y_panelhdr + 1
+end
+
+local function find_command_x()
+ for x = 1, 80 do
+ if is_string_at(x, y_panelhdr, "Command") then
+ return x
+ end
+ end
+ return 64
+end
+
+local function set_display_option(n)
+ send("S")
+ send(curses.KEY_DOWN)
+ send(curses.KEY_RIGHT)
+ send(curses.KEY_DOWN, n, "quick")
+ send("\n")
+ send(curses.KEY_F10)
+end
+
+describe("htop test suite", function()
+
+ running_it("performs incremental filter", function()
+ send("\\")
+ send("x\127bux\127sted") -- test backspace
+ send("\n")
+ delay(short_delay)
+ rt:update()
+ local pid = (" "..tostring(unistd.getpid())):sub(-5)
+ local ourpid = check_string_at(1, y_panelhdr + 1, pid)
+ send("\\")
+ send(ESC)
+ send(curses.KEY_F5)
+ send(curses.KEY_HOME)
+ delay(short_delay)
+ rt:update()
+ local initpid = check_string_at(1, y_panelhdr + 1, " 1")
+ delay(short_delay)
+ rt:update()
+ send(curses.KEY_F5)
+ assert.equal(check(ourpid))
+ assert.equal(check(initpid))
+ end)
+
+ running_it("performs incremental search", function()
+ send(curses.KEY_HOME)
+ send("/")
+ send("busted")
+ local attr = rt:cellAttr(rt:rows() - 1, 30)
+ delay(short_delay)
+ local line = find_selected_y()
+ local pid = (" "..tostring(unistd.getpid())):sub(-5)
+ assert.equal(attr, attrs.black_on_cyan)
+ local ourpid = check_string_at(1, line, pid)
+ send("\n")
+ send(curses.KEY_HOME)
+ assert.equal(check(ourpid))
+ end)
+
+ running_it("performs pid search", function()
+ send(curses.KEY_F5)
+ send(curses.KEY_END)
+ send("1")
+ delay(short_delay)
+ local line = find_selected_y()
+ local initpid = check_string_at(1, line, " 1")
+ send(curses.KEY_F5)
+ assert.equal(check(initpid))
+ end)
+
+
+ running_it("horizontal scroll", function()
+ local h_scroll = 20
+ send(curses.KEY_F5)
+ delay(short_delay)
+ local str1 = string_at(1+h_scroll, y_panelhdr+1, 5)
+ send(curses.KEY_RIGHT)
+ delay(short_delay)
+ local str2 = string_at(1, y_panelhdr+1, 5)
+ send(curses.KEY_LEFT)
+ delay(short_delay)
+ local str3 = string_at(1+h_scroll, y_panelhdr+1, 5)
+ send(curses.KEY_LEFT)
+ delay(short_delay)
+ local str4 = string_at(1+h_scroll, y_panelhdr+1, 5)
+ send(curses.KEY_F5)
+ assert.equal(str1, str2)
+ assert.equal(str2, str3)
+ assert.equal(str3, str4)
+ end)
+
+ running_it("kills a process", function()
+ send(curses.KEY_HOME)
+ send("\\")
+ send("sleep 12345")
+ local attr = rt:cellAttr(rt:rows() - 1, 30)
+ assert.equal(attr, attrs.black_on_cyan)
+ send("\n")
+ delay(short_delay)
+ rt:update()
+ local col = find_command_x()
+ local procname = check_string_at(col, y_panelhdr + 1, "sleep 12345")
+ send("k")
+ send("\n")
+ send("\\")
+ send(ESC)
+ delay(short_delay)
+ assert.equal(check(procname))
+ assert.not_equal((os.execute("ps aux | grep -q '[s]leep 12345'")), true)
+ end)
+
+ running_it("runs strace", function()
+ send(curses.KEY_HOME)
+ send("/")
+ send("busted")
+ send("\n")
+ send("s")
+ delay(long_delay)
+ send(ESC)
+ end)
+
+ running_it("runs lsof", function()
+ send(curses.KEY_HOME)
+ send("/")
+ send("busted")
+ send("\n")
+ send("l")
+ delay(long_delay)
+ send(ESC)
+ end)
+
+ running_it("performs filtering in lsof", function()
+ send(curses.KEY_HOME)
+ send("/")
+ send("htop")
+ send("\n")
+ send("l")
+ send(curses.KEY_F4)
+ send("pipe")
+ delay(long_delay)
+ local pipefd = check_string_at(1, 3, " 3")
+ send(ESC)
+ assert.equal(check(pipefd))
+ end)
+
+ running_it("performs search in lsof", function()
+ send(curses.KEY_HOME)
+ send("/")
+ send("htop")
+ send("\n")
+ send("l")
+ send(curses.KEY_F3)
+ send("pipe")
+ delay(long_delay)
+ local line = find_selected_y(3)
+ local pipefd = check_string_at(1, line, " 3")
+ send(ESC)
+ assert.equal(check(pipefd))
+ end)
+
+
+ running_it("cycles through meter modes in the default meters", function()
+ send("S")
+ for _ = 1, 2 do
+ send(curses.KEY_RIGHT)
+ for _ = 1, 3 do
+ send("\n", 4)
+ send(curses.KEY_DOWN)
+ end
+ end
+ send(ESC)
+ end)
+
+ running_it("show process of a user", function()
+ send(curses.KEY_F5)
+ send("u")
+ send(curses.KEY_DOWN)
+ delay(short_delay)
+ rt:update()
+ local chosen = string_at(1, y_panelhdr + 2, 9)
+ send("\n")
+ send(curses.KEY_HOME)
+ delay(short_delay)
+ rt:update()
+ local shown = string_at(7, y_panelhdr + 1, 9)
+ send("u")
+ send("\n")
+ send(curses.KEY_HOME)
+ delay(short_delay)
+ rt:update()
+ local inituser = string_at(7, y_panelhdr + 1, 9)
+ send(curses.KEY_F5)
+ assert.equal(shown, chosen)
+ assert.equal(inituser, "root ")
+ end)
+
+ running_it("performs failing search", function()
+ send(curses.KEY_HOME)
+ send("/")
+ send("xxxxxxxxxx")
+ delay(short_delay)
+ rt:update()
+ local attr = rt:cellAttr(rt:rows() - 1, 30)
+ assert.equal(attr, attrs.red_on_cyan)
+ send("\n")
+ end)
+
+ running_it("cycles through search", function()
+ send(curses.KEY_HOME)
+ send("/")
+ send("sh")
+ local lastpid
+ local pidpairs = {}
+ for _ = 1, 3 do
+ send(curses.KEY_F3)
+ local line = find_selected_y()
+ local pid = string_at(1, line, 5)
+ if lastpid then
+ pidpairs[#pidpairs + 1] = { lastpid, pid }
+ lastpid = pid
+ end
+ end
+ send(curses.KEY_HOME)
+ for _, pair in pairs(pidpairs) do
+ assert.not_equal(pair[1], pair[2])
+ end
+ end)
+
+ running_it("visits each setup screen", function()
+ send("S")
+ send(curses.KEY_DOWN, 3)
+ send(curses.KEY_F10)
+ end)
+
+ running_it("adds and removes PPID column", function()
+ send("S")
+ send(curses.KEY_DOWN, 3)
+ send(curses.KEY_RIGHT, 2)
+ send(curses.KEY_DOWN, 2)
+ send("\n")
+ send(curses.KEY_F10)
+ delay(short_delay)
+ local ppid = check_string_at(2, y_panelhdr, "PPID")
+ send("S")
+ send(curses.KEY_DOWN, 3)
+ send(curses.KEY_RIGHT, 1)
+ send(curses.KEY_DC)
+ send(curses.KEY_F10)
+ delay(short_delay)
+ local not_ppid = check_string_at(2, y_panelhdr, "PPID")
+ assert.equal(check(ppid))
+ assert.not_equal(check(not_ppid))
+ end)
+
+ running_it("changes CPU affinity for a process", function()
+ send("a")
+ send(" \n")
+ send(ESC)
+ end)
+
+ running_it("renices for a process", function()
+ send("/")
+ send("busted")
+ send("\n")
+ local line = find_selected_y()
+ local before = check_string_at(22, line, " 0")
+ send(curses.KEY_F8)
+ delay(short_delay)
+ local after = check_string_at(22, line, " 1")
+ assert.equal(check(before))
+ assert.equal(check(after))
+ end)
+
+ running_it("tries to lower nice for a process", function()
+ send("/")
+ send("busted")
+ send("\n")
+ local line = find_selected_y()
+ local before = string_at(22, line, 2)
+ send(curses.KEY_F7)
+ delay(short_delay)
+ local after = string_at(22, line, 2)
+ assert.equal(before, after) -- no permissions
+ end)
+
+ running_it("invert sort order", function()
+ local cpu_col = 45
+ send("P")
+ send("I")
+ send(curses.KEY_HOME)
+ delay(short_delay)
+ local zerocpu = check_string_at(cpu_col, y_panelhdr + 1, " 0.0")
+ send("I")
+ delay(short_delay)
+ local nonzerocpu = check_string_at(cpu_col, y_panelhdr + 1, " 0.0")
+ assert.equal(check(zerocpu))
+ assert.not_equal(check(nonzerocpu))
+ end)
+
+ running_it("changes IO priority for a process", function()
+ send("/")
+ send("htop")
+ send("\n")
+ send("i")
+ send(curses.KEY_END)
+ send("\n")
+ send(ESC)
+ end)
+
+ running_it("shows help", function()
+ send(curses.KEY_F1)
+ send("\n")
+ set_display_option(9)
+ send(curses.KEY_F1)
+ send("\n")
+ set_display_option(9)
+ end)
+
+ running_it("moves meters around", function()
+ send("S")
+ send(curses.KEY_RIGHT)
+ send(curses.KEY_UP)
+ send("\n")
+ send(curses.KEY_DOWN)
+ send(curses.KEY_UP)
+ send(curses.KEY_RIGHT)
+ send(curses.KEY_RIGHT)
+ send(curses.KEY_LEFT)
+ send(curses.KEY_LEFT)
+ send("\n")
+ send(curses.KEY_F10)
+ end)
+
+ local meters = {
+ { name = "clock", down = 0, string = "Time" },
+ { name = "load", down = 2, string = "Load" },
+ { name = "battery", down = 7, string = "Battery" },
+ { name = "hostname", down = 8, string = "Hostname" },
+ { name = "memory", down = 3, string = "Mem" },
+ { name = "CPU average", down = 16, string = "Avg" },
+ }
+
+ running_it("checks various CPU meters", function()
+ send("S")
+ send(curses.KEY_RIGHT, 3)
+ send(curses.KEY_DOWN, 9, "quick")
+ for _ = 9, 14 do
+ send("\n")
+ send("\n")
+ send(curses.KEY_DC)
+ send(curses.KEY_RIGHT)
+ send(curses.KEY_DOWN)
+ end
+ end)
+
+ for _, item in ipairs(meters) do
+ running_it("adds and removes a "..item.name.." widget", function()
+ send("S")
+ send(curses.KEY_RIGHT, 3)
+ send(curses.KEY_DOWN, item.down)
+ send("\n")
+ send(curses.KEY_UP, 4)
+ send("\n")
+ send(curses.KEY_F4, 4) -- cycle through meter modes
+ delay(short_delay)
+ rt:update()
+ local with = check_string_at(x_metercol2, 2, item.string)
+ send(curses.KEY_DC)
+ delay(short_delay)
+ local without = check_string_at(x_metercol2, 2, item.string)
+ send(curses.KEY_F10)
+ assert.equal(check(with))
+ assert.not_equal(check(without))
+ end)
+ end
+
+ running_it("goes through themes", function()
+ send(curses.KEY_F2)
+ send(curses.KEY_DOWN, 2)
+ send(curses.KEY_RIGHT)
+ for _ = 1, 6 do
+ send("\n")
+ send(curses.KEY_DOWN)
+ end
+ send(curses.KEY_UP, 6)
+ send("\n")
+ send(curses.KEY_F10)
+ end)
+
+ local display_options = {
+ { name = "tree view", down = 0 },
+ { name = "shadow other user's process", down = 1 },
+ { name = "hide kernel threads", down = 2 },
+ { name = "hide userland threads", down = 3 },
+ { name = "display threads in different color", down = 4 },
+ { name = "show custom thread names", down = 5 },
+ { name = "highlight basename", down = 6 },
+ { name = "highlight large numbers", down = 7 },
+ { name = "leave margin around header", down = 8 },
+ { name = "use detailed CPU time", down = 9 },
+ { name = "count from zero", down = 10 },
+ { name = "update process names", down = 11 },
+ { name = "guest time in CPU%", down = 12 },
+ }
+
+ for _, item in ipairs(display_options) do
+ running_it("checks display option to "..item.name, function()
+ for _ = 1, 2 do
+ set_display_option(item.down)
+ delay(short_delay)
+ end
+ end)
+ end
+
+ running_it("shows detailed CPU with guest time", function()
+ for _ = 1, 2 do
+ send("S")
+ send(curses.KEY_DOWN)
+ send(curses.KEY_RIGHT)
+ send(curses.KEY_DOWN, 9)
+ send("\n")
+ send(curses.KEY_DOWN, 3)
+ send("\n")
+ send(curses.KEY_LEFT)
+ send(curses.KEY_UP)
+ send(curses.KEY_RIGHT)
+ send(curses.KEY_F4, 4) -- cycle through CPU meter modes
+ send(curses.KEY_F10)
+ delay(short_delay)
+ end
+ end)
+
+ running_it("expands and collapses tree", function()
+ send(curses.KEY_F5) -- tree view
+ send(curses.KEY_HOME)
+ send(curses.KEY_DOWN) -- second process in the tree
+ send("-")
+ send("+")
+ send(curses.KEY_F5)
+ end)
+
+ running_it("sets sort key", function()
+ send(".")
+ send("\n")
+ end)
+
+ running_it("tags all children", function()
+ send(curses.KEY_F5) -- tree view
+ send(curses.KEY_HOME) -- ensure we're at init
+ send("c")
+ local taggedattrs = {}
+ rt:update()
+ for y = y_panelhdr + 2, 23 do
+ table.insert(taggedattrs, rt:cellAttr(y-1, 4))
+ end
+ delay(short_delay)
+ send("U")
+ local untaggedattrs = {}
+ rt:update()
+ for y = y_panelhdr + 2, 23 do
+ table.insert(untaggedattrs, rt:cellAttr(y-1, 4))
+ end
+ send(curses.KEY_F5)
+
+ for _, taggedattr in ipairs(taggedattrs) do
+ assert.equal(attrs.yellow_on_black, taggedattr)
+ end
+ for _, untaggedattr in ipairs(untaggedattrs) do
+ assert.equal(attrs.white_on_black, untaggedattr)
+ end
+ end)
+
+ for i = 1, 62 do
+ running_it("show column "..i, function()
+ send("S")
+ send(curses.KEY_END)
+ send(curses.KEY_RIGHT, 1)
+ if i > 1 then
+ send(curses.KEY_DC)
+ end
+ send(curses.KEY_RIGHT, 1)
+ local down = i
+ while down > 13 do
+ send(curses.KEY_NPAGE)
+ down = down - 13
+ end
+ send(curses.KEY_DOWN, down, "quick")
+ send("\n")
+ send(curses.KEY_F10)
+ if i == 62 then
+ send("S")
+ send(curses.KEY_END)
+ send(curses.KEY_RIGHT, 1)
+ if i > 1 then
+ send(curses.KEY_DC)
+ end
+ send(curses.KEY_F10)
+ end
+ end)
+ end
+
+ it("finally quits", function()
+ assert(not terminated())
+ send("q")
+ while not terminated() do
+ unistd.sleep(1)
+ send("q")
+ end
+ assert(terminated())
+ if visual then
+ curses.endwin()
+ end
+ os.execute("make lcov && xdg-open lcov/index.html")
+ end)
+end)

© 2014-2024 Faster IT GmbH | imprint | privacy policy