aboutsummaryrefslogtreecommitdiffstats
path: root/test/test_data.lua
blob: e5b589f4924d1caeefe4468aebaf78a126e31cc0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
-- Parser checks for lib/data.lua.
-- Run from the repo root:  lua test/test_data.lua
-- Parsers are what break silently on a kernel or hardware change, so they are
-- what gets a test. Everything else in this project is verified by screenshot.

package.path = './?.lua;' .. package.path
local data = require 'lib.data'

local function read(path)
  local f = assert(io.open(path, 'r'))
  local s = f:read('*a')
  f:close()
  return s
end

-- cpu_times: total and idle jiffies from the aggregate "cpu " line.
local total, idle = data.cpu_times(read('test/fixtures/proc_stat'))
-- 209094+2199+135469+11604898+24408+0+946 = 11977014
assert(total == 11977014, 'cpu total, got ' .. tostring(total))
-- idle field is the 4th value, 11604898; iowait (24408) counts as idle too
assert(idle == 11629306, 'cpu idle, got ' .. tostring(idle))

-- mem_info: values in kB, as the file gives them.
local mem = data.mem_info(read('test/fixtures/proc_meminfo'))
assert(mem.total == 31943076, 'mem total, got ' .. tostring(mem.total))
assert(mem.available == 25627088, 'mem available, got ' .. tostring(mem.available))
-- used is total minus available, which is what a user means by "used"
assert(mem.used == 6315988, 'mem used, got ' .. tostring(mem.used))

-- millidegrees: hwmon temp*_input is millidegrees C, rounded to whole degrees.
assert(data.millidegrees(read('test/fixtures/temp1_input')) == 53,
  'temp, got ' .. tostring(data.millidegrees(read('test/fixtures/temp1_input'))))
-- A missing or unreadable sensor must yield nil, not an error and not 0:
-- 0 degrees is a plausible reading and would be indistinguishable from failure.
assert(data.millidegrees(nil) == nil, 'nil input must give nil')
assert(data.millidegrees('') == nil, 'empty input must give nil')
assert(data.millidegrees('garbage') == nil, 'unparseable input must give nil')

-- CPU load is a delta between two samples, so the counter holds state.
-- The first call has no previous sample and must report nil, not a number:
-- any number it invented would be wrong, and 100% on startup looks like a
-- real spike.
local c = data.new_cpu_counter()
assert(c:sample(1000, 900) == nil, 'first sample must give nil')

-- Second sample: 100 more total jiffies, 50 of them idle, so 50% busy.
-- Held in a local first: calling sample() again inside the assert message would
-- advance the counter a third time.
local busy = c:sample(1100, 950)
assert(busy == 50.0, 'second sample, got ' .. tostring(busy))

-- A counter that did not advance means no elapsed time, not 0% load.
local c2 = data.new_cpu_counter()
c2:sample(1000, 900)
assert(c2:sample(1000, 900) == nil, 'zero delta must give nil, not a division by zero')

-- === Per-core CPU =========================================================
-- The aggregate line answers "how busy is the machine"; the equaliser needs
-- one entry per core. Both come from the same file, so they are parsed by the
-- same rules and differ only in which lines they read.
local percore = read('test/fixtures/proc_stat_percore')
local cores = data.per_cpu_times(percore)
assert(#cores == 4, 'one entry per cpuN line, got ' .. tostring(#cores))

-- cpu0: 6441+24+3225+1056496+1932+0+760 = 1068878 total, idle+iowait = 1058428
assert(cores[1].total == 1068878, 'core 0 total, got ' .. tostring(cores[1].total))
assert(cores[1].idle == 1058428, 'core 0 idle, got ' .. tostring(cores[1].idle))

-- Ordering matters: bar N must be core N, so the list follows the file.
assert(cores[2].total == 1066406, 'core 1 total, got ' .. tostring(cores[2].total))

-- The aggregate 'cpu ' line must NOT be counted as a core: it would draw a
-- seventeenth bar showing the average, which looks like a real core.
for i, c in ipairs(cores) do
  assert(c.total < 2000000, 'entry ' .. i .. ' looks like the aggregate line')
end

-- A counter per core is just another instance, which is why new_cpu_counter
-- holds its own previous sample rather than using a module-level one.
local c0 = data.new_cpu_counter()
assert(c0:sample(cores[1].total, cores[1].idle) == nil, 'first sample is nil')
local busy = c0:sample(cores[1].total + 100, cores[1].idle + 25)
assert(busy == 75.0, 'second sample 75%, got ' .. tostring(busy))

-- Malformed input yields an empty list, never an error: a raise here is a
-- blank dashboard.
assert(#data.per_cpu_times('') == 0, 'empty input gives an empty list')
assert(#data.per_cpu_times('garbage\nlines\n') == 0, 'garbage gives an empty list')
assert(#data.per_cpu_times(nil) == 0, 'nil gives an empty list')
-- A truncated line (fewer than the 5 fields the maths needs) is skipped
-- rather than producing a nonsense total.
assert(#data.per_cpu_times('cpu0 1 2\n') == 0, 'a short line is skipped')

print('test_data: all assertions passed')