add unit tests
This commit is contained in:
parent
a2d20dab8e
commit
0c4c957d67
|
@ -0,0 +1,15 @@
|
|||
name: Cargo build & test
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
name: Rust project
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: cargo test --verbose
|
|
@ -23,3 +23,6 @@ serde = { version = "1.0.197", optional = true, features = ["derive"] }
|
|||
serde_json = "1.0.114"
|
||||
tracing = "0.1.40"
|
||||
zip = { version = "2.1.3", default-features = false, features = ["deflate", "time"], optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.10.1"
|
||||
|
|
|
@ -421,7 +421,7 @@ impl Condition {
|
|||
/// Will fail if the condition contains an `Or` variant. Use `compile` instead.
|
||||
fn str_cond(&self) -> Option<String> {
|
||||
match self {
|
||||
Self::Atom(s) => Some("if ".to_string() + &s),
|
||||
Self::Atom(s) => Some("if ".to_string() + s),
|
||||
Self::Not(n) => match *(*n).clone() {
|
||||
Self::Atom(s) => Some("unless ".to_string() + &s),
|
||||
_ => None,
|
||||
|
@ -491,7 +491,7 @@ mod tests {
|
|||
#[allow(clippy::redundant_clone)]
|
||||
#[test]
|
||||
fn test_condition() {
|
||||
let c1 = Condition::Atom("foo".to_string());
|
||||
let c1 = Condition::from("foo");
|
||||
let c2 = Condition::Atom("bar".to_string());
|
||||
let c3 = Condition::Atom("baz".to_string());
|
||||
|
||||
|
@ -574,4 +574,56 @@ mod tests {
|
|||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_combine_conditions_commands() {
|
||||
let conditions = vec!["a", "b", "c"]
|
||||
.into_iter()
|
||||
.map(str::to_string)
|
||||
.collect();
|
||||
let commands = &[(true, "1".to_string()), (false, "2".to_string())];
|
||||
|
||||
let combined = combine_conditions_commands(conditions, commands);
|
||||
assert_eq!(
|
||||
combined,
|
||||
vec![
|
||||
(true, "a 1".to_string()),
|
||||
(false, "2".to_string()),
|
||||
(true, "b 1".to_string()),
|
||||
(false, "2".to_string()),
|
||||
(true, "c 1".to_string()),
|
||||
(false, "2".to_string())
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compile() {
|
||||
let compiled = Execute::As(
|
||||
"@ְa".to_string(),
|
||||
Box::new(Execute::If(
|
||||
"block ~ ~-1 ~ minecraft:stone".into(),
|
||||
Box::new(Execute::Run(Box::new("say hi".into()))),
|
||||
None,
|
||||
)),
|
||||
)
|
||||
.compile(
|
||||
&CompileOptions::default(),
|
||||
&MutCompilerState::default(),
|
||||
&FunctionCompilerState::default(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
compiled,
|
||||
vec!["execute as @ְa if block ~ ~-1 ~ minecraft:stone run say hi".to_string()]
|
||||
);
|
||||
|
||||
let direct = Execute::Run(Box::new("say direct".into())).compile(
|
||||
&CompileOptions::default(),
|
||||
&MutCompilerState::default(),
|
||||
&FunctionCompilerState::default(),
|
||||
);
|
||||
|
||||
assert_eq!(direct, vec!["say direct".to_string()]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@ impl Command {
|
|||
#[must_use]
|
||||
fn get_count(&self, options: &CompileOptions) -> usize {
|
||||
match self {
|
||||
// TODO: change comment to compile to `1`, make sure nothing breaks
|
||||
Self::Comment(_) => 0,
|
||||
Self::Debug(_) => usize::from(options.debug),
|
||||
Self::Raw(cmd) => cmd.split('\n').count(),
|
||||
|
@ -266,3 +267,60 @@ fn validate_raw_cmd(cmd: &str, pack_formats: &RangeInclusive<u8>) -> bool {
|
|||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Mutex;
|
||||
|
||||
use crate::util::compile::CompilerState;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_raw() {
|
||||
let command_a = Command::Raw("say Hello, world!".to_string());
|
||||
let command_b = Command::raw("say foo bar");
|
||||
|
||||
let options = &CompileOptions::default();
|
||||
let global_state = &Mutex::new(CompilerState::default());
|
||||
let function_state = &FunctionCompilerState::default();
|
||||
|
||||
assert_eq!(
|
||||
command_a.compile(options, global_state, function_state),
|
||||
vec!["say Hello, world!".to_string()]
|
||||
);
|
||||
assert_eq!(command_a.get_count(options), 1);
|
||||
assert_eq!(
|
||||
command_b.compile(options, global_state, function_state),
|
||||
vec!["say foo bar".to_string()]
|
||||
);
|
||||
assert_eq!(command_b.get_count(options), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_comment() {
|
||||
let comment = Command::Comment("this is a comment".to_string());
|
||||
|
||||
let options = &CompileOptions::default();
|
||||
let global_state = &Mutex::new(CompilerState::default());
|
||||
let function_state = &FunctionCompilerState::default();
|
||||
|
||||
assert_eq!(
|
||||
comment.compile(options, global_state, function_state),
|
||||
vec!["#this is a comment".to_string()]
|
||||
);
|
||||
assert_eq!(comment.get_count(options), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate() {
|
||||
let tag = Command::raw("tag @s add foo");
|
||||
|
||||
assert!(tag.validate(&(6..=9)));
|
||||
assert!(!tag.validate(&(2..=5)));
|
||||
|
||||
let kill = Command::raw("kill @p");
|
||||
|
||||
assert!(kill.validate(&(2..=40)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,3 +74,33 @@ impl Function {
|
|||
self.commands.iter().all(|c| c.validate(pack_formats))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::util::compile::CompilerState;
|
||||
use std::sync::Mutex;
|
||||
|
||||
#[test]
|
||||
fn test_function() {
|
||||
let mut function = Function::new("namespace", "name");
|
||||
|
||||
assert_eq!(function.get_commands().len(), 0);
|
||||
|
||||
function.add_command(Command::raw("say Hello, world!"));
|
||||
|
||||
assert_eq!(function.get_commands().len(), 1);
|
||||
|
||||
let options = &CompileOptions::default();
|
||||
let global_state = &Mutex::new(CompilerState::default());
|
||||
let function_state = &FunctionCompilerState::default();
|
||||
|
||||
let compiled = function.compile(options, global_state, function_state);
|
||||
|
||||
assert!(matches!(
|
||||
compiled,
|
||||
VFile::Text(content) if content == "say Hello, world!"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -158,3 +158,56 @@ fn generate_mcmeta(dp: &Datapack, _options: &CompileOptions, _state: &MutCompile
|
|||
|
||||
VFile::Text(content.to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_datapack() {
|
||||
let template_dir = tempfile::tempdir().expect("error creating tempdir");
|
||||
|
||||
let mut dp = Datapack::new(Datapack::LATEST_FORMAT)
|
||||
.with_description("My datapack")
|
||||
.with_template_folder(template_dir.path())
|
||||
.expect("error reading template folder");
|
||||
|
||||
assert_eq!(dp.namespaces.len(), 0);
|
||||
|
||||
let _ = dp.namespace_mut("foo");
|
||||
assert_eq!(dp.namespaces.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_mcmeta() {
|
||||
let dp = &Datapack::new(Datapack::LATEST_FORMAT).with_description("foo");
|
||||
let state = Mutex::new(CompilerState::default());
|
||||
let mcmeta = generate_mcmeta(dp, &CompileOptions::default(), &state);
|
||||
|
||||
let json = if let VFile::Text(text) = mcmeta {
|
||||
serde_json::from_str::<serde_json::Value>(&text).unwrap()
|
||||
} else {
|
||||
panic!("mcmeta should be text not binary")
|
||||
};
|
||||
|
||||
let pack = json
|
||||
.as_object()
|
||||
.expect("mcmeta is not object")
|
||||
.get("pack")
|
||||
.expect("no pack value")
|
||||
.as_object()
|
||||
.expect("mcmeta pack is not object");
|
||||
assert_eq!(
|
||||
pack.get("description")
|
||||
.expect("no key pack.description")
|
||||
.as_str(),
|
||||
Some("foo")
|
||||
);
|
||||
assert_eq!(
|
||||
pack.get("pack_format")
|
||||
.expect("no key pack.pack_format")
|
||||
.as_u64(),
|
||||
Some(u64::from(Datapack::LATEST_FORMAT))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,13 +109,7 @@ impl Namespace {
|
|||
// compile tags
|
||||
for ((path, tag_type), tag) in &self.tags {
|
||||
let vfile = tag.compile(options, state);
|
||||
root_folder.add_file(
|
||||
&format!(
|
||||
"tags/{tag_type}/{path}.json",
|
||||
tag_type = tag_type.to_string()
|
||||
),
|
||||
vfile,
|
||||
);
|
||||
root_folder.add_file(&format!("tags/{tag_type}/{path}.json"), vfile);
|
||||
}
|
||||
|
||||
root_folder
|
||||
|
@ -129,3 +123,23 @@ impl Namespace {
|
|||
.all(|function| function.validate(pack_formats))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_namespace() {
|
||||
let mut namespace = Namespace::new("foo");
|
||||
|
||||
assert_eq!(namespace.get_name(), "foo");
|
||||
assert_eq!(namespace.get_functions().len(), 0);
|
||||
assert_eq!(namespace.get_tags().len(), 0);
|
||||
|
||||
let _ = namespace.function_mut("bar");
|
||||
assert_eq!(namespace.get_functions().len(), 1);
|
||||
|
||||
assert!(namespace.function("bar").is_some());
|
||||
assert!(namespace.function("baz").is_none());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
//! A tag for various types.
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::{
|
||||
util::compile::{CompileOptions, MutCompilerState},
|
||||
virtual_fs::VFile,
|
||||
|
@ -81,9 +83,9 @@ pub enum TagType {
|
|||
/// `Others(<registry path>)` => `data/<namespace>/tags/<registry path>`
|
||||
Others(String),
|
||||
}
|
||||
impl ToString for TagType {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
impl Display for TagType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let str = match self {
|
||||
Self::Blocks => "block".to_string(),
|
||||
Self::Fluids => "fluid".to_string(),
|
||||
Self::Items => "item".to_string(),
|
||||
|
@ -91,7 +93,8 @@ impl ToString for TagType {
|
|||
Self::GameEvents => "game_event".to_string(),
|
||||
Self::Functions => "function".to_string(),
|
||||
Self::Others(path) => path.to_string(),
|
||||
}
|
||||
};
|
||||
f.write_str(&str)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,11 +125,53 @@ impl TagValue {
|
|||
match self {
|
||||
Self::Simple(value) => serde_json::Value::String(value.clone()),
|
||||
Self::Advanced { id, required } => {
|
||||
let mut map = serde_json::Map::new();
|
||||
map.insert("id".to_string(), serde_json::Value::String(id.clone()));
|
||||
map.insert("required".to_string(), serde_json::Value::Bool(*required));
|
||||
serde_json::Value::Object(map)
|
||||
serde_json::json!({
|
||||
"id": id.clone(),
|
||||
"required": *required
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_tag() {
|
||||
let mut tag = Tag::new(false);
|
||||
assert!(!tag.get_replace());
|
||||
|
||||
tag.set_replace(true);
|
||||
assert!(tag.get_replace());
|
||||
|
||||
tag.add_value(TagValue::from("foo:bar"));
|
||||
tag.add_value(TagValue::Advanced {
|
||||
id: "bar:baz".to_string(),
|
||||
required: true,
|
||||
});
|
||||
|
||||
assert_eq!(tag.get_values().len(), 2);
|
||||
|
||||
let compiled = tag.compile(&CompileOptions::default(), &MutCompilerState::default());
|
||||
|
||||
if let VFile::Text(text) = compiled {
|
||||
let deserialized = serde_json::from_str::<serde_json::Value>(&text)
|
||||
.expect("Failed to deserialize tag");
|
||||
assert_eq!(
|
||||
deserialized,
|
||||
serde_json::json!({
|
||||
"replace": true,
|
||||
"values": [
|
||||
"foo:bar",
|
||||
{
|
||||
"id": "bar:baz",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
rustdoc::broken_intra_doc_links,
|
||||
clippy::missing_errors_doc
|
||||
)]
|
||||
#![warn(clippy::all, clippy::pedantic)]
|
||||
#![warn(clippy::all, clippy::pedantic, clippy::perf)]
|
||||
#![allow(clippy::missing_panics_doc, clippy::missing_const_for_fn)]
|
||||
|
||||
pub mod datapack;
|
||||
|
|
|
@ -40,7 +40,7 @@ pub struct CompilerState {}
|
|||
pub type MutCompilerState = Mutex<CompilerState>;
|
||||
|
||||
/// State of the compiler for each function that can change during compilation.
|
||||
#[derive(Debug, Getters)]
|
||||
#[derive(Debug, Getters, Default)]
|
||||
pub struct FunctionCompilerState {
|
||||
/// Next unique identifier.
|
||||
uid_counter: Mutex<usize>,
|
||||
|
|
|
@ -38,7 +38,7 @@ impl<T> ExtendableQueue<T> {
|
|||
|
||||
/// Get the queue.
|
||||
#[must_use]
|
||||
pub fn get(&self) -> &Arc<RwLock<VecDeque<T>>> {
|
||||
pub fn get_arc(&self) -> &Arc<RwLock<VecDeque<T>>> {
|
||||
&self.queue
|
||||
}
|
||||
|
||||
|
@ -85,3 +85,39 @@ impl<T> Iterator for ExtendableQueue<T> {
|
|||
self.queue.write().unwrap().pop_front()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_queue() {
|
||||
let mut queue = ExtendableQueue::default();
|
||||
queue.push(1);
|
||||
queue.push(2);
|
||||
queue.push(3);
|
||||
|
||||
assert_eq!(queue.len(), 3);
|
||||
|
||||
let mut count = 0;
|
||||
|
||||
while let Some(el) = queue.next() {
|
||||
count += el;
|
||||
|
||||
if el == 1 {
|
||||
queue.extend(vec![4, 5, 6]);
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(count, 21);
|
||||
assert!(queue.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from() {
|
||||
let base = vec![1, 2, 3, 4];
|
||||
let queue = ExtendableQueue::from(base.clone());
|
||||
|
||||
assert!(queue.into_iter().zip(base).all(|(a, b)| a == b));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -284,11 +284,9 @@ impl TryFrom<&Path> for VFolder {
|
|||
if let Some(name) = name {
|
||||
if path.is_dir() {
|
||||
root_vfolder.add_existing_folder(&name, Self::try_from(path.as_path())?);
|
||||
} else if path.is_file() {
|
||||
} else {
|
||||
let file = VFile::try_from(path.as_path())?;
|
||||
root_vfolder.add_file(&name, file);
|
||||
} else {
|
||||
unreachable!("Path is neither file nor directory");
|
||||
}
|
||||
} else {
|
||||
return Err(io::Error::new(
|
||||
|
@ -353,6 +351,8 @@ mod tests {
|
|||
let v_file_2 = VFile::from("baz");
|
||||
v_folder.add_file("bar/baz.txt", v_file_2);
|
||||
|
||||
v_folder.add_file("bar/foo.bin", VFile::Binary(vec![1, 2, 3, 4]));
|
||||
|
||||
assert_eq!(v_folder.get_files().len(), 1);
|
||||
assert_eq!(v_folder.get_folders().len(), 1);
|
||||
assert!(v_folder.get_file("bar/baz.txt").is_some());
|
||||
|
@ -361,5 +361,79 @@ mod tests {
|
|||
.expect("folder not found")
|
||||
.get_file("baz.txt")
|
||||
.is_some());
|
||||
|
||||
let temp = tempfile::tempdir().expect("failed to create temp dir");
|
||||
v_folder.place(temp.path()).expect("failed to place folder");
|
||||
|
||||
assert_eq!(
|
||||
fs::read_to_string(temp.path().join("foo.txt")).expect("failed to read file"),
|
||||
"foo"
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_to_string(temp.path().join("bar/baz.txt")).expect("failed to read file"),
|
||||
"baz"
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read(temp.path().join("bar/foo.bin")).expect("failed to read file"),
|
||||
vec![1, 2, 3, 4]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_flatten() {
|
||||
let mut v_folder = VFolder::new();
|
||||
v_folder.add_file("a.txt", VFile::from("a"));
|
||||
v_folder.add_file("a/b.txt", VFile::from("b"));
|
||||
v_folder.add_file("a/b/c.txt", VFile::from("c"));
|
||||
|
||||
let flattened = v_folder.flatten();
|
||||
assert_eq!(flattened.len(), 3);
|
||||
assert!(flattened.iter().any(|(path, _)| path == "a.txt"));
|
||||
assert!(flattened.iter().any(|(path, _)| path == "a/b.txt"));
|
||||
assert!(flattened.iter().any(|(path, _)| path == "a/b/c.txt"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge() {
|
||||
let mut first = VFolder::new();
|
||||
first.add_file("a.txt", VFile::from("a"));
|
||||
first.add_file("a/b.txt", VFile::from("b"));
|
||||
|
||||
let mut second = VFolder::new();
|
||||
second.add_file("a.txt", VFile::from("a2"));
|
||||
second.add_file("c.txt", VFile::from("c"));
|
||||
second.add_file("c/d.txt", VFile::from("d"));
|
||||
second.add_file("a/e.txt", VFile::from("e"));
|
||||
|
||||
let replaced = first.merge(second);
|
||||
assert_eq!(replaced.len(), 1);
|
||||
|
||||
assert!(first.get_file("a.txt").is_some());
|
||||
assert!(first.get_file("a/b.txt").is_some());
|
||||
assert!(first.get_file("c.txt").is_some());
|
||||
assert!(first.get_file("c/d.txt").is_some());
|
||||
assert!(first.get_file("a/e.txt").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_try_from() {
|
||||
let temp_dir = tempfile::tempdir().expect("failed to create temp dir");
|
||||
fs::create_dir_all(temp_dir.path().join("bar")).expect("failed to create dir");
|
||||
fs::write(temp_dir.path().join("foo.txt"), "foo").expect("failed to write file");
|
||||
fs::write(temp_dir.path().join("bar/baz.txt"), "baz").expect("failed to write file");
|
||||
|
||||
let v_folder = VFolder::try_from(temp_dir.path()).expect("failed to convert");
|
||||
assert_eq!(v_folder.get_files().len(), 1);
|
||||
assert_eq!(v_folder.get_folders().len(), 1);
|
||||
if let VFile::Binary(data) = v_folder.get_file("foo.txt").expect("file not found") {
|
||||
assert_eq!(data, b"foo");
|
||||
} else {
|
||||
panic!("File is not binary");
|
||||
}
|
||||
if let VFile::Binary(data) = v_folder.get_file("bar/baz.txt").expect("file not found") {
|
||||
assert_eq!(data, b"baz");
|
||||
} else {
|
||||
panic!("File is not binary");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue