diff --git a/.gitignore b/.gitignore index ea8c4bf..09c020b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/tests_tmp \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 63f464e..737f97d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -35,9 +35,11 @@ dependencies = [ "semver", "serde", "serde_json", + "serial_test", "simple-log", "static_vcruntime", "steamlocate", + "strip-ansi-escapes", "tokio", "winresource", ] @@ -419,12 +421,28 @@ dependencies = [ ] [[package]] -name = "futures-channel" -version = "0.3.29" +name = "futures" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -433,6 +451,17 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.30" @@ -468,6 +497,7 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -1357,6 +1387,15 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +[[package]] +name = "scc" +version = "2.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeb7ac86243095b70a7920639507b71d51a63390d1ba26c4f60a552fbb914a37" +dependencies = [ + "sdd", +] + [[package]] name = "schannel" version = "0.1.22" @@ -1372,6 +1411,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sdd" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0495e4577c672de8254beb68d01a9b62d0e8a13c099edecdbedccce3223cd29f" + [[package]] name = "security-framework" version = "2.9.2" @@ -1488,6 +1533,31 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "serial_test" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b4b487fe2acf240a021cf57c6b2b4903b1e78ca0ecd862a71b71d2a51fed77d" +dependencies = [ + "futures", + "log", + "once_cell", + "parking_lot", + "scc", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "sha2" version = "0.10.7" @@ -1570,6 +1640,15 @@ dependencies = [ "winreg", ] +[[package]] +name = "strip-ansi-escapes" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ff8ef943b384c414f54aefa961dd2bd853add74ec75e7ac74cf91dba62bcfa" +dependencies = [ + "vte", +] + [[package]] name = "subtle" version = "2.5.0" @@ -1904,6 +1983,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "vcpkg" version = "0.2.15" @@ -1916,6 +2001,26 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "vte" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197" +dependencies = [ + "utf8parse", + "vte_generate_state_changes", +] + +[[package]] +name = "vte_generate_state_changes" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e369bee1b05d510a7b4ed645f5faa90619e05437111783ea5848f28d97d3c2e" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "want" version = "0.3.1" diff --git a/Cargo.toml b/Cargo.toml index b6c842e..7fccf84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,10 @@ runas = "1.2.0" winresource = "0.1.17" static_vcruntime = "2.0" +[dev-dependencies] +strip-ansi-escapes = "0.2.0" +serial_test = "3.1.1" + [package.metadata.winresource] OriginalFilename = "alterware-launcher.exe" FileDescription = "AlterWare Launcher" diff --git a/src/main.rs b/src/main.rs index d6e0c05..0530e8e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,9 @@ mod misc; mod self_update; mod structs; +#[cfg(test)] +mod tests; + use global::*; use structs::*; diff --git a/src/misc.rs b/src/misc.rs index 4720be8..407133b 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -43,12 +43,12 @@ pub fn fatal_error(error: &str) { pub fn human_readable_bytes(bytes: u64) -> String { let mut bytes = bytes as f64; let mut i = 0; - let units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; - while bytes > 1024.0 { + const UNITS: [&str; 9] = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + while bytes >= 1024.0 { bytes /= 1024.0; i += 1; } - format!("{bytes:.2}{}", units[i]) + format!("{bytes:.2}{}", UNITS[i]) } pub fn pb_style_download(pb: &ProgressBar, state: bool) { diff --git a/src/structs.rs b/src/structs.rs index 378c863..122e95b 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -32,7 +32,7 @@ impl<'a> Game<'a> { } } -#[derive(serde::Deserialize, serde::Serialize)] +#[derive(serde::Deserialize, serde::Serialize, PartialEq, Debug, Clone)] pub struct Config { pub update_only: bool, pub skip_self_update: bool, @@ -75,7 +75,7 @@ impl PrintPrefix { } } -#[derive(serde::Deserialize, serde::Serialize, Default)] +#[derive(serde::Deserialize, serde::Serialize, Default, PartialEq, Debug, Clone)] pub struct Cache { pub iw4x_revision: String, pub hashes: HashMap, diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..3f615ff --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,114 @@ +mod config { + use crate::{config, fs, structs}; + use serial_test::serial; + use std::path::Path; + + fn setup_test_path() -> std::path::PathBuf { + let path = Path::new("tests_tmp").join("config.json"); + if path.exists() { + fs::remove_file(&path).unwrap(); + } + path + } + + #[test] + #[serial] + fn load_config() { + let path = setup_test_path(); + + let config = config::load(path.clone()); + assert_eq!(config, structs::Config::default()); + + fs::remove_file(path).unwrap(); + } + + #[test] + #[serial] + fn save_and_load_config() { + let path = setup_test_path(); + + let config = structs::Config::default(); + config::save(path.clone(), config.clone()); + let loaded_config = config::load(path.clone()); + assert_eq!(loaded_config, config); + + fs::remove_file(path).unwrap(); + } + + #[test] + #[serial] + fn save_value() { + let path = setup_test_path(); + + config::save_value(path.clone(), "update_only", true); + let loaded_config = config::load(path.clone()); + assert_eq!(loaded_config.update_only, true); + + fs::remove_file(path).unwrap(); + } +} + +mod misc { + use crate::{fs, misc, structs}; + use std::{fs::File, io::Write, path::Path}; + + #[test] + fn file_blake3() { + let path = Path::new("tests_tmp").join("blake3"); + if path.exists() { + fs::remove_file(&path).unwrap(); + } + fs::create_dir_all(path.parent().unwrap()).unwrap(); + + File::create(&path) + .unwrap() + .write_all(b"alterware") + .unwrap(); + let blake3 = misc::file_blake3(&path).unwrap(); + assert_eq!( + blake3, + "f18a70588a620f3a874120dbc2a41f49a0f44349c8a9c10c51f2f1c7bb678daa" + ); + + fs::remove_file(path).unwrap(); + } + + #[test] + fn human_readable_bytes() { + assert_eq!(misc::human_readable_bytes(0), "0.00B"); + assert_eq!(misc::human_readable_bytes(1023), "1023.00B"); + assert_eq!(misc::human_readable_bytes(1024), "1.00KB"); + assert_eq!(misc::human_readable_bytes(1099511627776), "1.00TB"); + } + + #[test] + #[cfg(unix)] + fn is_program_in_path() { + assert!(misc::is_program_in_path("ls")); + assert!(!misc::is_program_in_path("nonexistent")); + } + + #[test] + fn cache_operations() { + let path = Path::new("tests_tmp"); + fs::create_dir_all(path).unwrap(); + let cache_file = path.join("awcache.json"); + + // Test initial empty cache + let initial_cache = misc::get_cache(path); + assert_eq!(initial_cache, structs::Cache::default()); + + // Test saving and loading cache + let test_cache = structs::Cache { + iw4x_revision: "r1234".to_string(), + hashes: [("test".to_string(), "hash".to_string())] + .into_iter() + .collect(), + }; + misc::save_cache(path, test_cache.clone()); + let loaded_cache = misc::get_cache(path); + assert_eq!(loaded_cache, test_cache); + + fs::remove_file(&cache_file).unwrap(); + } +}