From 2939a84ea38f1ed6badd818e70f3da219b394987 Mon Sep 17 00:00:00 2001 From: Hisham Muhammad Date: Fri, 27 Mar 2015 16:29:09 -0300 Subject: [PATCH 1/8] Beginnings of a test suite! --- test_spec.lua | 176 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100755 test_spec.lua diff --git a/test_spec.lua b/test_spec.lua new file mode 100755 index 00000000..5546cd3e --- /dev/null +++ b/test_spec.lua @@ -0,0 +1,176 @@ +#!/usr/bin/env lua + +local VISUALTEST = os.getenv("VISUALTEST") + +local visual = VISUALTEST or false +local visual_delay = VISUALTEST and (tonumber(VISUALTEST)) or 0.1 + +local signal = require("posix.signal") +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) + +os.execute("make coverage") +os.execute("rm -f *.gcda */*.gcda") +os.execute("rm -f coverage.info test.htoprc") +os.execute("rm -rf lcov") + +rt:forkPty("HTOPRC=./test.htoprc ./htop") + +local stdscr, term_win +-- Curses initalization 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 delay(t) + time.nanosleep({ tv_sec = math.floor(t), tv_nsec = (t - math.floor(t)) * 1000000000 }) +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) + for i = 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 + 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 + +time.nanosleep({ tv_sec = 0, tv_nsec = 150000000 }) -- give some time for htop to initialize. + +local pos_panelhdr = (function() + for i = 1, 24 do + if is_string_at(3, i, "PID") then + return i + end + end +end)() or 1 + +show() + +local terminated = false +signal.signal(signal.SIGCHLD, function(_) + terminated = true +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 + +describe("htop test suite", function() + running_it("visits each setup screen", function() + send("S") + send(curses.KEY_DOWN, 3) + send(curses.KEY_F10) + end) + running_it("changes CPU affinity for a process", function() + send("a") + send(" \n") + send(ESC) + end) + running_it("adds and removes a clock widget", function() + send("S") + send(curses.KEY_RIGHT, 3) + send("\n") + send(curses.KEY_UP, 4) + send("\n") + local time = check_string_at(41, 2, "Time") + send(curses.KEY_DC) + delay(0.1) + local not_time = check_string_at(41, 2, "Time") + send(ESC) + assert.equal(check(time)) + assert.not_equal(check(not_time)) + end) + running_it("adds a hostname widget", function() + send("S") + send(curses.KEY_RIGHT, 3) + send(curses.KEY_DOWN, 8) + send("\n") + send("\n") + send(ESC) + end) + it("finally quits", function() + assert(not terminated) + send("q") + while not terminated do + unistd.sleep(1) + end + assert(terminated) + if visual then + curses.endwin() + end + os.execute("make lcov && xdg-open lcov/index.html") + end) +end) + From 4eee71051b2fe20a3d9453af76cf8a10b259f800 Mon Sep 17 00:00:00 2001 From: Hisham Muhammad Date: Wed, 25 Mar 2015 17:14:40 -0300 Subject: [PATCH 2/8] Add coverage testing rules --- Makefile.am | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Makefile.am b/Makefile.am index 5e5ea0b8..73959199 100644 --- a/Makefile.am +++ b/Makefile.am @@ -65,9 +65,18 @@ profile: debug: $(MAKE) all CFLAGS="" AM_CPPFLAGS="-ggdb -DDEBUG" +coverage: + $(MAKE) all CFLAGS="" AM_CPPFLAGS="-fprofile-arcs -ftest-coverage -DDEBUG" AM_LDFLAGS="-lgcov" + .c.h: @srcdir@/scripts/MakeHeader.py $< cppcheck: cppcheck -q -v . --enable=all -DHAVE_CGROUP -DHAVE_OPENVZ -DHAVE_TASKSTATS +.PHONY: lcov + +lcov: + mkdir -p lcov + lcov --capture --directory . --output-file coverage.info + genhtml coverage.info --output-directory lcov From 99e58b3200663793824917eff9b133455c287189 Mon Sep 17 00:00:00 2001 From: Hisham Muhammad Date: Sat, 28 Mar 2015 19:36:06 -0300 Subject: [PATCH 3/8] Add test, change env variable name --- test_spec.lua | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/test_spec.lua b/test_spec.lua index 5546cd3e..38828678 100755 --- a/test_spec.lua +++ b/test_spec.lua @@ -1,9 +1,9 @@ #!/usr/bin/env lua -local VISUALTEST = os.getenv("VISUALTEST") +local VISUALDELAY = os.getenv("VISUALDELAY") -local visual = VISUALTEST or false -local visual_delay = VISUALTEST and (tonumber(VISUALTEST)) or 0.1 +local visual = VISUALDELAY or false +local visual_delay = VISUALDELAY and (tonumber(VISUALDELAY)) or 0.1 local signal = require("posix.signal") local unistd = require("posix.unistd") @@ -133,6 +133,25 @@ describe("htop test suite", function() 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(0.2) + local ppid = check_string_at(2, pos_panelhdr, "PPID") + send("S") + send(curses.KEY_DOWN, 3) + send(curses.KEY_RIGHT, 1) + send(curses.KEY_DC) + send(curses.KEY_F10) + delay(0.2) + local not_ppid = check_string_at(2, pos_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") @@ -146,7 +165,7 @@ describe("htop test suite", function() send("\n") local time = check_string_at(41, 2, "Time") send(curses.KEY_DC) - delay(0.1) + delay(0.3) local not_time = check_string_at(41, 2, "Time") send(ESC) assert.equal(check(time)) From 849adf6155537950a9fb9b32d44b89dc5776a5af Mon Sep 17 00:00:00 2001 From: Hisham Muhammad Date: Mon, 30 Mar 2015 01:52:39 -0300 Subject: [PATCH 4/8] 78.8% test coverage! --- test_spec.lua | 323 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 288 insertions(+), 35 deletions(-) diff --git a/test_spec.lua b/test_spec.lua index 38828678..38f27b53 100755 --- a/test_spec.lua +++ b/test_spec.lua @@ -5,7 +5,6 @@ local VISUALDELAY = os.getenv("VISUALDELAY") local visual = VISUALDELAY or false local visual_delay = VISUALDELAY and (tonumber(VISUALDELAY)) or 0.1 -local signal = require("posix.signal") local unistd = require("posix.unistd") local time = require("posix.time") local curses = require("posix.curses") @@ -17,7 +16,10 @@ 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("HTOPRC=./test.htoprc ./htop") local stdscr, term_win @@ -65,6 +67,7 @@ local function show(key) end local function send(key, times) + if times == 0 then return end for i = 1, times or 1 do delay(0.003) -- 30ms delay to avoid clobbering Esc sequences if type(key) == "string" then @@ -95,31 +98,34 @@ local function check_string_at(x, y, str) return { str, string_at(x, y, #str) } end -local ESC = 27 +local ESC = "\27\27" time.nanosleep({ tv_sec = 0, tv_nsec = 150000000 }) -- give some time for htop to initialize. -local pos_panelhdr = (function() - for i = 1, 24 do - if is_string_at(3, i, "PID") then - return i +local y_panelhdr = (function() + for y = 1, 24 do + if is_string_at(3, y, "PID") then + return y end end end)() or 1 +local x_metercol2 = 43 + show() -local terminated = false -signal.signal(signal.SIGCHLD, function(_) - terminated = true -end) +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) + assert(not terminated()) show() fn() - assert(not terminated) + assert(not terminated()) end) end @@ -127,12 +133,181 @@ local function check(t) return t[1], t[2] end +local attrs = { + black_on_cyan = 6, + red_on_cyan = 22, +} + +local function find_selected_y() + for y = 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 + describe("htop test suite", function() + + running_it("performs incremental filter", function() + send("\\") + send("busted") + send("\n") + delay(0.2) + 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(0.15) + rt:update() + local initpid = check_string_at(1, y_panelhdr + 1, " 1") + delay(0.15) + 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) + send("\n") + delay(0.4) + rt:update() + 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(curses.KEY_HOME) + assert.equal(check(ourpid)) + 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(0.3) + 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(0.3) + 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(1) + delay(1) + send(ESC) + end) + + running_it("runs lsof", function() + send(curses.KEY_HOME) + send("/") + send("busted") + send("\n") + send("l") + delay(1) + delay(1) + send(ESC) + end) + + running_it("cycles through meter modes", function() + send("S") + send(curses.KEY_RIGHT) + send(curses.KEY_DOWN) + send("\n\n\n\n\n") + send(ESC) + end) + + running_it("show process of a user", function() + send(curses.KEY_F5) + send("u") + send(curses.KEY_DOWN) + delay(0.3) + rt:update() + local chosen = string_at(1, y_panelhdr + 2, 9) + send("\n") + send(curses.KEY_HOME) + delay(0.3) + rt:update() + local shown = string_at(7, y_panelhdr + 1, 9) + send("u") + send("\n") + send(curses.KEY_HOME) + delay(0.3) + 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(0.3) + 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) @@ -141,51 +316,129 @@ describe("htop test suite", function() send("\n") send(curses.KEY_F10) delay(0.2) - local ppid = check_string_at(2, pos_panelhdr, "PPID") + 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(0.2) - local not_ppid = check_string_at(2, pos_panelhdr, "PPID") + 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("adds and removes a clock widget", function() - send("S") - send(curses.KEY_RIGHT, 3) - send("\n") - send(curses.KEY_UP, 4) - send("\n") - local time = check_string_at(41, 2, "Time") - send(curses.KEY_DC) - delay(0.3) - local not_time = check_string_at(41, 2, "Time") - send(ESC) - assert.equal(check(time)) - assert.not_equal(check(not_time)) - end) - running_it("adds a hostname widget", function() - send("S") - send(curses.KEY_RIGHT, 3) - send(curses.KEY_DOWN, 8) + + 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) + + 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" }, + } + + 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) + if branch == "wip" then + send("\n") + send(curses.KEY_UP, 4) + send("\n") + else + send(curses.KEY_F6) + send(curses.KEY_LEFT) + send(curses.KEY_DOWN, 4) + send(curses.KEY_F7, 4) + end + delay(0.15) + rt:update() + local with = check_string_at(x_metercol2, 2, item.string) + send(curses.KEY_DC) + delay(0.15) + 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 i = 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 + send("S") + send(curses.KEY_DOWN) + send(curses.KEY_RIGHT) + send(curses.KEY_DOWN, item.down) + send("\n") + send(curses.KEY_F10) + delay(0.1) + end + end) + end + + for i = 1, 53 do + running_it("show column "..i, function() + send("S") + send(curses.KEY_END) + send(curses.KEY_RIGHT, 2) + send(curses.KEY_DOWN, i) + send("\n") + send(curses.KEY_F10) + end) + end + it("finally quits", function() - assert(not terminated) + assert(not terminated()) send("q") - while not terminated do + while not terminated() do unistd.sleep(1) end - assert(terminated) + assert(terminated()) if visual then curses.endwin() end From 5320bab202aa5dad893a0da5848be1c40728d9e1 Mon Sep 17 00:00:00 2001 From: Hisham Muhammad Date: Tue, 31 Mar 2015 22:31:06 -0300 Subject: [PATCH 5/8] Cleanup unused field. --- Panel.c | 1 - Panel.h | 1 - 2 files changed, 2 deletions(-) diff --git a/Panel.c b/Panel.c index 681e9ca5..866fccca 100644 --- a/Panel.c +++ b/Panel.c @@ -48,7 +48,6 @@ typedef struct PanelClass_ { struct Panel_ { Object super; - PanelClass* class; int x, y, w, h; WINDOW* window; Vector* items; diff --git a/Panel.h b/Panel.h index 91c0a40c..d1aaec36 100644 --- a/Panel.h +++ b/Panel.h @@ -37,7 +37,6 @@ typedef struct PanelClass_ { struct Panel_ { Object super; - PanelClass* class; int x, y, w, h; WINDOW* window; Vector* items; From 4c24a9b462e2e55e9d2f6d24694d5408c886c556 Mon Sep 17 00:00:00 2001 From: Hisham Muhammad Date: Tue, 31 Mar 2015 23:23:10 -0300 Subject: [PATCH 6/8] Fixes to subclassing Process. --- Process.c | 28 ++++++++++++++++++++-------- Process.h | 15 ++++++++++++++- htop.c | 2 +- linux/LinuxProcess.c | 17 ++++++++++++++--- linux/LinuxProcess.h | 2 ++ 5 files changed, 51 insertions(+), 13 deletions(-) diff --git a/Process.c b/Process.c index ff1baba2..9048cfe2 100644 --- a/Process.c +++ b/Process.c @@ -192,6 +192,15 @@ typedef struct Process_ { } Process; +typedef void (*Process_WriteField)(Process*, RichString*, ProcessField); + +typedef struct ProcessClass_ { + const ObjectClass super; + const Process_WriteField writeField; +} ProcessClass; + +#define As_Process(this_) ((ProcessClass*)((this_)->super.klass)) + }*/ const char *Process_fieldNames[] = { @@ -470,7 +479,7 @@ static inline void Process_outputRate(RichString* str, int attr, char* buffer, i RichString_append(str, attr, buffer); } -static void Process_writeField(Process* this, RichString* str, ProcessField field) { +void Process_writeField(Process* this, RichString* str, ProcessField field) { char buffer[256]; buffer[255] = '\0'; int attr = CRT_colors[DEFAULT_COLOR]; int baseattr = CRT_colors[PROCESS_BASENAME]; @@ -630,12 +639,12 @@ static void Process_writeField(Process* this, RichString* str, ProcessField fiel RichString_append(str, attr, buffer); } -static void Process_display(Object* cast, RichString* out) { +void Process_display(Object* cast, RichString* out) { Process* this = (Process*) cast; ProcessField* fields = this->pl->fields; RichString_prune(out); for (int i = 0; fields[i]; i++) - Process_writeField(this, out, fields[i]); + As_Process(this)->writeField(this, out, fields[i]); if (this->pl->shadowOtherUsers && (int)this->st_uid != Process_getuid) RichString_setAttr(out, CRT_colors[PROCESS_SHADOW]); if (this->tag == true) @@ -651,11 +660,14 @@ void Process_done(Process* this) { #endif } -ObjectClass Process_class = { - .extends = Class(Object), - .display = Process_display, - .delete = Process_delete, - .compare = Process_compare +ProcessClass Process_class = { + .super = { + .extends = Class(Object), + .display = Process_display, + .delete = Process_delete, + .compare = Process_compare + }, + .writeField = Process_writeField, }; void Process_init(Process* this, struct ProcessList_* pl) { diff --git a/Process.h b/Process.h index be54adab..a08ec92c 100644 --- a/Process.h +++ b/Process.h @@ -171,6 +171,15 @@ typedef struct Process_ { } Process; +typedef void (*Process_WriteField)(Process*, RichString*, ProcessField); + +typedef struct ProcessClass_ { + const ObjectClass super; + const Process_WriteField writeField; +} ProcessClass; + +#define As_Process(this_) ((ProcessClass*)((this_)->super.klass)) + extern const char *Process_fieldNames[]; @@ -189,9 +198,13 @@ void Process_setupColumnWidths(); #define ONE_DECIMAL_M (ONE_DECIMAL_K * ONE_DECIMAL_K) #define ONE_DECIMAL_G (ONE_DECIMAL_M * ONE_DECIMAL_K) +void Process_writeField(Process* this, RichString* str, ProcessField field); + +void Process_display(Object* cast, RichString* out); + void Process_done(Process* this); -extern ObjectClass Process_class; +extern ProcessClass Process_class; void Process_init(Process* this, struct ProcessList_* pl); diff --git a/htop.c b/htop.c index 62e49c9c..ce173275 100644 --- a/htop.c +++ b/htop.c @@ -694,7 +694,7 @@ int main(int argc, char** argv) { CRT_init(settings->delay, settings->colorScheme); - Panel* panel = Panel_new(0, headerHeight, COLS, LINES - headerHeight - 2, false, &Process_class); + Panel* panel = Panel_new(0, headerHeight, COLS, LINES - headerHeight - 2, false, (ObjectClass*) &Process_class); ProcessList_setPanel(pl, panel); FunctionBar* defaultBar = FunctionBar_new(defaultFunctions, NULL, NULL); diff --git a/linux/LinuxProcess.c b/linux/LinuxProcess.c index b534d4da..269843a4 100644 --- a/linux/LinuxProcess.c +++ b/linux/LinuxProcess.c @@ -27,9 +27,19 @@ typedef struct LinuxProcess_ { }*/ +ProcessClass LinuxProcess_class = { + .super = { + .extends = Class(Process), + .display = Process_display, + .delete = Process_delete, + .compare = LinuxProcess_compare + }, + .writeField = (Process_WriteField) LinuxProcess_writeField, +}; + LinuxProcess* LinuxProcess_new(ProcessList* pl) { LinuxProcess* this = calloc(sizeof(LinuxProcess), 1); - Object_setClass(this, Class(Process)); + Object_setClass(this, Class(LinuxProcess)); Process_init(&this->super, pl); return this; } @@ -85,7 +95,8 @@ void LinuxProcess_writeField(LinuxProcess* this, RichString* str, ProcessField f break; } default: - snprintf(buffer, n, "- "); + Process_writeField((Process*)this, str, field); + return; } RichString_append(str, attr, buffer); } @@ -104,6 +115,6 @@ long LinuxProcess_compare(const void* v1, const void* v2) { case IO_PRIORITY: return LinuxProcess_effectiveIOPriority(p1) - LinuxProcess_effectiveIOPriority(p2); default: - return (p1->super.pid - p2->super.pid); + return Process_compare(v1, v2); } } diff --git a/linux/LinuxProcess.h b/linux/LinuxProcess.h index a162c5eb..f7a14764 100644 --- a/linux/LinuxProcess.h +++ b/linux/LinuxProcess.h @@ -20,6 +20,8 @@ typedef struct LinuxProcess_ { #define Process_delete LinuxProcess_delete +extern ProcessClass LinuxProcess_class; + LinuxProcess* LinuxProcess_new(ProcessList* pl); void LinuxProcess_delete(Object* cast); From a7236559a702055d5d2d98f459de88188819c2fd Mon Sep 17 00:00:00 2001 From: Hisham Muhammad Date: Tue, 31 Mar 2015 23:23:40 -0300 Subject: [PATCH 7/8] 81.1% test coverage! --- test_spec.lua | 75 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 61 insertions(+), 14 deletions(-) diff --git a/test_spec.lua b/test_spec.lua index 38f27b53..e57437d6 100755 --- a/test_spec.lua +++ b/test_spec.lua @@ -20,7 +20,7 @@ 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("HTOPRC=./test.htoprc ./htop") +rt:forkPty("LC_ALL=C HTOPRC=./test.htoprc ./htop") local stdscr, term_win -- Curses initalization needed even when not in visual mode @@ -68,7 +68,7 @@ end local function send(key, times) if times == 0 then return end - for i = 1, times or 1 do + 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 @@ -138,8 +138,9 @@ local attrs = { red_on_cyan = 22, } -local function find_selected_y() - for y = y_panelhdr + 1, rt:rows() - 1 do +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 @@ -161,7 +162,7 @@ describe("htop test suite", function() running_it("performs incremental filter", function() send("\\") - send("busted") + send("x\127bux\127sted") -- test backspace send("\n") delay(0.2) rt:update() @@ -188,7 +189,6 @@ describe("htop test suite", function() local attr = rt:cellAttr(rt:rows() - 1, 30) send("\n") delay(0.4) - rt:update() local line = find_selected_y() local pid = (" "..tostring(unistd.getpid())):sub(-5) assert.equal(attr, attrs.black_on_cyan) @@ -224,7 +224,6 @@ describe("htop test suite", function() send("\n") send("s") delay(1) - delay(1) send(ESC) end) @@ -235,15 +234,48 @@ describe("htop test suite", function() send("\n") send("l") delay(1) - delay(1) send(ESC) end) - running_it("cycles through meter modes", function() + 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(1) + 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(1) + 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") - send(curses.KEY_RIGHT) - send(curses.KEY_DOWN) - send("\n\n\n\n\n") + for _ = 1, 2 do + send(curses.KEY_RIGHT) + for _ = 1, 3 do + send("\n", 4) + send(curses.KEY_DOWN) + end + end send(ESC) end) @@ -366,6 +398,7 @@ describe("htop test suite", function() send(curses.KEY_DOWN, 4) send(curses.KEY_F7, 4) end + send(curses.KEY_F4, 4) -- cycle through meter modes delay(0.15) rt:update() local with = check_string_at(x_metercol2, 2, item.string) @@ -382,7 +415,7 @@ describe("htop test suite", function() send(curses.KEY_F2) send(curses.KEY_DOWN, 2) send(curses.KEY_RIGHT) - for i = 1, 6 do + for _ = 1, 6 do send("\n") send(curses.KEY_DOWN) end @@ -420,8 +453,22 @@ describe("htop test suite", function() 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_F10) + delay(0.1) + end + end) - for i = 1, 53 do + for i = 1, 62 do running_it("show column "..i, function() send("S") send(curses.KEY_END) From cb8ac6b0f1f456f94efc8aead597faf6a95788da Mon Sep 17 00:00:00 2001 From: Hisham Muhammad Date: Tue, 31 Mar 2015 23:24:06 -0300 Subject: [PATCH 8/8] Ignore coverage files. --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index c21162d6..85f580bf 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,12 @@ htop # all object files *.o +*.gcda +*/*.gcda +*.gcno +*/*.gcno +*.h.gch + .deps/ Makefile Makefile.in