mirror of
https://gitlab.steamos.cloud/holo/steamos-manager.git
synced 2025-07-14 02:11:54 -04:00
daemon: Add config loading
This commit is contained in:
parent
1a69cce50b
commit
2f784f9741
3 changed files with 545 additions and 4 deletions
38
Cargo.lock
generated
38
Cargo.lock
generated
|
@ -283,6 +283,20 @@ dependencies = [
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "config"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7328b20597b53c2454f0b1919720c25c7339051c02b72b7e05409e00b14132be"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"lazy_static",
|
||||||
|
"nom",
|
||||||
|
"pathdiff",
|
||||||
|
"serde",
|
||||||
|
"toml",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.12"
|
version = "0.2.12"
|
||||||
|
@ -591,6 +605,12 @@ dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minimal-lexical"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.7.2"
|
version = "0.7.2"
|
||||||
|
@ -624,6 +644,16 @@ dependencies = [
|
||||||
"memoffset",
|
"memoffset",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "7.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"minimal-lexical",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num_cpus"
|
name = "num_cpus"
|
||||||
version = "1.16.0"
|
version = "1.16.0"
|
||||||
|
@ -665,6 +695,12 @@ version = "2.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
|
checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pathdiff"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-lite"
|
name = "pin-project-lite"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
|
@ -894,7 +930,9 @@ name = "steamos-manager"
|
||||||
version = "24.5.1"
|
version = "24.5.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"async-trait",
|
||||||
"clap",
|
"clap",
|
||||||
|
"config",
|
||||||
"inotify",
|
"inotify",
|
||||||
"itertools",
|
"itertools",
|
||||||
"libc",
|
"libc",
|
||||||
|
|
|
@ -12,7 +12,9 @@ strip="symbols"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
|
async-trait = "0.1"
|
||||||
clap = { version = "4.5", default-features = false, features = ["derive", "help", "std", "usage"] }
|
clap = { version = "4.5", default-features = false, features = ["derive", "help", "std", "usage"] }
|
||||||
|
config = { version = "0.14", default-features = false, features = ["async", "toml"] }
|
||||||
inotify = { version = "0.10", default-features = false, features = ["stream"] }
|
inotify = { version = "0.10", default-features = false, features = ["stream"] }
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
itertools = "0.12"
|
itertools = "0.12"
|
||||||
|
|
|
@ -6,12 +6,53 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use config::builder::AsyncState;
|
||||||
|
use config::{AsyncSource, ConfigBuilder, ConfigError, FileFormat, Format, Map, Value};
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
use std::fmt::Debug;
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
use tokio::fs::{create_dir_all, read_to_string, write};
|
use std::path::Path;
|
||||||
use tracing::{error, info};
|
use tokio::fs::{create_dir_all, read_dir, read_to_string, write};
|
||||||
|
use tracing::{debug, error, info};
|
||||||
|
|
||||||
use crate::daemon::DaemonContext;
|
use crate::daemon::DaemonContext;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct AsyncFileSource<F: Format, P: AsRef<Path> + Sized + Send + Sync> {
|
||||||
|
path: P,
|
||||||
|
format: F,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F: Format, P: AsRef<Path> + Sized + Send + Sync + Debug> AsyncFileSource<F, P> {
|
||||||
|
fn from(path: P, format: F) -> AsyncFileSource<F, P> {
|
||||||
|
AsyncFileSource { path, format }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<F: Format + Send + Sync + Debug, P: AsRef<Path> + Sized + Send + Sync + Debug> AsyncSource
|
||||||
|
for AsyncFileSource<F, P>
|
||||||
|
{
|
||||||
|
async fn collect(&self) -> Result<Map<String, Value>, ConfigError> {
|
||||||
|
let path = self.path.as_ref();
|
||||||
|
let text = match read_to_string(&path).await {
|
||||||
|
Ok(text) => text,
|
||||||
|
Err(e) => {
|
||||||
|
if e.kind() == ErrorKind::NotFound {
|
||||||
|
info!("No config file {} found", path.to_string_lossy());
|
||||||
|
return Ok(Map::new());
|
||||||
|
}
|
||||||
|
return Err(ConfigError::Foreign(Box::new(e)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let path = path.to_string_lossy().to_string();
|
||||||
|
self.format
|
||||||
|
.parse(Some(&path), &text)
|
||||||
|
.map_err(ConfigError::Foreign)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(in crate::daemon) async fn read_state<C: DaemonContext>(context: &C) -> Result<C::State> {
|
pub(in crate::daemon) async fn read_state<C: DaemonContext>(context: &C) -> Result<C::State> {
|
||||||
let path = context.state_path()?;
|
let path = context.state_path()?;
|
||||||
let state = match read_to_string(path).await {
|
let state = match read_to_string(path).await {
|
||||||
|
@ -39,6 +80,466 @@ pub(in crate::daemon) async fn write_state<C: DaemonContext>(context: &C) -> Res
|
||||||
Ok(write(path, state.as_bytes()).await?)
|
Ok(write(path, state.as_bytes()).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(in crate::daemon) async fn read_config<C: DaemonContext>(_context: &C) -> Result<C::Config> {
|
pub(in crate::daemon) async fn read_config<C: DaemonContext>(context: &C) -> Result<C::Config> {
|
||||||
todo!();
|
let builder = ConfigBuilder::<AsyncState>::default();
|
||||||
|
let system_config_path = context.system_config_path()?;
|
||||||
|
let user_config_path = context.user_config_path()?;
|
||||||
|
|
||||||
|
let builder = builder.add_async_source(AsyncFileSource::from(
|
||||||
|
system_config_path.join("config.toml"),
|
||||||
|
FileFormat::Toml,
|
||||||
|
));
|
||||||
|
let builder = read_config_directory(builder, system_config_path.join("config.toml.d")).await?;
|
||||||
|
|
||||||
|
let builder = builder.add_async_source(AsyncFileSource::from(
|
||||||
|
user_config_path.join("config.toml"),
|
||||||
|
FileFormat::Toml,
|
||||||
|
));
|
||||||
|
let builder = read_config_directory(builder, user_config_path.join("config.toml.d")).await?;
|
||||||
|
let config = builder.build().await?;
|
||||||
|
Ok(config.try_deserialize()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_config_directory<P: AsRef<Path> + Sync + Send>(
|
||||||
|
builder: ConfigBuilder<AsyncState>,
|
||||||
|
path: P,
|
||||||
|
) -> Result<ConfigBuilder<AsyncState>> {
|
||||||
|
let mut dir = match read_dir(&path).await {
|
||||||
|
Ok(dir) => dir,
|
||||||
|
Err(e) => {
|
||||||
|
if e.kind() == ErrorKind::NotFound {
|
||||||
|
debug!(
|
||||||
|
"No config fragment directory {} found",
|
||||||
|
path.as_ref().to_string_lossy()
|
||||||
|
);
|
||||||
|
return Ok(builder);
|
||||||
|
}
|
||||||
|
error!("Error reading config fragment directory: {e}");
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut entries = Vec::new();
|
||||||
|
while let Some(entry) = dir.next_entry().await? {
|
||||||
|
let path = entry.path();
|
||||||
|
match path.extension() {
|
||||||
|
Some(ext) if ext == OsStr::new("toml") => entries.push(path),
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entries.sort();
|
||||||
|
Ok(entries.into_iter().fold(builder, |builder, path| {
|
||||||
|
builder.add_async_source(AsyncFileSource::from(path, FileFormat::Toml))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use crate::daemon::Daemon;
|
||||||
|
use crate::{path, testing, write_synced};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Copy, Clone, Default, PartialEq, Debug)]
|
||||||
|
struct TestSubstate {
|
||||||
|
subvalue: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Copy, Clone, Default, PartialEq, Debug)]
|
||||||
|
#[serde(default)]
|
||||||
|
struct TestState {
|
||||||
|
substate: TestSubstate,
|
||||||
|
value: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct TestContext {
|
||||||
|
state: TestState,
|
||||||
|
config: TestState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DaemonContext for TestContext {
|
||||||
|
type State = TestState;
|
||||||
|
type Config = TestState;
|
||||||
|
type Command = ();
|
||||||
|
|
||||||
|
fn user_config_path(&self) -> Result<PathBuf> {
|
||||||
|
Ok(path("user"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn system_config_path(&self) -> Result<PathBuf> {
|
||||||
|
Ok(path("system"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn state(&self) -> TestState {
|
||||||
|
self.state
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn start(
|
||||||
|
&mut self,
|
||||||
|
state: Self::State,
|
||||||
|
config: Self::Config,
|
||||||
|
_daemon: &mut Daemon<Self>,
|
||||||
|
) -> Result<()> {
|
||||||
|
self.state = state;
|
||||||
|
self.config = config;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn reload(&mut self, config: Self::Config, _daemon: &mut Daemon<Self>) -> Result<()> {
|
||||||
|
self.config = config;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_command(
|
||||||
|
&mut self,
|
||||||
|
_cmd: Self::Command,
|
||||||
|
_daemon: &mut Daemon<Self>,
|
||||||
|
) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_read_state() {
|
||||||
|
let _h = testing::start();
|
||||||
|
|
||||||
|
let context = TestContext::default();
|
||||||
|
let state = read_state(&context).await.expect("read_state");
|
||||||
|
|
||||||
|
assert_eq!(state, TestState::default());
|
||||||
|
|
||||||
|
let state_path = context.state_path().expect("state_path");
|
||||||
|
create_dir_all(state_path.parent().unwrap())
|
||||||
|
.await
|
||||||
|
.expect("create_dir_all");
|
||||||
|
|
||||||
|
write_synced(
|
||||||
|
state_path,
|
||||||
|
"value = 1\n\n[substate]\nsubvalue = 2\n".as_bytes(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("write");
|
||||||
|
|
||||||
|
let state = read_state(&context).await.expect("read_state");
|
||||||
|
assert_eq!(
|
||||||
|
state,
|
||||||
|
TestState {
|
||||||
|
value: 1,
|
||||||
|
substate: TestSubstate { subvalue: 2 }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_read_extra_state() {
|
||||||
|
let _h = testing::start();
|
||||||
|
|
||||||
|
let context = TestContext::default();
|
||||||
|
let state_path = context.state_path().expect("state_path");
|
||||||
|
create_dir_all(state_path.parent().unwrap())
|
||||||
|
.await
|
||||||
|
.expect("create_dir_all");
|
||||||
|
|
||||||
|
write_synced(
|
||||||
|
state_path,
|
||||||
|
"value = 1\nvalue2 = 3\n\n[substate]\nsubvalue = 2\n".as_bytes(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("write");
|
||||||
|
|
||||||
|
let state = read_state(&context).await.expect("read_state");
|
||||||
|
assert_eq!(
|
||||||
|
state,
|
||||||
|
TestState {
|
||||||
|
value: 1,
|
||||||
|
substate: TestSubstate { subvalue: 2 }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_read_missing_state() {
|
||||||
|
let _h = testing::start();
|
||||||
|
|
||||||
|
let context = TestContext::default();
|
||||||
|
let state_path = context.state_path().expect("state_path");
|
||||||
|
create_dir_all(state_path.parent().unwrap())
|
||||||
|
.await
|
||||||
|
.expect("create_dir_all");
|
||||||
|
|
||||||
|
write_synced(state_path, "[substate]\nsubvalue = 2\n".as_bytes())
|
||||||
|
.await
|
||||||
|
.expect("write");
|
||||||
|
|
||||||
|
let state = read_state(&context).await.expect("read_state");
|
||||||
|
assert_eq!(
|
||||||
|
state,
|
||||||
|
TestState {
|
||||||
|
value: 0,
|
||||||
|
substate: TestSubstate { subvalue: 2 }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_write_state() {
|
||||||
|
let _h = testing::start();
|
||||||
|
|
||||||
|
let mut context = TestContext::default();
|
||||||
|
let state_path = context.state_path().expect("state_path");
|
||||||
|
|
||||||
|
write_state(&context).await.expect("write_state");
|
||||||
|
let config = read_to_string(&state_path).await.expect("read_to_string");
|
||||||
|
assert_eq!(config, "value = 0\n\n[substate]\nsubvalue = 0\n");
|
||||||
|
|
||||||
|
context.state.value = 1;
|
||||||
|
write_state(&context).await.expect("write_state");
|
||||||
|
let config = read_to_string(&state_path).await.expect("read_to_string");
|
||||||
|
assert_eq!(config, "value = 1\n\n[substate]\nsubvalue = 0\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_read_system_config() {
|
||||||
|
let _h = testing::start();
|
||||||
|
|
||||||
|
let context = TestContext::default();
|
||||||
|
let config = read_config(&context).await.expect("read_config");
|
||||||
|
assert_eq!(config, TestState::default());
|
||||||
|
|
||||||
|
let system_config_path = context.system_config_path().expect("system_config_path");
|
||||||
|
create_dir_all(&system_config_path)
|
||||||
|
.await
|
||||||
|
.expect("create_dir_all");
|
||||||
|
|
||||||
|
let config = read_config(&context).await.expect("read_config");
|
||||||
|
assert_eq!(config, TestState::default());
|
||||||
|
|
||||||
|
write_synced(
|
||||||
|
system_config_path.join("config.toml"),
|
||||||
|
"value = 1\n\n[substate]\nsubvalue = 2\n".as_bytes(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("write");
|
||||||
|
|
||||||
|
let config = read_config(&context).await.expect("read_config");
|
||||||
|
assert_eq!(
|
||||||
|
config,
|
||||||
|
TestState {
|
||||||
|
value: 1,
|
||||||
|
substate: TestSubstate { subvalue: 2 }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_read_user_config() {
|
||||||
|
let _h = testing::start();
|
||||||
|
|
||||||
|
let context = TestContext::default();
|
||||||
|
let config = read_config(&context).await.expect("read_config");
|
||||||
|
assert_eq!(config, TestState::default());
|
||||||
|
|
||||||
|
let user_config_path = context.user_config_path().expect("user_config_path");
|
||||||
|
create_dir_all(&user_config_path)
|
||||||
|
.await
|
||||||
|
.expect("create_dir_all");
|
||||||
|
|
||||||
|
let config = read_config(&context).await.expect("read_config");
|
||||||
|
assert_eq!(config, TestState::default());
|
||||||
|
|
||||||
|
write_synced(
|
||||||
|
user_config_path.join("config.toml"),
|
||||||
|
"value = 1\n\n[substate]\nsubvalue = 2\n".as_bytes(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("write");
|
||||||
|
|
||||||
|
let config = read_config(&context).await.expect("read_config");
|
||||||
|
assert_eq!(
|
||||||
|
config,
|
||||||
|
TestState {
|
||||||
|
value: 1,
|
||||||
|
substate: TestSubstate { subvalue: 2 }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_config_ordering() {
|
||||||
|
let _h = testing::start();
|
||||||
|
|
||||||
|
let context = TestContext::default();
|
||||||
|
|
||||||
|
let system_config_path = context.user_config_path().expect("system_config_path");
|
||||||
|
create_dir_all(&system_config_path)
|
||||||
|
.await
|
||||||
|
.expect("create_dir_all");
|
||||||
|
|
||||||
|
let user_config_path = context.user_config_path().expect("user_config_path");
|
||||||
|
create_dir_all(&user_config_path)
|
||||||
|
.await
|
||||||
|
.expect("create_dir_all");
|
||||||
|
|
||||||
|
write_synced(
|
||||||
|
system_config_path.join("config.toml"),
|
||||||
|
"value = 1\n\n[substate]\nsubvalue = 2\n".as_bytes(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("write");
|
||||||
|
|
||||||
|
write_synced(
|
||||||
|
user_config_path.join("config.toml"),
|
||||||
|
"value = 3\n\n[substate]\nsubvalue = 4\n".as_bytes(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("write");
|
||||||
|
|
||||||
|
let config = read_config(&context).await.expect("read_config");
|
||||||
|
assert_eq!(
|
||||||
|
config,
|
||||||
|
TestState {
|
||||||
|
value: 3,
|
||||||
|
substate: TestSubstate { subvalue: 4 }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_config_partial_ordering() {
|
||||||
|
let _h = testing::start();
|
||||||
|
|
||||||
|
let context = TestContext::default();
|
||||||
|
|
||||||
|
let system_config_path = context.system_config_path().expect("system_config_path");
|
||||||
|
create_dir_all(&system_config_path)
|
||||||
|
.await
|
||||||
|
.expect("create_dir_all");
|
||||||
|
|
||||||
|
let user_config_path = context.user_config_path().expect("user_config_path");
|
||||||
|
create_dir_all(&user_config_path)
|
||||||
|
.await
|
||||||
|
.expect("create_dir_all");
|
||||||
|
|
||||||
|
write_synced(
|
||||||
|
system_config_path.join("config.toml"),
|
||||||
|
"value = 1\n\n[substate]\nsubvalue = 2\n".as_bytes(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("write");
|
||||||
|
|
||||||
|
let config = read_config(&context).await.expect("read_config");
|
||||||
|
assert_eq!(
|
||||||
|
config,
|
||||||
|
TestState {
|
||||||
|
value: 1,
|
||||||
|
substate: TestSubstate { subvalue: 2 }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
write_synced(
|
||||||
|
user_config_path.join("config.toml"),
|
||||||
|
"value = 3\n".as_bytes(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("write");
|
||||||
|
|
||||||
|
let config = read_config(&context).await.expect("read_config");
|
||||||
|
assert_eq!(
|
||||||
|
config,
|
||||||
|
TestState {
|
||||||
|
value: 3,
|
||||||
|
substate: TestSubstate { subvalue: 2 }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_read_user_config_fragments() {
|
||||||
|
let _h = testing::start();
|
||||||
|
|
||||||
|
let context = TestContext::default();
|
||||||
|
|
||||||
|
let user_config_path = context.user_config_path().expect("user_config_path");
|
||||||
|
create_dir_all(user_config_path.join("config.toml.d"))
|
||||||
|
.await
|
||||||
|
.expect("create_dir_all");
|
||||||
|
|
||||||
|
write_synced(
|
||||||
|
user_config_path.join("config.toml"),
|
||||||
|
"value = 1\n\n[substate]\nsubvalue = 2\n".as_bytes(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("write");
|
||||||
|
|
||||||
|
let config = read_config(&context).await.expect("read_config");
|
||||||
|
assert_eq!(
|
||||||
|
config,
|
||||||
|
TestState {
|
||||||
|
value: 1,
|
||||||
|
substate: TestSubstate { subvalue: 2 }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
write_synced(
|
||||||
|
user_config_path.join("config.toml.d/frag.toml"),
|
||||||
|
"[substate]\nsubvalue = 3\n".as_bytes(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("write");
|
||||||
|
|
||||||
|
let config = read_config(&context).await.expect("read_config");
|
||||||
|
assert_eq!(
|
||||||
|
config,
|
||||||
|
TestState {
|
||||||
|
value: 1,
|
||||||
|
substate: TestSubstate { subvalue: 3 }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_read_system_config_fragments() {
|
||||||
|
let _h = testing::start();
|
||||||
|
|
||||||
|
let context = TestContext::default();
|
||||||
|
|
||||||
|
let system_config_path = context.system_config_path().expect("system_config_path");
|
||||||
|
create_dir_all(system_config_path.join("config.toml.d"))
|
||||||
|
.await
|
||||||
|
.expect("create_dir_all");
|
||||||
|
|
||||||
|
write_synced(
|
||||||
|
system_config_path.join("config.toml"),
|
||||||
|
"value = 1\n\n[substate]\nsubvalue = 2\n".as_bytes(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("write");
|
||||||
|
|
||||||
|
let config = read_config(&context).await.expect("read_config");
|
||||||
|
assert_eq!(
|
||||||
|
config,
|
||||||
|
TestState {
|
||||||
|
value: 1,
|
||||||
|
substate: TestSubstate { subvalue: 2 }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
write_synced(
|
||||||
|
system_config_path.join("config.toml.d/frag.toml"),
|
||||||
|
"[substate]\nsubvalue = 3\n".as_bytes(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("write");
|
||||||
|
|
||||||
|
let config = read_config(&context).await.expect("read_config");
|
||||||
|
assert_eq!(
|
||||||
|
config,
|
||||||
|
TestState {
|
||||||
|
value: 1,
|
||||||
|
substate: TestSubstate { subvalue: 3 }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue