Improve the config parsing logic and test cases

This commit is contained in:
Federico Terzi 2021-03-07 15:53:02 +01:00
parent 7b9e43ab06
commit 0ca740914f
7 changed files with 565 additions and 370 deletions

View File

@ -1,16 +1,11 @@
use std::collections::HashSet; use std::collections::HashSet;
use anyhow::Result;
mod yaml;
mod path; mod path;
mod parse;
mod util; mod util;
mod resolve;
pub struct Config { pub trait Config {
pub label: Option<String>, fn label(&self) -> &str;
//pub backend: fn match_paths(&self) -> &HashSet<String>;
pub match_paths: HashSet<String>, }
}
impl Config {
}

View File

@ -0,0 +1,43 @@
use anyhow::Result;
use thiserror::Error;
use std::{convert::TryInto, path::Path};
mod yaml;
#[derive(Debug, Clone, PartialEq, Default)]
pub(crate) struct ParsedConfig {
pub label: Option<String>,
// Includes
pub includes: Option<Vec<String>>,
pub excludes: Option<Vec<String>>,
pub extra_includes: Option<Vec<String>>,
pub extra_excludes: Option<Vec<String>>,
pub use_standard_includes: Option<bool>,
// Filters
pub filter_title: Option<String>,
pub filter_class: Option<String>,
pub filter_exec: Option<String>,
pub filter_os: Option<String>,
}
impl ParsedConfig {
pub fn load(path: &Path) -> Result<Self> {
let content = std::fs::read_to_string(path)?;
match yaml::YAMLConfig::parse_from_str(&content) {
Ok(config) => {
Ok(config.try_into()?)
}
Err(err) => {
Err(ParsedConfigError::LoadFailed(err).into())
}
}
}
}
#[derive(Error, Debug)]
pub enum ParsedConfigError {
#[error("can't load config `{0}`")]
LoadFailed(#[from] anyhow::Error),
}

View File

@ -0,0 +1,120 @@
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
use crate::util::is_yaml_empty;
use super::ParsedConfig;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub(crate) struct YAMLConfig {
#[serde(default)]
pub label: Option<String>,
#[serde(default)]
pub includes: Option<Vec<String>>,
#[serde(default)]
pub excludes: Option<Vec<String>>,
#[serde(default)]
pub extra_includes: Option<Vec<String>>,
#[serde(default)]
pub extra_excludes: Option<Vec<String>>,
#[serde(default)]
pub use_standard_includes: Option<bool>,
// Filters
#[serde(default)]
pub filter_title: Option<String>,
#[serde(default)]
pub filter_class: Option<String>,
#[serde(default)]
pub filter_exec: Option<String>,
#[serde(default)]
pub filter_os: Option<String>,
}
impl YAMLConfig {
pub fn parse_from_str(yaml: &str) -> Result<Self> {
// Because an empty string is not valid YAML but we want to support it anyway
if is_yaml_empty(yaml) {
return Ok(serde_yaml::from_str(
"arbitrary_field_that_will_not_block_the_parser: true",
)?);
}
Ok(serde_yaml::from_str(yaml)?)
}
}
impl TryFrom<YAMLConfig> for ParsedConfig {
type Error = anyhow::Error;
fn try_from(yaml_config: YAMLConfig) -> Result<Self, Self::Error> {
Ok(Self {
label: yaml_config.label,
use_standard_includes: yaml_config.use_standard_includes,
includes: yaml_config.includes,
extra_includes: yaml_config.extra_includes,
excludes: yaml_config.excludes,
extra_excludes: yaml_config.extra_excludes,
filter_class: yaml_config.filter_class,
filter_exec: yaml_config.filter_exec,
filter_os: yaml_config.filter_os,
filter_title: yaml_config.filter_title,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::convert::TryInto;
#[test]
fn conversion_to_parsed_config_works_correctly() {
let config = YAMLConfig::parse_from_str(
r#"
label: "test"
use_standard_includes: true
includes: ["test1"]
extra_includes: ["test2"]
excludes: ["test3"]
extra_excludes: ["test4"]
filter_class: "test5"
filter_exec: "test6"
filter_os: "test7"
filter_title: "test8"
"#,
)
.unwrap();
let parsed_config: ParsedConfig = config.try_into().unwrap();
assert_eq!(
parsed_config,
ParsedConfig {
label: Some("test".to_string()),
use_standard_includes: Some(true),
includes: Some(vec!["test1".to_string()]),
extra_includes: Some(vec!["test2".to_string()]),
excludes: Some(vec!["test3".to_string()]),
extra_excludes: Some(vec!["test4".to_string()]),
filter_class: Some("test5".to_string()),
filter_exec: Some("test6".to_string()),
filter_os: Some("test7".to_string()),
filter_title: Some("test8".to_string()),
}
)
}
}

View File

@ -1,6 +1,6 @@
use std::{ use std::{
collections::HashSet, collections::HashSet,
path::{Path, PathBuf}, path::{Path},
}; };
use glob::glob; use glob::glob;

View File

@ -0,0 +1,393 @@
use super::{parse::ParsedConfig, path::calculate_paths, Config};
use crate::merge;
use anyhow::Result;
use std::iter::FromIterator;
use std::{collections::HashSet, path::Path};
use thiserror::Error;
const STANDARD_INCLUDES: &[&str] = &["../match/**/*.yml"];
const STANDARD_EXCLUDES: &[&str] = &["../match/**/_*.yml"];
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct ResolvedConfig {
parsed: ParsedConfig,
// Generated properties
match_paths: HashSet<String>,
}
impl Default for ResolvedConfig {
fn default() -> Self {
Self {
parsed: Default::default(),
match_paths: HashSet::new(),
}
}
}
impl Config for ResolvedConfig {
fn label(&self) -> &str {
self.parsed.label.as_deref().unwrap_or("none")
}
fn match_paths(&self) -> &HashSet<String> {
&self.match_paths
}
}
impl ResolvedConfig {
pub fn load(path: &Path, parent: Option<&Self>) -> Result<Self> {
let mut config = ParsedConfig::load(path)?;
// Merge with parent config if present
if let Some(parent) = parent {
Self::merge_parsed(&mut config, &parent.parsed);
}
// Extract the base directory
let base_dir = path
.parent()
.ok_or_else(|| ResolveError::ParentResolveFailed())?;
let match_paths = Self::generate_match_paths(&config, base_dir);
Ok(Self {
parsed: config,
match_paths,
})
}
fn merge_parsed(child: &mut ParsedConfig, parent: &ParsedConfig) {
// Override the None fields with the parent's value
merge!(
ParsedConfig,
child,
parent,
// Fields
label,
includes,
excludes,
extra_includes,
extra_excludes,
use_standard_includes,
filter_title,
filter_class,
filter_exec,
filter_os
);
}
fn aggregate_includes(config: &ParsedConfig) -> HashSet<String> {
let mut includes = HashSet::new();
if config.use_standard_includes.is_none() || config.use_standard_includes.unwrap() {
STANDARD_INCLUDES.iter().for_each(|include| {
includes.insert(include.to_string());
})
}
if let Some(yaml_includes) = config.includes.as_ref() {
yaml_includes.iter().for_each(|include| {
includes.insert(include.to_string());
})
}
if let Some(extra_includes) = config.extra_includes.as_ref() {
extra_includes.iter().for_each(|include| {
includes.insert(include.to_string());
})
}
includes
}
fn aggregate_excludes(config: &ParsedConfig) -> HashSet<String> {
let mut excludes = HashSet::new();
if config.use_standard_includes.is_none() || config.use_standard_includes.unwrap() {
STANDARD_EXCLUDES.iter().for_each(|exclude| {
excludes.insert(exclude.to_string());
})
}
if let Some(yaml_excludes) = config.excludes.as_ref() {
yaml_excludes.iter().for_each(|exclude| {
excludes.insert(exclude.to_string());
})
}
if let Some(extra_excludes) = config.extra_excludes.as_ref() {
extra_excludes.iter().for_each(|exclude| {
excludes.insert(exclude.to_string());
})
}
excludes
}
fn generate_match_paths(config: &ParsedConfig, base_dir: &Path) -> HashSet<String> {
let includes = Self::aggregate_includes(config);
let excludes = Self::aggregate_excludes(config);
// Extract the paths
let exclude_paths = calculate_paths(base_dir, excludes.iter());
let include_paths = calculate_paths(base_dir, includes.iter());
HashSet::from_iter(include_paths.difference(&exclude_paths).cloned())
}
}
#[derive(Error, Debug)]
pub enum ResolveError {
#[error("unable to resolve parent path")]
ParentResolveFailed(),
}
#[cfg(test)]
mod tests {
use super::*;
use crate::util::tests::use_test_directory;
use std::fs::create_dir_all;
use std::iter::FromIterator;
#[test]
fn aggregate_includes_empty_config() {
assert_eq!(
ResolvedConfig::aggregate_includes(&ParsedConfig {
..Default::default()
}),
HashSet::from_iter(vec!["../match/**/*.yml".to_string(),].iter().cloned())
);
}
#[test]
fn aggregate_includes_no_standard() {
assert_eq!(
ResolvedConfig::aggregate_includes(&ParsedConfig {
use_standard_includes: Some(false),
..Default::default()
}),
HashSet::new()
);
}
#[test]
fn aggregate_includes_custom_includes() {
assert_eq!(
ResolvedConfig::aggregate_includes(&ParsedConfig {
includes: Some(vec!["custom/*.yml".to_string()]),
..Default::default()
}),
HashSet::from_iter(
vec!["../match/**/*.yml".to_string(), "custom/*.yml".to_string()]
.iter()
.cloned()
)
);
}
#[test]
fn aggregate_includes_extra_includes() {
assert_eq!(
ResolvedConfig::aggregate_includes(&ParsedConfig {
extra_includes: Some(vec!["custom/*.yml".to_string()]),
..Default::default()
}),
HashSet::from_iter(
vec!["../match/**/*.yml".to_string(), "custom/*.yml".to_string()]
.iter()
.cloned()
)
);
}
#[test]
fn aggregate_includes_includes_and_extra_includes() {
assert_eq!(
ResolvedConfig::aggregate_includes(&ParsedConfig {
includes: Some(vec!["sub/*.yml".to_string()]),
extra_includes: Some(vec!["custom/*.yml".to_string()]),
..Default::default()
}),
HashSet::from_iter(
vec!["../match/**/*.yml".to_string(), "custom/*.yml".to_string(), "sub/*.yml".to_string()]
.iter()
.cloned()
)
);
}
#[test]
fn aggregate_excludes_empty_config() {
assert_eq!(
ResolvedConfig::aggregate_excludes(&ParsedConfig {
..Default::default()
}),
HashSet::from_iter(vec!["../match/**/_*.yml".to_string(),].iter().cloned())
);
}
#[test]
fn aggregate_excludes_no_standard() {
assert_eq!(
ResolvedConfig::aggregate_excludes(&ParsedConfig {
use_standard_includes: Some(false),
..Default::default()
}),
HashSet::new()
);
}
#[test]
fn aggregate_excludes_custom_excludes() {
assert_eq!(
ResolvedConfig::aggregate_excludes(&ParsedConfig {
excludes: Some(vec!["custom/*.yml".to_string()]),
..Default::default()
}),
HashSet::from_iter(
vec!["../match/**/_*.yml".to_string(), "custom/*.yml".to_string()]
.iter()
.cloned()
)
);
}
#[test]
fn aggregate_excludes_extra_excludes() {
assert_eq!(
ResolvedConfig::aggregate_excludes(&ParsedConfig {
extra_excludes: Some(vec!["custom/*.yml".to_string()]),
..Default::default()
}),
HashSet::from_iter(
vec!["../match/**/_*.yml".to_string(), "custom/*.yml".to_string()]
.iter()
.cloned()
)
);
}
#[test]
fn aggregate_excludes_excludes_and_extra_excludes() {
assert_eq!(
ResolvedConfig::aggregate_excludes(&ParsedConfig {
excludes: Some(vec!["sub/*.yml".to_string()]),
extra_excludes: Some(vec!["custom/*.yml".to_string()]),
..Default::default()
}),
HashSet::from_iter(
vec!["../match/**/_*.yml".to_string(), "custom/*.yml".to_string(), "sub/*.yml".to_string()]
.iter()
.cloned()
)
);
}
#[test]
fn merge_parent_field_parent_fallback() {
let parent = ParsedConfig {
use_standard_includes: Some(false),
..Default::default()
};
let mut child = ParsedConfig {
..Default::default()
};
assert_eq!(child.use_standard_includes, None);
ResolvedConfig::merge_parsed(&mut child, &parent);
assert_eq!(child.use_standard_includes, Some(false));
}
#[test]
fn merge_parent_field_child_overwrite_parent() {
let parent = ParsedConfig {
use_standard_includes: Some(true),
..Default::default()
};
let mut child = ParsedConfig {
use_standard_includes: Some(false),
..Default::default()
};
assert_eq!(child.use_standard_includes, Some(false));
ResolvedConfig::merge_parsed(&mut child, &parent);
assert_eq!(child.use_standard_includes, Some(false));
}
#[test]
fn match_paths_generated_correctly() {
use_test_directory(|_, match_dir, config_dir| {
let sub_dir = match_dir.join("sub");
create_dir_all(&sub_dir).unwrap();
let base_file = match_dir.join("base.yml");
std::fs::write(&base_file, "test").unwrap();
let another_file = match_dir.join("another.yml");
std::fs::write(&another_file, "test").unwrap();
let under_file = match_dir.join("_sub.yml");
std::fs::write(&under_file, "test").unwrap();
let sub_file = sub_dir.join("sub.yml");
std::fs::write(&sub_file, "test").unwrap();
let config_file = config_dir.join("default.yml");
std::fs::write(&config_file, "").unwrap();
let config = ResolvedConfig::load(&config_file, None).unwrap();
let mut expected = HashSet::new();
expected.insert(base_file.to_string_lossy().to_string());
expected.insert(another_file.to_string_lossy().to_string());
expected.insert(sub_file.to_string_lossy().to_string());
assert_eq!(config.match_paths(), &expected);
});
}
#[test]
fn match_paths_generated_correctly_with_child_config() {
use_test_directory(|_, match_dir, config_dir| {
let sub_dir = match_dir.join("sub");
create_dir_all(&sub_dir).unwrap();
let base_file = match_dir.join("base.yml");
std::fs::write(&base_file, "test").unwrap();
let another_file = match_dir.join("another.yml");
std::fs::write(&another_file, "test").unwrap();
let under_file = match_dir.join("_sub.yml");
std::fs::write(&under_file, "test").unwrap();
let sub_file = sub_dir.join("another.yml");
std::fs::write(&sub_file, "test").unwrap();
let sub_under_file = sub_dir.join("_sub.yml");
std::fs::write(&sub_under_file, "test").unwrap();
// Configs
let parent_file = config_dir.join("parent.yml");
std::fs::write(&parent_file, r#"
excludes: ['../**/another.yml']
"#).unwrap();
let config_file = config_dir.join("default.yml");
std::fs::write(&config_file, r#"
use_standard_includes: false
excludes: []
includes: ["../match/sub/*.yml"]
"#).unwrap();
let parent = ResolvedConfig::load(&parent_file, None).unwrap();
let child = ResolvedConfig::load(&config_file, Some(&parent)).unwrap();
let mut expected = HashSet::new();
expected.insert(sub_file.to_string_lossy().to_string());
expected.insert(sub_under_file.to_string_lossy().to_string());
assert_eq!(child.match_paths(), &expected);
let mut expected = HashSet::new();
expected.insert(base_file.to_string_lossy().to_string());
assert_eq!(parent.match_paths(), &expected);
});
}
}

View File

@ -1,355 +0,0 @@
use anyhow::{private::kind::TraitKind, Result};
use serde::{Deserialize, Serialize};
use std::{iter::FromIterator, path::Path};
use std::{collections::HashSet, convert::TryFrom};
use crate::{merge, util::is_yaml_empty};
use super::path::calculate_paths;
const STANDARD_INCLUDES: &[&str] = &["match/**/*.yml"];
const STANDARD_EXCLUDES: &[&str] = &["match/**/_*.yml"];
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct YAMLConfig {
#[serde(default)]
pub label: Option<String>,
#[serde(default)]
pub includes: Option<Vec<String>>,
#[serde(default)]
pub excludes: Option<Vec<String>>,
#[serde(default)]
pub extra_includes: Option<Vec<String>>,
#[serde(default)]
pub extra_excludes: Option<Vec<String>>,
#[serde(default)]
pub use_standard_includes: Option<bool>,
// Filters
#[serde(default)]
pub filter_title: Option<String>,
#[serde(default)]
pub filter_class: Option<String>,
#[serde(default)]
pub filter_exec: Option<String>,
#[serde(default)]
pub filter_os: Option<String>,
}
impl YAMLConfig {
pub fn parse_from_str(yaml: &str) -> Result<Self> {
// Because an empty string is not valid YAML but we want to support it anyway
if is_yaml_empty(yaml) {
return Ok(serde_yaml::from_str(
"arbitrary_field_that_will_not_block_the_parser: true",
)?);
}
Ok(serde_yaml::from_str(yaml)?)
}
pub fn merge_parent(&mut self, parent: &YAMLConfig) {
// Override the None fields with the parent's value
merge!(
YAMLConfig,
self,
parent,
// Fields
label,
includes,
excludes,
extra_includes,
extra_excludes,
use_standard_includes,
filter_title,
filter_class,
filter_exec,
filter_os
);
}
pub fn aggregate_includes(&self) -> HashSet<String> {
let mut includes = HashSet::new();
if self.use_standard_includes.is_none() || self.use_standard_includes.unwrap() {
STANDARD_INCLUDES.iter().for_each(|include| {
includes.insert(include.to_string());
})
}
if let Some(yaml_includes) = self.includes.as_ref() {
yaml_includes.iter().for_each(|include| {
includes.insert(include.to_string());
})
}
if let Some(extra_includes) = self.extra_includes.as_ref() {
extra_includes.iter().for_each(|include| {
includes.insert(include.to_string());
})
}
includes
}
pub fn aggregate_excludes(&self) -> HashSet<String> {
let mut excludes = HashSet::new();
if self.use_standard_includes.is_none() || self.use_standard_includes.unwrap() {
STANDARD_EXCLUDES.iter().for_each(|exclude| {
excludes.insert(exclude.to_string());
})
}
if let Some(yaml_excludes) = self.excludes.as_ref() {
yaml_excludes.iter().for_each(|exclude| {
excludes.insert(exclude.to_string());
})
}
if let Some(extra_excludes) = self.extra_excludes.as_ref() {
extra_excludes.iter().for_each(|exclude| {
excludes.insert(exclude.to_string());
})
}
excludes
}
pub fn generate_match_paths(&self, base_dir: &Path) -> HashSet<String> {
let includes = self.aggregate_includes();
let excludes = self.aggregate_excludes();
// Extract the paths
let exclude_paths = calculate_paths(base_dir, excludes.iter());
let include_paths = calculate_paths(base_dir, includes.iter());
HashSet::from_iter(include_paths.difference(&exclude_paths).cloned())
}
// TODO: test
pub fn to_config(&self, base_dir: &Path) -> Result<super::Config> {
let match_paths = self.generate_match_paths(base_dir);
Ok(super::Config {
label: self.label.clone(),
match_paths,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::util::tests::use_test_directory;
use std::iter::FromIterator;
use std::fs::create_dir_all;
#[test]
fn aggregate_includes_empty_config() {
assert_eq!(
YAMLConfig::parse_from_str("").unwrap().aggregate_includes(),
HashSet::from_iter(vec!["match/**/*.yml".to_string(),].iter().cloned())
);
}
#[test]
fn aggregate_includes_no_standard() {
assert_eq!(
YAMLConfig::parse_from_str("use_standard_includes: false").unwrap().aggregate_includes(),
HashSet::new()
);
}
#[test]
fn aggregate_includes_custom_includes() {
assert_eq!(
YAMLConfig::parse_from_str("includes: ['custom/*.yml']")
.unwrap()
.aggregate_includes(),
HashSet::from_iter(
vec!["match/**/*.yml".to_string(), "custom/*.yml".to_string()]
.iter()
.cloned()
)
);
}
#[test]
fn aggregate_includes_extra_includes() {
assert_eq!(
YAMLConfig::parse_from_str("extra_includes: ['custom/*.yml']")
.unwrap()
.aggregate_includes(),
HashSet::from_iter(
vec!["match/**/*.yml".to_string(), "custom/*.yml".to_string()]
.iter()
.cloned()
)
);
}
#[test]
fn aggregate_includes_includes_and_extra_includes() {
assert_eq!(
YAMLConfig::parse_from_str("includes: ['sub/*.yml']\nextra_includes: ['custom/*.yml']")
.unwrap()
.aggregate_includes(),
HashSet::from_iter(
vec!["match/**/*.yml".to_string(), "custom/*.yml".to_string(), "sub/*.yml".to_string()]
.iter()
.cloned()
)
);
}
#[test]
fn aggregate_excludes_empty_config() {
assert_eq!(
YAMLConfig::parse_from_str("").unwrap().aggregate_excludes(),
HashSet::from_iter(vec!["match/**/_*.yml".to_string(),].iter().cloned())
);
}
#[test]
fn aggregate_excludes_no_standard() {
assert_eq!(
YAMLConfig::parse_from_str("use_standard_includes: false").unwrap().aggregate_excludes(),
HashSet::new()
);
}
#[test]
fn aggregate_excludes_custom_excludes() {
assert_eq!(
YAMLConfig::parse_from_str("excludes: ['custom/*.yml']")
.unwrap()
.aggregate_excludes(),
HashSet::from_iter(
vec!["match/**/_*.yml".to_string(), "custom/*.yml".to_string()]
.iter()
.cloned()
)
);
}
#[test]
fn aggregate_excludes_extra_excludes() {
assert_eq!(
YAMLConfig::parse_from_str("extra_excludes: ['custom/*.yml']")
.unwrap()
.aggregate_excludes(),
HashSet::from_iter(
vec!["match/**/_*.yml".to_string(), "custom/*.yml".to_string()]
.iter()
.cloned()
)
);
}
#[test]
fn aggregate_excludes_excludes_and_extra_excludes() {
assert_eq!(
YAMLConfig::parse_from_str("excludes: ['sub/*.yml']\nextra_excludes: ['custom/*.yml']")
.unwrap()
.aggregate_excludes(),
HashSet::from_iter(
vec!["match/**/_*.yml".to_string(), "custom/*.yml".to_string(), "sub/*.yml".to_string()]
.iter()
.cloned()
)
);
}
#[test]
fn merge_parent_field_parent_fallback() {
let parent =
YAMLConfig::parse_from_str("use_standard_includes: false").unwrap();
let mut child =
YAMLConfig::parse_from_str("").unwrap();
assert_eq!(child.use_standard_includes, None);
child.merge_parent(&parent);
assert_eq!(child.use_standard_includes, Some(false));
}
#[test]
fn merge_parent_field_child_overwrite_parent() {
let parent =
YAMLConfig::parse_from_str("use_standard_includes: true").unwrap();
let mut child =
YAMLConfig::parse_from_str("use_standard_includes: false").unwrap();
assert_eq!(child.use_standard_includes, Some(false));
child.merge_parent(&parent);
assert_eq!(child.use_standard_includes, Some(false));
}
#[test]
fn generate_match_paths_works_correctly() {
use_test_directory(|base, match_dir, _| {
let sub_dir = match_dir.join("sub");
create_dir_all(&sub_dir).unwrap();
std::fs::write(match_dir.join("base.yml"), "test").unwrap();
std::fs::write(match_dir.join("another.yml"), "test").unwrap();
std::fs::write(match_dir.join("_sub.yml"), "test").unwrap();
std::fs::write(sub_dir.join("sub.yml"), "test").unwrap();
let config = YAMLConfig::parse_from_str("").unwrap();
let mut expected = HashSet::new();
expected.insert(format!("{}/match/base.yml", base.to_string_lossy()));
expected.insert(format!("{}/match/another.yml", base.to_string_lossy()));
expected.insert(format!("{}/match/sub/sub.yml", base.to_string_lossy()));
assert_eq!(config.generate_match_paths(base), expected);
});
}
#[test]
fn generate_match_paths_works_correctly_with_child_config() {
use_test_directory(|base, match_dir, _| {
let sub_dir = match_dir.join("sub");
create_dir_all(&sub_dir).unwrap();
std::fs::write(match_dir.join("base.yml"), "test").unwrap();
std::fs::write(match_dir.join("another.yml"), "test").unwrap();
std::fs::write(match_dir.join("_sub.yml"), "test").unwrap();
std::fs::write(sub_dir.join("another.yml"), "test").unwrap();
std::fs::write(sub_dir.join("_sub.yml"), "test").unwrap();
let parent = YAMLConfig::parse_from_str(r"
excludes: ['**/another.yml']
").unwrap();
let mut child = YAMLConfig::parse_from_str(r"
use_standard_includes: false
excludes: []
includes: ['match/sub/*.yml']
").unwrap();
child.merge_parent(&parent);
let mut expected = HashSet::new();
expected.insert(format!("{}/match/sub/another.yml", base.to_string_lossy()));
expected.insert(format!("{}/match/sub/_sub.yml", base.to_string_lossy()));
assert_eq!(child.generate_match_paths(base), expected);
let mut expected = HashSet::new();
expected.insert(format!("{}/match/base.yml", base.to_string_lossy()));
assert_eq!(parent.generate_match_paths(base), expected);
});
}
// TODO: test conversion to Config (we need to test that the file match resolution works)
}

View File

@ -91,9 +91,8 @@ mod tests {
server.accept_one().unwrap(); server.accept_one().unwrap();
}); });
if cfg!(target_os = "windows") { // TODO: avoid delay and change the IPC code so that we can wait for the IPC
std::thread::sleep(std::time::Duration::from_secs(1)); //std::thread::sleep(std::time::Duration::from_secs(1));
}
let client = client::<Event>("testespansoipc", &std::env::temp_dir()).unwrap(); let client = client::<Event>("testespansoipc", &std::env::temp_dir()).unwrap();
client.send(Event::Foo("hello".to_string())).unwrap(); client.send(Event::Foo("hello".to_string())).unwrap();