Add some config unit tests

This commit is contained in:
Ottatop 2024-02-17 00:00:25 -06:00
parent 8a2a6a3185
commit cea9d9048a
6 changed files with 260 additions and 19 deletions

45
Cargo.lock generated
View file

@ -1199,6 +1199,16 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0b5399f6804fbab912acbd8878ed3532d506b7c951b8f9f164ef90fef39e3f4"
[[package]]
name = "lock_api"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.20"
@ -1449,6 +1459,29 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "parking_lot"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.4.1",
"smallvec",
"windows-targets 0.48.5",
]
[[package]]
name = "percent-encoding"
version = "2.3.1"
@ -1505,7 +1538,6 @@ dependencies = [
"bitflags 2.4.2",
"clap",
"image",
"lazy_static",
"nix",
"pinnacle-api-defs",
"prost",
@ -1515,6 +1547,8 @@ dependencies = [
"smithay",
"smithay-drm-extras",
"sysinfo",
"temp-env",
"tempfile",
"thiserror",
"tokio",
"tokio-stream",
@ -2084,6 +2118,15 @@ dependencies = [
"windows",
]
[[package]]
name = "temp-env"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96374855068f47402c3121c6eed88d29cb1de8f3ab27090e273e420bdabcf050"
dependencies = [
"parking_lot",
]
[[package]]
name = "tempfile"
version = "3.10.0"

View file

@ -26,7 +26,6 @@ anyhow = { version = "1.0.79", features = ["backtrace"] }
clap = { version = "4.4.18", features = ["derive"] }
xkbcommon = "0.7.0"
xdg = "2.5.2"
lazy_static = "1.4.0"
sysinfo = "0.30.5"
nix = { version = "0.27.1", features = ["user", "resource"] }
prost = "0.12.3"
@ -41,6 +40,10 @@ pinnacle-api-defs = { path = "./pinnacle-api-defs" }
[build-dependencies]
xdg = "2.5.2"
[dev-dependencies]
temp-env = "0.3.6"
tempfile = "3.10.0"
[features]
default = ["egl", "winit", "udev", "xwayland"]
egl = ["smithay/use_system_lib", "smithay/backend_egl"]

View file

@ -31,6 +31,7 @@ use sysinfo::ProcessRefreshKind;
use tokio::{sync::mpsc::UnboundedSender, task::JoinHandle};
use toml::Table;
use xdg::BaseDirectories;
use xkbcommon::xkb::Keysym;
use crate::{
@ -42,7 +43,7 @@ const DEFAULT_SOCKET_DIR: &str = "/tmp";
/// The metaconfig struct containing what to run, what envs to run it with, various keybinds, and
/// the target socket directory.
#[derive(serde::Deserialize, Debug)]
#[derive(serde::Deserialize, Debug, PartialEq)]
pub struct Metaconfig {
pub command: Vec<String>,
pub envs: Option<Table>,
@ -51,13 +52,13 @@ pub struct Metaconfig {
pub socket_dir: Option<String>,
}
#[derive(serde::Deserialize, Debug)]
#[derive(serde::Deserialize, Debug, PartialEq)]
pub struct Keybind {
modifiers: Vec<Modifier>,
key: Key,
}
#[derive(serde::Deserialize, Debug, Clone, Copy)]
#[derive(serde::Deserialize, Debug, Clone, Copy, PartialEq)]
enum Modifier {
Shift,
Ctrl,
@ -84,7 +85,7 @@ impl From<Vec<self::Modifier>> for ModifierMask {
}
// TODO: accept xkbcommon names instead
#[derive(serde::Deserialize, Debug, Clone, Copy)]
#[derive(serde::Deserialize, Debug, Clone, Copy, PartialEq)]
#[serde(rename_all = "snake_case")]
#[repr(u32)]
pub enum Key {
@ -207,12 +208,12 @@ fn parse_metaconfig(config_dir: &Path) -> anyhow::Result<Metaconfig> {
/// Get the config dir. This is $PINNACLE_CONFIG_DIR, then $XDG_CONFIG_HOME/pinnacle,
/// then ~/.config/pinnacle.
pub fn get_config_dir() -> PathBuf {
pub fn get_config_dir(xdg_base_dirs: &BaseDirectories) -> PathBuf {
let config_dir = std::env::var("PINNACLE_CONFIG_DIR")
.ok()
.and_then(|s| Some(PathBuf::from(shellexpand::full(&s).ok()?.to_string())));
config_dir.unwrap_or(crate::XDG_BASE_DIRS.get_config_home())
config_dir.unwrap_or(xdg_base_dirs.get_config_home())
}
impl State {
@ -224,7 +225,7 @@ impl State {
tracing::info!("Starting config at {}", config_dir.display());
let default_lua_config_dir = crate::XDG_BASE_DIRS.get_data_file("default_config");
let default_lua_config_dir = self.xdg_base_dirs.get_data_file("default_config");
let load_default_config = |state: &mut State, reason: &str| {
tracing::error!(
@ -281,7 +282,7 @@ impl State {
socket_dir
} else {
// Otherwise, use $XDG_RUNTIME_DIR. If that doesn't exist, use /tmp.
crate::XDG_BASE_DIRS
self.xdg_base_dirs
.get_runtime_directory()
.cloned()
.unwrap_or(PathBuf::from(DEFAULT_SOCKET_DIR))
@ -427,7 +428,7 @@ impl State {
std::env::set_var(
"PINNACLE_PROTO_DIR",
crate::XDG_BASE_DIRS.get_data_file("protobuf"),
self.xdg_base_dirs.get_data_file("protobuf"),
);
let (grpc_sender, grpc_receiver) =
@ -503,3 +504,193 @@ impl State {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::env::var;
#[test]
fn config_dir_with_relative_env_works() -> anyhow::Result<()> {
let relative_path = "api/rust/examples/default_config";
temp_env::with_var("PINNACLE_CONFIG_DIR", Some(relative_path), || {
let xdg_base_dirs = BaseDirectories::with_prefix("pinnacle")?;
// Prepending the relative path with the current dir *shouldn't* be necessary, me thinks
let expected = PathBuf::from(relative_path);
assert_eq!(get_config_dir(&xdg_base_dirs), expected);
Ok(())
})
}
#[test]
fn config_dir_with_tilde_env_works() -> anyhow::Result<()> {
temp_env::with_var("PINNACLE_CONFIG_DIR", Some("~/some/dir/somewhere/"), || {
let xdg_base_dirs = BaseDirectories::with_prefix("pinnacle")?;
let expected = PathBuf::from(var("HOME")?).join("some/dir/somewhere");
assert_eq!(get_config_dir(&xdg_base_dirs), expected);
Ok(())
})
}
#[test]
fn config_dir_with_absolute_env_works() -> anyhow::Result<()> {
let absolute_path = "/its/morbin/time";
temp_env::with_var("PINNACLE_CONFIG_DIR", Some(absolute_path), || {
let xdg_base_dirs = BaseDirectories::with_prefix("pinnacle")?;
let expected = PathBuf::from(absolute_path);
assert_eq!(get_config_dir(&xdg_base_dirs), expected);
Ok(())
})
}
#[test]
fn config_dir_without_env_and_with_xdg_works() -> anyhow::Result<()> {
let xdg_config_home = "/some/different/xdg/config/path";
temp_env::with_vars(
[
("PINNACLE_CONFIG_DIR", None),
("XDG_CONFIG_HOME", Some(xdg_config_home)),
],
|| {
let xdg_base_dirs = BaseDirectories::with_prefix("pinnacle")?;
let expected = PathBuf::from(xdg_config_home).join("pinnacle");
assert_eq!(get_config_dir(&xdg_base_dirs), expected);
Ok(())
},
)
}
#[test]
fn config_dir_without_env_and_without_xdg_works() -> anyhow::Result<()> {
temp_env::with_vars(
[
("PINNACLE_CONFIG_DIR", None::<&str>),
("XDG_CONFIG_HOME", None),
],
|| {
let xdg_base_dirs = BaseDirectories::with_prefix("pinnacle")?;
let expected = PathBuf::from(var("HOME")?).join(".config/pinnacle");
assert_eq!(get_config_dir(&xdg_base_dirs), expected);
Ok(())
},
)
}
#[test]
fn full_metaconfig_successfully_parses() -> anyhow::Result<()> {
let metaconfig_text = r#"
command = ["lua", "init.lua"]
reload_keybind = { modifiers = ["Ctrl", "Alt"], key = "r" }
kill_keybind = { modifiers = ["Ctrl", "Alt", "Shift"], key = "escape" }
socket_dir = "/path/to/socket/dir"
[envs]
MARCO = "polo"
SUN = "chips"
"#;
let metaconfig_dir = tempfile::tempdir()?;
std::fs::write(
metaconfig_dir.path().join("metaconfig.toml"),
metaconfig_text,
)?;
let expected_metaconfig = Metaconfig {
command: vec!["lua".to_string(), "init.lua".to_string()],
envs: Some(toml::Table::from_iter([
("MARCO".to_string(), toml::Value::String("polo".to_string())),
("SUN".to_string(), toml::Value::String("chips".to_string())),
])),
reload_keybind: Keybind {
modifiers: vec![Modifier::Ctrl, Modifier::Alt],
key: Key::R,
},
kill_keybind: Keybind {
modifiers: vec![Modifier::Ctrl, Modifier::Alt, Modifier::Shift],
key: Key::Escape,
},
socket_dir: Some("/path/to/socket/dir".to_string()),
};
assert_eq!(
parse_metaconfig(metaconfig_dir.path())?,
expected_metaconfig
);
Ok(())
}
#[test]
fn minimal_metaconfig_successfully_parses() -> anyhow::Result<()> {
let metaconfig_text = r#"
command = ["lua", "init.lua"]
reload_keybind = { modifiers = ["Ctrl", "Alt"], key = "r" }
kill_keybind = { modifiers = ["Ctrl", "Alt", "Shift"], key = "escape" }
"#;
let metaconfig_dir = tempfile::tempdir()?;
std::fs::write(
metaconfig_dir.path().join("metaconfig.toml"),
metaconfig_text,
)?;
let expected_metaconfig = Metaconfig {
command: vec!["lua".to_string(), "init.lua".to_string()],
envs: None,
reload_keybind: Keybind {
modifiers: vec![Modifier::Ctrl, Modifier::Alt],
key: Key::R,
},
kill_keybind: Keybind {
modifiers: vec![Modifier::Ctrl, Modifier::Alt, Modifier::Shift],
key: Key::Escape,
},
socket_dir: None,
};
assert_eq!(
parse_metaconfig(metaconfig_dir.path())?,
expected_metaconfig
);
Ok(())
}
#[test]
fn incorrect_metaconfig_does_not_parse() -> anyhow::Result<()> {
let metaconfig_text = r#"
command = "lua" # not an array
reload_keybind = { modifiers = ["Ctrl", "Alt"], key = "r" }
# Missing `kill_keybind`
# kill_keybind = { modifiers = ["Ctrl", "Alt", "Shift"], key = "escape" }
"#;
let metaconfig_dir = tempfile::tempdir()?;
std::fs::write(
metaconfig_dir.path().join("metaconfig.toml"),
metaconfig_text,
)?;
assert!(parse_metaconfig(metaconfig_dir.path()).is_err());
Ok(())
}
}

View file

@ -292,7 +292,7 @@ impl State {
self.shutdown();
}
Some(KeyAction::ReloadConfig) => {
self.start_config(crate::config::get_config_dir())
self.start_config(crate::config::get_config_dir(&self.xdg_base_dirs))
.expect("failed to restart config");
}
None => (),

View file

@ -32,11 +32,6 @@ mod state;
mod tag;
mod window;
lazy_static::lazy_static! {
pub static ref XDG_BASE_DIRS: BaseDirectories =
BaseDirectories::with_prefix("pinnacle").expect("couldn't create xdg BaseDirectories");
}
#[derive(clap::Args, Debug)]
#[group(id = "backend", required = false, multiple = false)]
struct Backends {
@ -63,7 +58,7 @@ struct Args {
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let xdg_state_dir = XDG_BASE_DIRS.get_state_home();
let xdg_state_dir = BaseDirectories::with_prefix("pinnacle")?.get_state_home();
let appender = tracing_appender::rolling::Builder::new()
.rotation(Rotation::HOURLY)

View file

@ -4,6 +4,7 @@ use crate::{
backend::Backend, config::Config, cursor::Cursor, focus::FocusState,
grab::resize_grab::ResizeSurfaceState, window::WindowElement,
};
use anyhow::Context;
use smithay::{
desktop::{PopupManager, Space},
input::{keyboard::XkbConfig, pointer::CursorImageStatus, Seat, SeatState},
@ -32,6 +33,7 @@ use smithay::{
};
use std::{cell::RefCell, sync::Arc, time::Duration};
use sysinfo::{ProcessRefreshKind, RefreshKind};
use xdg::BaseDirectories;
use crate::input::InputState;
@ -91,6 +93,8 @@ pub struct State {
// Currently only used to keep track of if the server has started
pub grpc_server_join_handle: Option<tokio::task::JoinHandle<()>>,
pub xdg_base_dirs: BaseDirectories,
}
impl State {
@ -150,7 +154,9 @@ impl State {
)?;
loop_handle.insert_idle(|state| {
if let Err(err) = state.start_config(crate::config::get_config_dir()) {
if let Err(err) =
state.start_config(crate::config::get_config_dir(&state.xdg_base_dirs))
{
panic!("failed to start config: {err}");
}
});
@ -264,6 +270,9 @@ impl State {
),
grpc_server_join_handle: None,
xdg_base_dirs: BaseDirectories::with_prefix("pinnacle")
.context("couldn't create xdg BaseDirectories")?,
};
Ok(state)