use serde_json::json; use crate::i3::X11Test; use crate::util::Test; macro_rules! screenshot { // shorthand when mocks aren't needed ( $test_name:ident, $item_json:expr ) => { screenshot!($test_name, $item_json, { default: {} }); }; // batch case (many cases with bin/fake_root mocks) ( $test_name:ident, $item_json:expr $(, { $( $case_name:ident: { $(bins => {$($bname:literal: $bdata:expr$(,)?)*};)? $(files => {$($fname:literal: $fdata:expr$(,)?)*};)? $(setup_fn => $setup_fn:expr;)? $(test_fn => $test_fn:expr;)? }$(,)? )+ } )+ ) => { $( $( paste::paste! { screenshot!( [<$test_name _ $case_name>], $item_json, bins = { $($($bname => $bdata)*)? }, roots = { $($($fname => $fdata)*)? } $(, setup_fn => $setup_fn)? $(, test_fn => $test_fn)? ); } )* )? }; // single case ( $test_name:ident, $item_json:expr, bins = { $($bname:expr => $bdata:expr$(,)?)* }, roots = { $($fname:expr => $fdata:expr$(,)?)* } $(, setup_fn => $setup_fn:expr)? $(, test_fn => $test_fn:expr)? ) => { x_test!( $test_name, { // disable separator let mut obj = $item_json; let map = obj.as_object_mut().unwrap(); map.insert("separator".into(), false.into()); // insert item afterwards for some artificial padding // done this way because some nerd fonts clip if it's the last item json!({ "items": [obj, { "type": "raw", "full_text": "" }] }) }, |_test: &mut Test| { $($setup_fn(&_test);)? $( _test.add_bin($bname, $bdata); )* $( _test.add_fake_file($fname, $fdata); )* }, |x_test: &X11Test| { $($test_fn(&x_test);)? x_test.screenshot("bar-0"); } ); }; } // battery --------------------------------------------------------------------- screenshot! { battery, json!({ "type": "battery", "interval": "1s", "batteries": [ "/sys/class/power_supply/BAT0", "/sys/class/power_supply/BAT0" ], }), { at_100: { files => { "/sys/class/power_supply/BAT0/charge_now": "100", "/sys/class/power_supply/BAT0/charge_full": "100", "/sys/class/power_supply/BAT0/status": "Discharging", }; }, at_60: { files => { "/sys/class/power_supply/BAT0/charge_now": "60", "/sys/class/power_supply/BAT0/charge_full": "100", "/sys/class/power_supply/BAT0/status": "Discharging", }; }, at_40: { files => { "/sys/class/power_supply/BAT0/charge_now": "40", "/sys/class/power_supply/BAT0/charge_full": "100", "/sys/class/power_supply/BAT0/status": "Discharging", }; }, at_20: { files => { "/sys/class/power_supply/BAT0/charge_now": "20", "/sys/class/power_supply/BAT0/charge_full": "100", "/sys/class/power_supply/BAT0/status": "Discharging", }; }, at_5: { files => { "/sys/class/power_supply/BAT0/charge_now": "5", "/sys/class/power_supply/BAT0/charge_full": "100", "/sys/class/power_supply/BAT0/status": "Discharging", }; }, charging: { files => { "/sys/class/power_supply/BAT0/charge_now": "10", "/sys/class/power_supply/BAT0/charge_full": "100", "/sys/class/power_supply/BAT0/status": "Charging", }; } full: { files => { "/sys/class/power_supply/BAT0/charge_now": "100", "/sys/class/power_supply/BAT0/charge_full": "100", "/sys/class/power_supply/BAT0/status": "Full", }; } } } // cpu ------------------------------------------------------------------------- screenshot! { cpu, json!({ "type": "cpu", "interval": "1s", }), // /proc/stat's values are // cpu_id user nice system idle iowait irq softirq steal guest guest_nice // for sysinfo's calculations: // see: https://github.com/GuillaumeGomez/sysinfo/blob/2fa03b052e92f4d8ce90e57c548b1732f848dbbd/src/linux/cpu.rs { at_0: { files => { "/proc/stat": "cpu 0 0 0 0 0 0 0 0 0 0" }; }, at_50: { files => { "/proc/stat": "cpu 1 0 0 1 0 0 0 0 0 0" }; }, at_67: { files => { "/proc/stat": "cpu 2 0 0 1 0 0 0 0 0 0" }; }, at_100: { files => { "/proc/stat": "cpu 1 0 0 0 0 0 0 0 0 0" }; }, } } // disk ------------------------------------------------------------------------ // NOTE: this one is difficult to mock, since it first reads `/proc/mount` and then // proceeds to call `statvfs` to get filesystem information. screenshot! { disk, json!({ "type": "disk", "interval": "1s", }) } // dunst ----------------------------------------------------------------------- screenshot!( dunst, json!({ "type": "dunst" }), { off: { test_fn => |t: &X11Test| { t.cmd("i3-msg exec 'dunstctl set-paused false'") }; }, on: { test_fn => |t: &X11Test| { t.cmd("i3-msg exec 'dunstctl set-paused true'") }; } } ); // kbd ------------------------------------------------------------------------- screenshot! { kbd, json!({ "type": "kbd", "show": ["caps_lock", "num_lock", "scroll_lock"] }), { caps_on: { files => { "/sys/class/leds/input0::capslock/brightness": "1", "/sys/class/leds/input0::numlock/brightness": "0", "/sys/class/leds/input0::scrolllock/brightness": "0", }; }, num_on: { files => { "/sys/class/leds/input0::capslock/brightness": "0", "/sys/class/leds/input0::numlock/brightness": "1", "/sys/class/leds/input0::scrolllock/brightness": "0", }; }, all_on: { files => { "/sys/class/leds/input0::capslock/brightness": "1", "/sys/class/leds/input0::numlock/brightness": "1", "/sys/class/leds/input0::scrolllock/brightness": "1", }; }, all_off: { files => { "/sys/class/leds/input0::capslock/brightness": "0", "/sys/class/leds/input0::numlock/brightness": "0", "/sys/class/leds/input0::scrolllock/brightness": "0", }; }, one_err: { files => { "/sys/class/leds/input0::capslock/brightness": "1", "/sys/class/leds/input0::numlock/brightness": "0", }; } } } // krb ------------------------------------------------------------------------- screenshot!( krb, json!({ "type": "krb", "interval": "1s", }), { on: { bins => { "klist": "#!/usr/bin/env bash\nexit 0" }; }, off: { bins => { "klist": "#!/usr/bin/env bash\nexit 1" }; } } ); // mem ------------------------------------------------------------------------- fn mem(total: u64, available: u64) -> String { format!( r#"\ MemTotal: {total} kB MemFree: 0 kB MemAvailable: {available} kB Buffers: 0 kB Cached: 0 kB Shmem: 0 kB SReclaimable: 0 kB SwapTotal: {total} kB SwapFree: {total} kB"#, total = total, available = available ) } screenshot!( mem, json!({ "type": "mem", "interval": "1s", }), // for sysinfo calculations: // see: https://github.com/GuillaumeGomez/sysinfo/blob/2fa03b052e92f4d8ce90e57c548b1732f848dbbd/src/linux/system.rs#L257 { free_100: { files => { "/proc/meminfo": mem(31250000, 31250000) }; }, free_75: { files => { "/proc/meminfo": mem(31250000, 23437500) }; }, free_50: { files => { "/proc/meminfo": mem(31250000, 15625000) }; }, free_25: { files => { "/proc/meminfo": mem(31250000, 7812500) }; }, free_0: { files => { "/proc/meminfo": mem(31250000, 0) }; }, at_0: { files => { "/proc/meminfo": mem(31250000, 31250000) }; test_fn => |test: &X11Test| test.i3stat_ipc("click mem left"); }, at_25: { files => { "/proc/meminfo": mem(31250000, 23437500) }; test_fn => |test: &X11Test| test.i3stat_ipc("click mem left"); }, at_50: { files => { "/proc/meminfo": mem(31250000, 15625000) }; test_fn => |test: &X11Test| test.i3stat_ipc("click mem left"); }, at_75: { files => { "/proc/meminfo": mem(31250000, 7812500) }; test_fn => |test: &X11Test| test.i3stat_ipc("click mem left"); }, at_100: { files => { "/proc/meminfo": mem(31250000, 0) }; test_fn => |test: &X11Test| test.i3stat_ipc("click mem left"); } } ); // net_usage ------------------------------------------------------------------- screenshot!( net_usage, json!({ "type": "net_usage", "interval": "1s", "minimum": "1B", "display": "bits", // after click it turns to bytes for screenshot "thresholds": ["1kiB", "1MiB", "10MiB", "25MiB", "100MiB"], // this is the least hacky solution I can think of right now... "_always_assume_interval": true }), // Mocking these out is tricky, since it's a constant measure of bytes over time. // So we start at 0, add some bytes, and then click to check again. // https://github.com/GuillaumeGomez/sysinfo/blob/2fa03b052e92f4d8ce90e57c548b1732f848dbbd/src/linux/network.rs#L53 { no_traffic: { files => { "/sys/class/net/wlan0/statistics/rx_bytes": "0", "/sys/class/net/wlan0/statistics/tx_bytes": "0", }; }, threshold_0: { files => { "/sys/class/net/wlan1/statistics/rx_bytes": "0", "/sys/class/net/wlan1/statistics/tx_bytes": "0", }; test_fn => |t: &X11Test| { t.cmd("echo 1 > /sys/class/net/wlan1/statistics/rx_bytes"); t.cmd("echo 2 > /sys/class/net/wlan1/statistics/tx_bytes"); t.i3stat_ipc("click net_usage left"); }; }, threshold_1: { files => { "/sys/class/net/wlan1/statistics/rx_bytes": "0", "/sys/class/net/wlan1/statistics/tx_bytes": "0", }; test_fn => |t: &X11Test| { t.cmd("echo 2048 > /sys/class/net/wlan1/statistics/rx_bytes"); t.cmd("echo 4096 > /sys/class/net/wlan1/statistics/tx_bytes"); t.i3stat_ipc("click net_usage left"); }; }, threshold_2: { files => { "/sys/class/net/wlan1/statistics/rx_bytes": "0", "/sys/class/net/wlan1/statistics/tx_bytes": "0", }; test_fn => |t: &X11Test| { t.cmd("echo 4000000 > /sys/class/net/wlan1/statistics/rx_bytes"); t.cmd("echo 8000000 > /sys/class/net/wlan1/statistics/tx_bytes"); t.i3stat_ipc("click net_usage left"); }; }, threshold_3: { files => { "/sys/class/net/wlan1/statistics/rx_bytes": "0", "/sys/class/net/wlan1/statistics/tx_bytes": "0", }; test_fn => |t: &X11Test| { t.cmd("echo 14000000 > /sys/class/net/wlan1/statistics/rx_bytes"); t.cmd("echo 18000000 > /sys/class/net/wlan1/statistics/tx_bytes"); t.i3stat_ipc("click net_usage left"); }; }, threshold_4: { files => { "/sys/class/net/wlan1/statistics/rx_bytes": "0", "/sys/class/net/wlan1/statistics/tx_bytes": "0", }; test_fn => |t: &X11Test| { t.cmd("echo 31000000 > /sys/class/net/wlan1/statistics/rx_bytes"); t.cmd("echo 32000000 > /sys/class/net/wlan1/statistics/tx_bytes"); t.i3stat_ipc("click net_usage left"); }; }, threshold_max: { files => { "/sys/class/net/wlan1/statistics/rx_bytes": "0", "/sys/class/net/wlan1/statistics/tx_bytes": "0", }; test_fn => |t: &X11Test| { t.cmd("echo 420000000 > /sys/class/net/wlan1/statistics/rx_bytes"); t.cmd("echo 430000000 > /sys/class/net/wlan1/statistics/tx_bytes"); t.i3stat_ipc("click net_usage left"); }; }, } ); // nic ------------------------------------------------------------------------- // NOTE: this is difficult to mock, since it uses `getifaddrs` for interface information // and then also uses `iwlib` to read WiFi information screenshot!(nic, json!({ "type": "nic" })); // pulse ----------------------------------------------------------------------- // NOTE: I'm not about to spin up a pulse server just for a test... screenshot!(pulse, json!({ "type": "pulse" })); // raw ------------------------------------------------------------------------- screenshot!( raw, json!({ "type": "raw", "full_text": "Hello, World!", "background": "#00ff00", "color": "#ff00ff", "border": "#ff00ff" }) ); // script ---------------------------------------------------------------------- screenshot!( script, json!({ "type": "script", "command": "echo -n hello", "output": "simple", }) ); // sensors --------------------------------------------------------------------- screenshot!( sensors, json!({ "type": "sensors", "interval": "1s", "component": "name temp1" }), { at_0: { files => { "/sys/class/hwmon/hwmon1/name": "name", "/sys/class/hwmon/hwmon1/temp1_input": "0", }; }, at_50: { files => { "/sys/class/hwmon/hwmon1/name": "name", "/sys/class/hwmon/hwmon1/temp1_input": "50000", }; }, at_70: { files => { "/sys/class/hwmon/hwmon1/name": "name", "/sys/class/hwmon/hwmon1/temp1_input": "70000", }; }, at_80: { files => { "/sys/class/hwmon/hwmon1/name": "name", "/sys/class/hwmon/hwmon1/temp1_input": "80000", }; }, at_100: { files => { "/sys/class/hwmon/hwmon1/name": "name", "/sys/class/hwmon/hwmon1/temp1_input": "100000", }; } } ); // time ------------------------------------------------------------------------ screenshot!( time, json!({ "type": "time", "interval": "1 s", "format_long": "%Y-%m-%d %H:%M:%S", "format_short": "%H:%M" }) );