123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687 |
- #!/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)
|