use moon_common::Id;
use moon_config::{
FileGroupInput, FileGroupInputFormat, GlobInput, GlobPath, Input, ManifestDepsInput,
ProjectInput, Uri, test_utils::*,
};
use schematic::RegexSetting;
mod input_shape {
use super::*;
mod parse_string {
use super::*;
#[test]
fn converts_backward_slashes() {
assert_eq!(
Input::parse("some\\file.txt").unwrap(),
Input::File(stub_file_input("some/file.txt"))
);
}
#[test]
fn env_vars() {
assert_eq!(Input::parse("$VAR").unwrap(), Input::EnvVar("VAR".into()));
assert_eq!(
Input::parse("$VAR_*").unwrap(),
Input::EnvVarGlob("VAR_*".into())
);
assert_eq!(
Input::parse("$VAR_*_SUFFIX").unwrap(),
Input::EnvVarGlob("VAR_*_SUFFIX".into())
);
assert_eq!(
Input::parse("$*_SUFFIX").unwrap(),
Input::EnvVarGlob("*_SUFFIX".into())
);
}
#[test]
fn token_funcs() {
assert_eq!(
Input::parse("@group(name)").unwrap(),
Input::TokenFunc("@group(name)".into())
);
assert_eq!(
Input::parse("@dirs(name)").unwrap(),
Input::TokenFunc("@dirs(name)".into())
);
assert_eq!(
Input::parse("@files(name)").unwrap(),
Input::TokenFunc("@files(name)".into())
);
assert_eq!(
Input::parse("@globs(name)").unwrap(),
Input::TokenFunc("@globs(name)".into())
);
assert_eq!(
Input::parse("@root(name)").unwrap(),
Input::TokenFunc("@root(name)".into())
);
}
#[test]
fn token_vars() {
assert_eq!(
Input::parse("$workspaceRoot").unwrap(),
Input::TokenVar("$workspaceRoot".into())
);
assert_eq!(
Input::parse("$projectLayer").unwrap(),
Input::TokenVar("$projectLayer".into())
);
}
#[test]
fn file_protocol() {
let mut input = stub_file_input("file.txt");
input.optional = Some(true);
assert_eq!(
Input::parse("file://file.txt?optional").unwrap(),
Input::File(input)
);
let mut input = stub_file_input("/file.txt");
input.optional = Some(false);
assert_eq!(
Input::parse("file:///file.txt?optional=false").unwrap(),
Input::File(input)
);
}
#[test]
fn file_project_relative() {
assert_eq!(
Input::parse("file.rs").unwrap(),
Input::File(stub_file_input("file.rs"))
);
assert_eq!(
Input::parse("dir/file.rs").unwrap(),
Input::File(stub_file_input("dir/file.rs"))
);
assert_eq!(
Input::parse("./file.rs").unwrap(),
Input::File(stub_file_input("file.rs"))
);
assert_eq!(
Input::parse("././dir/file.rs").unwrap(),
Input::File(stub_file_input("dir/file.rs"))
);
}
#[test]
fn file_project_relative_protocol() {
assert_eq!(
Input::parse("file://file.rs").unwrap(),
Input::File(stub_file_input("file.rs"))
);
assert_eq!(
Input::parse("file://dir/file.rs").unwrap(),
Input::File(stub_file_input("dir/file.rs"))
);
assert_eq!(
Input::parse("file://./file.rs").unwrap(),
Input::File(stub_file_input("file.rs"))
);
assert_eq!(
Input::parse("file://././dir/file.rs").unwrap(),
Input::File(stub_file_input("dir/file.rs"))
);
}
#[test]
fn file_workspace_relative() {
assert_eq!(
Input::parse("/file.rs").unwrap(),
Input::File(stub_file_input("/file.rs"))
);
assert_eq!(
Input::parse("/dir/file.rs").unwrap(),
Input::File(stub_file_input("/dir/file.rs"))
);
assert_eq!(
Input::parse("/.cache/$projectSource").unwrap(),
Input::File(stub_file_input("/.cache/$projectSource"))
);
}
#[test]
fn file_workspace_relative_protocol() {
assert_eq!(
Input::parse("file:///file.rs").unwrap(),
Input::File(stub_file_input("/file.rs"))
);
assert_eq!(
Input::parse("file:///dir/file.rs").unwrap(),
Input::File(stub_file_input("/dir/file.rs"))
);
assert_eq!(
Input::parse("file:///.cache/$projectSource").unwrap(),
Input::File(stub_file_input("/.cache/$projectSource"))
);
}
#[test]
fn glob_protocol() {
let mut input = stub_glob_input("file.*");
input.cache = true;
assert_eq!(
Input::parse("glob://file.*?cache").unwrap(),
Input::Glob(input)
);
let mut input = stub_glob_input("/file.*");
input.cache = false;
assert_eq!(
Input::parse("glob:///file.*?cache=false").unwrap(),
Input::Glob(input)
);
}
#[test]
fn glob_protocol_supports_all_syntax() {
for pat in [
"*.png",
"ba(r|z).txt",
"**/{*.{go,rs}}",
"**/*.{md,txt}",
"pkg/**/PKGBUILD",
"dir/{a?c,x?z,foo}",
"lib/[qa-cX-Z]/*",
"(?-i)photos/**/*.(?i){jpg,jpeg}",
"a/<b/**:1,>",
"file.tsx?",
] {
let mut input = stub_glob_input(pat);
input.cache = true;
assert_eq!(
Input::Glob(GlobInput {
glob: GlobPath(pat.into()),
cache: true
}),
Input::Glob(input)
);
let mut input = stub_glob_input(pat);
input.cache = false;
assert_eq!(
Input::Glob(GlobInput {
glob: GlobPath(pat.into()),
cache: false
}),
Input::Glob(input)
);
}
}
#[test]
fn glob_project_relative() {
assert_eq!(
Input::parse("!file.*").unwrap(),
Input::Glob(stub_glob_input("!file.*"))
);
assert_eq!(
Input::parse("dir/**/*").unwrap(),
Input::Glob(stub_glob_input("dir/**/*"))
);
assert_eq!(
Input::parse("./dir/**/*").unwrap(),
Input::Glob(stub_glob_input("dir/**/*"))
);
assert_eq!(
Input::parse("$projectSource/**/*").unwrap(),
Input::Glob(stub_glob_input("$projectSource/**/*"))
);
}
#[test]
fn glob_project_relative_protocol() {
assert_eq!(
Input::parse("glob://!file.*").unwrap(),
Input::Glob(stub_glob_input("!file.*"))
);
assert_eq!(
Input::parse("glob://dir/**/*").unwrap(),
Input::Glob(stub_glob_input("dir/**/*"))
);
assert_eq!(
Input::parse("glob://./dir/**/*").unwrap(),
Input::Glob(stub_glob_input("dir/**/*"))
);
assert_eq!(
Input::parse("glob://$projectSource/**/*").unwrap(),
Input::Glob(stub_glob_input("$projectSource/**/*"))
);
}
#[test]
fn glob_workspace_relative() {
assert_eq!(
Input::parse("/!file.*").unwrap(),
Input::Glob(stub_glob_input("!/file.*"))
);
assert_eq!(
Input::parse("!/file.*").unwrap(),
Input::Glob(stub_glob_input("!/file.*"))
);
assert_eq!(
Input::parse("/dir/**/*").unwrap(),
Input::Glob(stub_glob_input("/dir/**/*"))
);
}
#[test]
fn glob_workspace_relative_protocol() {
assert_eq!(
Input::parse("glob:///!file.*").unwrap(),
Input::Glob(stub_glob_input("!/file.*"))
);
assert_eq!(
Input::parse("glob://!/file.*").unwrap(),
Input::Glob(stub_glob_input("!/file.*"))
);
assert_eq!(
Input::parse("glob:///dir/**/*").unwrap(),
Input::Glob(stub_glob_input("/dir/**/*"))
);
}
#[test]
fn file_group_protocol() {
assert_eq!(
Input::parse("group://sources").unwrap(),
Input::FileGroup(FileGroupInput {
group: Id::raw("sources"),
..Default::default()
})
);
assert_eq!(
Input::parse("group://sources?format=dirs").unwrap(),
Input::FileGroup(FileGroupInput {
group: Id::raw("sources"),
format: FileGroupInputFormat::Dirs,
})
);
assert_eq!(
Input::parse("group://sources?as=root").unwrap(),
Input::FileGroup(FileGroupInput {
group: Id::raw("sources"),
format: FileGroupInputFormat::Root,
})
);
}
#[test]
fn project_protocol() {
assert_eq!(
Input::parse("project://app").unwrap(),
Input::Project(ProjectInput {
project: "app".into(),
..Default::default()
})
);
assert_eq!(
Input::parse("project://app?filter=src/**&filter=!tests/**/*").unwrap(),
Input::Project(ProjectInput {
project: "app".into(),
filter: vec!["src/**".into(), "!tests/**/*".into()],
..Default::default()
})
);
assert_eq!(
Input::parse("project://app?group=sources").unwrap(),
Input::Project(ProjectInput {
project: "app".into(),
group: Some(Id::raw("sources")),
..Default::default()
})
);
}
#[test]
fn project_protocol_all() {
assert_eq!(
Input::parse("project://^").unwrap(),
Input::Project(ProjectInput {
project: "^".into(),
..Default::default()
})
);
assert_eq!(
Input::parse("project://^?filter=src/**&filter=!tests/**/*").unwrap(),
Input::Project(ProjectInput {
project: "^".into(),
filter: vec!["src/**".into(), "!tests/**/*".into()],
..Default::default()
})
);
assert_eq!(
Input::parse("project://^?group=sources").unwrap(),
Input::Project(ProjectInput {
project: "^".into(),
group: Some(Id::raw("sources")),
..Default::default()
})
);
}
#[test]
fn project_protocol_scoped() {
assert_eq!(
Input::parse("project://^development").unwrap(),
Input::Project(ProjectInput {
project: "^development".into(),
..Default::default()
})
);
assert_eq!(
Input::parse("project://^production?filter=src/**&filter=!tests/**/*").unwrap(),
Input::Project(ProjectInput {
project: "^production".into(),
filter: vec!["src/**".into(), "!tests/**/*".into()],
..Default::default()
})
);
assert_eq!(
Input::parse("project://^build?group=sources").unwrap(),
Input::Project(ProjectInput {
project: "^build".into(),
group: Some(Id::raw("sources")),
..Default::default()
})
);
}
#[test]
#[should_panic(expected = "input protocol `unknown://` is not supported")]
fn errors_for_unknown_protocol() {
Input::parse("unknown://test").unwrap();
}
#[test]
#[should_panic(expected = "parent directory traversal (..) is not supported")]
fn errors_for_parent_traversal() {
Input::parse("../../file.txt").unwrap();
}
#[test]
#[should_panic(expected = "parent directory traversal (..) is not supported")]
fn errors_for_parent_traversal_inner() {
Input::parse("dir/../../file.txt").unwrap();
}
}
mod parse_object {
use super::*;
#[test]
fn files() {
let input: Input = serde_json::from_str(r#""file.txt""#).unwrap();
assert_eq!(input, Input::File(stub_file_input("file.txt")));
let input: Input = serde_json::from_str(r#"{ "file": "file.txt" }"#).unwrap();
assert_eq!(input, Input::File(stub_file_input("file.txt")));
let input: Input =
serde_json::from_str(r#"{ "file": "dir/file.txt", "optional": true }"#).unwrap();
assert_eq!(
input,
Input::File({
let mut inner = stub_file_input("dir/file.txt");
inner.optional = Some(true);
inner
})
);
let input: Input = serde_json::from_str(
r#"{ "file": "/root/file.txt", "optional": true, "matches": "a|b|c" }"#,
)
.unwrap();
assert_eq!(
input,
Input::File({
let mut inner = stub_file_input("/root/file.txt");
inner.optional = Some(true);
inner.content = Some(RegexSetting::new("a|b|c").unwrap());
inner
})
);
}
#[test]
fn globs() {
let input: Input = serde_json::from_str(r#""file.*""#).unwrap();
assert_eq!(input, Input::Glob(stub_glob_input("file.*")));
let input: Input = serde_json::from_str(r#"{ "glob": "file.*" }"#).unwrap();
assert_eq!(input, Input::Glob(stub_glob_input("file.*")));
let input: Input =
serde_json::from_str(r#"{ "glob": "/dir/file.*", "cache": false }"#).unwrap();
assert_eq!(
input,
Input::Glob({
let mut inner = stub_glob_input("/dir/file.*");
inner.cache = false;
inner
})
);
}
#[test]
#[should_panic] fn errors_for_parent_traversal() {
let _: Input = serde_json::from_str(r#"{ "glob": "../../file.*" }"#).unwrap();
}
#[test]
#[should_panic] fn errors_for_parent_traversal_inner() {
let _: Input = serde_json::from_str(r#"{ "glob": "dir/../../file.*" }"#).unwrap();
}
#[test]
fn file_group() {
let input: Input = serde_json::from_str(r#"{ "group": "sources" }"#).unwrap();
assert_eq!(
input,
Input::FileGroup(FileGroupInput {
group: Id::raw("sources"),
..Default::default()
})
);
let input: Input =
serde_json::from_str(r#"{ "group": "sources", "format": "files" }"#).unwrap();
assert_eq!(
input,
Input::FileGroup(FileGroupInput {
group: Id::raw("sources"),
format: FileGroupInputFormat::Files,
})
);
}
#[test]
fn project_sources() {
let input: Input = serde_json::from_str(r#"{ "project": "app" }"#).unwrap();
assert_eq!(
input,
Input::Project(ProjectInput {
project: "app".into(),
..Default::default()
})
);
let input: Input =
serde_json::from_str(r#"{ "project": "app", "group": "sources" }"#).unwrap();
assert_eq!(
input,
Input::Project(ProjectInput {
project: "app".into(),
group: Some(Id::raw("sources")),
..Default::default()
})
);
let input: Input = serde_json::from_str(
r#"{ "project": "app", "group": "sources", "filter": ["src/**/*"] }"#,
)
.unwrap();
assert_eq!(
input,
Input::Project(ProjectInput {
project: "app".into(),
group: Some(Id::raw("sources")),
filter: vec!["src/**/*".into()],
})
);
}
#[test]
fn project_sources_all() {
let input: Input = serde_json::from_str(r#"{ "project": "^" }"#).unwrap();
assert_eq!(
input,
Input::Project(ProjectInput {
project: "^".into(),
..Default::default()
})
);
let input: Input =
serde_json::from_str(r#"{ "project": "^", "group": "sources" }"#).unwrap();
assert_eq!(
input,
Input::Project(ProjectInput {
project: "^".into(),
group: Some(Id::raw("sources")),
..Default::default()
})
);
let input: Input = serde_json::from_str(
r#"{ "project": "^", "group": "sources", "filter": ["src/**/*"] }"#,
)
.unwrap();
assert_eq!(
input,
Input::Project(ProjectInput {
project: "^".into(),
group: Some(Id::raw("sources")),
filter: vec!["src/**/*".into()],
})
);
}
}
mod file {
use super::*;
#[test]
fn project_relative() {
let input = stub_file_input("project/file.txt");
assert_eq!(input.file, "project/file.txt");
assert_eq!(input.get_path(), "project/file.txt");
assert!(!input.is_workspace_relative());
let input = stub_file_input("./project/file.txt");
assert_eq!(input.file, "project/file.txt");
assert_eq!(input.get_path(), "project/file.txt");
assert!(!input.is_workspace_relative());
}
#[test]
fn workspace_relative() {
let input = stub_file_input("/root/file.txt");
assert_eq!(input.file, "/root/file.txt");
assert_eq!(input.get_path(), "root/file.txt");
assert!(input.is_workspace_relative());
}
#[test]
fn same_filename_different_context_not_equal() {
let project = stub_file_input("file.txt");
let workspace = stub_file_input("/file.txt");
assert_ne!(project, workspace);
}
#[test]
fn supports_matches_field() {
let input = stub_file_input("file.txt?matches=abc");
assert_eq!(input.content.unwrap().as_str(), "abc");
let input = stub_file_input("file.txt?match=abc");
assert_eq!(input.content.unwrap().as_str(), "abc");
let input = stub_file_input("file.txt?matches");
assert!(input.content.is_none());
}
#[test]
fn supports_optional_field() {
let input = stub_file_input("file.txt?optional");
assert!(input.optional.unwrap());
let input = stub_file_input("file.txt?optional=true");
assert!(input.optional.unwrap());
let input = stub_file_input("file.txt?optional=false");
assert!(!input.optional.unwrap());
}
#[test]
#[should_panic(expected = "globs are not supported")]
fn errors_for_glob() {
stub_file_input("file.*");
}
#[test]
#[should_panic(expected = "unsupported value for `optional`")]
fn errors_invalid_optional_field() {
stub_file_input("file.txt?optional=invalid");
}
#[test]
#[should_panic(expected = "unknown file field `unknown`")]
fn errors_unknown_field() {
stub_file_input("file.txt?unknown");
}
}
mod file_group {
use super::*;
#[test]
fn id() {
let input = FileGroupInput::from_uri(Uri::parse("group://sources").unwrap()).unwrap();
assert_eq!(input.group, "sources");
}
#[test]
fn supports_format_field() {
let input =
FileGroupInput::from_uri(Uri::parse("group://sources?format=dirs").unwrap())
.unwrap();
assert_eq!(input.format, FileGroupInputFormat::Dirs);
let input =
FileGroupInput::from_uri(Uri::parse("group://sources?as=root").unwrap()).unwrap();
assert_eq!(input.format, FileGroupInputFormat::Root);
}
#[test]
#[should_panic(expected = "a file group identifier is required")]
fn errors_no_id() {
FileGroupInput::from_uri(Uri::parse("group://").unwrap()).unwrap();
}
#[test]
#[should_panic(expected = "Invalid identifier format")]
fn errors_invalid_id() {
FileGroupInput::from_uri(Uri::parse("group://@&n3k(").unwrap()).unwrap();
}
#[test]
#[should_panic(expected = "Unknown enum variant")]
fn errors_invalid_format_field() {
FileGroupInput::from_uri(Uri::parse("group://id?format=unknown").unwrap()).unwrap();
}
#[test]
#[should_panic(expected = "unknown file group field `unknown`")]
fn errors_unknown_field() {
FileGroupInput::from_uri(Uri::parse("group://id?unknown").unwrap()).unwrap();
}
}
mod glob {
use super::*;
#[test]
fn default_cache_enabled() {
let input = stub_glob_input("file.*");
assert!(input.cache);
}
#[test]
fn project_relative() {
let input = stub_glob_input("project/file.*");
assert_eq!(input.glob, "project/file.*");
assert_eq!(input.get_path(), "project/file.*");
assert!(!input.is_workspace_relative());
assert!(!input.is_negated());
let input = stub_glob_input("./project/file.*");
assert_eq!(input.glob, "project/file.*");
assert_eq!(input.get_path(), "project/file.*");
assert!(!input.is_workspace_relative());
assert!(!input.is_negated());
}
#[test]
fn project_relative_negated() {
let input = stub_glob_input("!project/file.*");
assert_eq!(input.glob, "!project/file.*");
assert_eq!(input.get_path(), "!project/file.*");
assert!(!input.is_workspace_relative());
assert!(input.is_negated());
let input = stub_glob_input("!./project/file.*");
assert_eq!(input.glob, "!project/file.*");
assert_eq!(input.get_path(), "!project/file.*");
assert!(!input.is_workspace_relative());
assert!(input.is_negated());
let input = stub_glob_input("./!project/file.*");
assert_eq!(input.glob, "!project/file.*");
assert_eq!(input.get_path(), "!project/file.*");
assert!(!input.is_workspace_relative());
assert!(input.is_negated());
}
#[test]
fn workspace_relative() {
let input = stub_glob_input("/root/file.*");
assert_eq!(input.glob, "/root/file.*");
assert_eq!(input.get_path(), "root/file.*");
assert!(input.is_workspace_relative());
assert!(!input.is_negated());
}
#[test]
fn workspace_relative_negated() {
let input = stub_glob_input("!/root/file.*");
assert_eq!(input.glob, "!/root/file.*");
assert_eq!(input.get_path(), "!root/file.*");
assert!(input.is_workspace_relative());
assert!(input.is_negated());
let input = stub_glob_input("/!root/file.*");
assert_eq!(input.glob, "!/root/file.*");
assert_eq!(input.get_path(), "!root/file.*");
assert!(input.is_workspace_relative());
assert!(input.is_negated());
}
#[test]
fn supports_cache_field() {
let input = stub_glob_input("glob://file.*?cache");
assert!(input.cache);
let input = stub_glob_input("glob://file.*?cache=true");
assert!(input.cache);
let input = stub_glob_input("glob://file.*?cache=false");
assert!(!input.cache);
}
#[test]
#[should_panic(expected = "unsupported value for `cache`")]
fn errors_invalid_cache_field() {
stub_glob_input("glob://file.*?cache=invalid");
}
#[test]
#[should_panic(expected = "unknown glob field `unknown`")]
fn errors_unknown_field() {
stub_glob_input("glob://file.*?unknown");
}
}
mod manifest_deps {
use super::*;
#[test]
fn id() {
let input =
ManifestDepsInput::from_uri(Uri::parse("manifest://node").unwrap()).unwrap();
assert_eq!(input.manifest, "node");
}
#[test]
fn supports_deps_field() {
for key in ["dep", "deps", "dependencies"] {
let input = ManifestDepsInput::from_uri(
Uri::parse(format!("manifest://node?{key}").as_str()).unwrap(),
)
.unwrap();
assert!(input.deps.is_empty());
let input = ManifestDepsInput::from_uri(
Uri::parse(format!("manifest://node?{key}=a").as_str()).unwrap(),
)
.unwrap();
assert_eq!(input.deps, ["a"]);
let input = ManifestDepsInput::from_uri(
Uri::parse(format!("manifest://node?{key}=a,b,c").as_str()).unwrap(),
)
.unwrap();
assert_eq!(input.deps, ["a", "b", "c"]);
let input = ManifestDepsInput::from_uri(
Uri::parse(format!("manifest://node?{key}=a&{key}=b,c&{key}=d").as_str())
.unwrap(),
)
.unwrap();
assert_eq!(input.deps, ["a", "b", "c", "d"]);
}
}
#[test]
#[should_panic(expected = "a toolchain identifier is required")]
fn errors_no_id() {
ManifestDepsInput::from_uri(Uri::parse("manifest://").unwrap()).unwrap();
}
#[test]
#[should_panic(expected = "Invalid identifier format")]
fn errors_invalid_id() {
ManifestDepsInput::from_uri(Uri::parse("manifest://@&n3k(").unwrap()).unwrap();
}
#[test]
#[should_panic(expected = "unknown manifest field `unknown`")]
fn errors_unknown_field() {
ManifestDepsInput::from_uri(Uri::parse("manifest://id?unknown").unwrap()).unwrap();
}
}
mod external_project {
use super::*;
#[test]
fn id() {
let input = ProjectInput::from_uri(Uri::parse("project://app").unwrap()).unwrap();
assert_eq!(input.project, "app");
}
#[test]
fn supports_file_group_field() {
for key in ["fileGroup", "group"] {
let input = ProjectInput::from_uri(
Uri::parse(format!("project://app?{key}").as_str()).unwrap(),
)
.unwrap();
assert!(input.group.is_none());
let input = ProjectInput::from_uri(
Uri::parse(format!("project://app?{key}=a").as_str()).unwrap(),
)
.unwrap();
assert_eq!(input.group.unwrap(), "a");
}
}
#[test]
fn supports_filter_field() {
let input =
ProjectInput::from_uri(Uri::parse("project://app?filter").unwrap()).unwrap();
assert!(input.filter.is_empty());
let input =
ProjectInput::from_uri(Uri::parse("project://app?filter=a&filter=b").unwrap())
.unwrap();
assert_eq!(input.filter, ["a", "b"]);
}
#[test]
#[should_panic(expected = "a project identifier is required")]
fn errors_no_id() {
ProjectInput::from_uri(Uri::parse("project://").unwrap()).unwrap();
}
#[test]
#[should_panic(expected = "Invalid identifier format")]
fn errors_invalid_id() {
ProjectInput::from_uri(Uri::parse("project://@&n3k(").unwrap()).unwrap();
}
#[test]
#[should_panic(expected = "unknown project field `unknown`")]
fn errors_unknown_field() {
ProjectInput::from_uri(Uri::parse("project://id?unknown").unwrap()).unwrap();
}
}
}