Add a couple of parameterised fixture tests

This commit is contained in:
Oliver Hamlet
2025-03-25 20:56:07 +00:00
parent 649560fa09
commit ce1742ef54
7 changed files with 569 additions and 1 deletions
+1
View File
@@ -1 +1,2 @@
/target
/testing-plugins
Generated
+221
View File
@@ -35,6 +35,12 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236"
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "bit-set"
version = "0.8.0"
@@ -65,6 +71,12 @@ dependencies = [
"generic-array",
]
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cfg-if"
version = "1.0.0"
@@ -271,6 +283,49 @@ version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
[[package]]
name = "futures-core"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-macro"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-task"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
[[package]]
name = "futures-timer"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
[[package]]
name = "futures-util"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-core",
"futures-macro",
"futures-task",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "generic-array"
version = "0.14.7"
@@ -304,6 +359,12 @@ dependencies = [
"windows-targets",
]
[[package]]
name = "glob"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
[[package]]
name = "hashbrown"
version = "0.14.5"
@@ -384,6 +445,8 @@ dependencies = [
"loot-condition-interpreter",
"petgraph",
"rayon",
"rstest",
"rstest_reuse",
"saphyr",
"saphyr-parser",
"tempfile",
@@ -558,6 +621,36 @@ dependencies = [
"indexmap",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "ppv-lite86"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro-crate"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b"
dependencies = [
"toml_edit",
]
[[package]]
name = "proc-macro2"
version = "1.0.93"
@@ -576,6 +669,36 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom 0.2.15",
]
[[package]]
name = "rayon"
version = "1.10.0"
@@ -636,6 +759,53 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "relative-path"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2"
[[package]]
name = "rstest"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fc39292f8613e913f7df8fa892b8944ceb47c247b78e1b1ae2f09e019be789d"
dependencies = [
"futures-timer",
"futures-util",
"rstest_macros",
"rustc_version",
]
[[package]]
name = "rstest_macros"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f168d99749d307be9de54d23fd226628d99768225ef08f6ffb52e0182a27746"
dependencies = [
"cfg-if",
"glob",
"proc-macro-crate",
"proc-macro2",
"quote",
"regex",
"relative-path",
"rustc_version",
"syn",
"unicode-ident",
]
[[package]]
name = "rstest_reuse"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3a8fb4672e840a587a66fc577a5491375df51ddb88f2a2c2a792598c326fe14"
dependencies = [
"quote",
"rand",
"syn",
]
[[package]]
name = "rust-ini"
version = "0.21.1"
@@ -648,6 +818,15 @@ dependencies = [
"unicase",
]
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]]
name = "rustix"
version = "0.38.44"
@@ -683,6 +862,12 @@ dependencies = [
"hashlink",
]
[[package]]
name = "semver"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
[[package]]
name = "sha2"
version = "0.10.8"
@@ -694,6 +879,15 @@ dependencies = [
"digest",
]
[[package]]
name = "slab"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
dependencies = [
"autocfg",
]
[[package]]
name = "syn"
version = "2.0.98"
@@ -768,6 +962,23 @@ dependencies = [
"crunchy",
]
[[package]]
name = "toml_datetime"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
[[package]]
name = "toml_edit"
version = "0.22.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
dependencies = [
"indexmap",
"toml_datetime",
"winnow",
]
[[package]]
name = "trim-in-place"
version = "0.1.7"
@@ -1015,6 +1226,15 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1"
dependencies = [
"memchr",
]
[[package]]
name = "wit-bindgen-rt"
version = "0.33.0"
@@ -1030,6 +1250,7 @@ version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"byteorder",
"zerocopy-derive",
]
+2
View File
@@ -18,4 +18,6 @@ unicase = "2.8.1"
rayon = "1.10.0"
[dev-dependencies]
rstest = "0.25.0"
rstest_reuse = "0.7.0"
tempfile = "3.17.1"
+1 -1
View File
@@ -35,7 +35,7 @@ $env:LIBLOOT_REVISION = git rev-parse --short HEAD
cargo build --release
```
There aren't many tests, but those that exist can be run using:
There aren't many tests, but those that exist can be run by first extracting the [testing-plugins](https://github.com/Ortham/testing-plugins) archive to this readme's directory (so that there's a `testing-plugins` directory there), then running:
```
cargo test
+57
View File
@@ -753,3 +753,60 @@ impl GameCache {
self.archive_paths.iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
use rstest_reuse::{apply, template};
use crate::tests::Fixture;
#[template]
#[rstest]
fn all_game_types(
#[values(
GameType::TES4,
GameType::TES5,
GameType::FO3,
GameType::FONV,
GameType::FO4,
GameType::TES5SE,
GameType::FO4VR,
GameType::TES5VR,
GameType::TES3,
GameType::Starfield,
GameType::OpenMW
)]
game_type: GameType,
) {
}
mod new {
use super::*;
#[apply(all_game_types)]
fn should_succeed_if_given_valid_game_path(game_type: GameType) {
let fixture = Fixture::new(game_type);
let game = Game::new(fixture.game_type, &fixture.game_path);
assert!(game.is_ok());
}
}
mod with_local_path {
use super::*;
#[apply(all_game_types)]
fn should_succeed_if_given_valid_paths(game_type: GameType) {
let fixture = Fixture::new(game_type);
let game =
Game::with_local_path(fixture.game_type, &fixture.game_path, &fixture.local_path);
assert!(game.is_ok());
}
}
}
+2
View File
@@ -6,6 +6,8 @@ mod logging;
pub mod metadata;
mod plugin;
mod sorting;
#[cfg(test)]
mod tests;
mod version;
pub use database::Database;
+285
View File
@@ -0,0 +1,285 @@
use std::{
fs::{File, copy, create_dir_all},
path::{Path, PathBuf, absolute},
time::{Duration, SystemTime},
};
use crate::GameType;
use tempfile::TempDir;
const BLANK_ESM: &str = "Blank.esm";
const BLANK_DIFFERENT_ESM: &str = "Blank - Different.esm";
const BLANK_MASTER_DEPENDENT_ESM: &str = "Blank - Master Dependent.esm";
const BLANK_DIFFERENT_MASTER_DEPENDENT_ESM: &str = "Blank - Different Master Dependent.esm";
const BLANK_ESP: &str = "Blank.esp";
const BLANK_DIFFERENT_ESP: &str = "Blank - Different.esp";
const BLANK_MASTER_DEPENDENT_ESP: &str = "Blank - Master Dependent.esp";
const BLANK_DIFFERENT_MASTER_DEPENDENT_ESP: &str = "Blank - Different Master Dependent.esp";
const BLANK_PLUGIN_DEPENDENT_ESP: &str = "Blank - Plugin Dependent.esp";
const BLANK_DIFFERENT_PLUGIN_DEPENDENT_ESP: &str = "Blank - Different Plugin Dependent.esp";
const BLANK_FULL_ESM: &str = "Blank.full.esm";
const BLANK_MEDIUM_ESM: &str = "Blank.medium.esm";
const BLANK_ESL: &str = "Blank.esl";
const NON_PLUGIN_FILE: &str = "NotAPlugin.esm";
fn source_plugins_path(game_type: GameType) -> PathBuf {
match game_type {
GameType::TES3 | GameType::OpenMW => absolute("./testing-plugins/Morrowind/Data Files"),
GameType::TES4 => absolute("./testing-plugins/Oblivion/Data"),
GameType::Starfield => absolute("./testing-plugins/Starfield/Data"),
GameType::FO3 | GameType::FONV | GameType::TES5 => {
absolute("./testing-plugins/Skyrim/Data")
}
_ => absolute("./testing-plugins/SkyrimSE/Data"),
}
.unwrap()
}
fn master_file(game_type: GameType) -> &'static str {
match game_type {
GameType::TES3 | GameType::OpenMW => "Morrowind.esm",
GameType::TES4 => "Oblivion.esm",
GameType::TES5 | GameType::TES5SE | GameType::TES5VR => "Skyrim.esm",
GameType::FO3 => "Fallout3.esm",
GameType::FONV => "FalloutNV.esm",
GameType::FO4 | GameType::FO4VR => "Fallout4.esm",
GameType::Starfield => "Starfield.esm",
}
}
fn copy_file(source_dir: &Path, dest_dir: &Path, filename: &str) {
copy(source_dir.join(filename), dest_dir.join(filename)).unwrap();
}
fn touch(file_path: &Path) {
std::fs::File::create(file_path).unwrap();
}
fn supports_light_plugins(game_type: GameType) -> bool {
matches!(
game_type,
GameType::TES5SE | GameType::TES5VR | GameType::FO4 | GameType::FO4VR | GameType::Starfield
)
}
fn is_load_order_timestamp_based(game_type: GameType) -> bool {
matches!(
game_type,
GameType::TES3 | GameType::TES4 | GameType::FO3 | GameType::FONV
)
}
fn initial_load_order(game_type: GameType) -> Vec<(&'static str, bool)> {
if game_type == GameType::Starfield {
vec![
(master_file(game_type), true),
(BLANK_ESM, true),
(BLANK_DIFFERENT_ESM, false),
(BLANK_FULL_ESM, false),
(BLANK_MASTER_DEPENDENT_ESM, false),
(BLANK_MEDIUM_ESM, false),
(BLANK_ESL, false),
(BLANK_ESP, false),
(BLANK_DIFFERENT_ESP, false),
(BLANK_MASTER_DEPENDENT_ESP, false),
]
} else {
let mut load_order = vec![
(master_file(game_type), true),
(BLANK_ESM, true),
(BLANK_DIFFERENT_ESM, false),
(BLANK_MASTER_DEPENDENT_ESM, false),
(BLANK_DIFFERENT_MASTER_DEPENDENT_ESM, false),
(BLANK_ESP, false),
(BLANK_DIFFERENT_ESP, false),
(BLANK_MASTER_DEPENDENT_ESP, false),
(BLANK_DIFFERENT_MASTER_DEPENDENT_ESP, true),
(BLANK_PLUGIN_DEPENDENT_ESP, false),
(BLANK_DIFFERENT_PLUGIN_DEPENDENT_ESP, false),
];
if supports_light_plugins(game_type) {
load_order.insert(5, (BLANK_ESL, false));
}
load_order
}
}
fn set_load_order(
game_type: GameType,
data_path: &Path,
local_path: &Path,
load_order: &[(&'static str, bool)],
) {
use std::io::Write;
match game_type {
GameType::TES3 => {}
GameType::OpenMW => {}
_ => {
let mut file = File::create(local_path.join("Plugins.txt")).unwrap();
for (plugin, is_active) in load_order {
if supports_light_plugins(game_type) {
if *is_active {
write!(file, "*").unwrap();
}
} else if !is_active {
continue;
}
writeln!(file, "{plugin}").unwrap();
}
}
}
if is_load_order_timestamp_based(game_type) {
let mut mod_time = SystemTime::now();
for (plugin, _) in load_order {
let ghosted_path = data_path.join(plugin.to_string() + ".ghost");
let file = if ghosted_path.exists() {
File::options().write(true).open(ghosted_path)
} else {
File::options().write(true).open(data_path.join(plugin))
};
file.unwrap().set_modified(mod_time).unwrap();
mod_time += Duration::from_secs(60);
}
} else if game_type == GameType::TES5 {
let mut file = File::create(local_path.join("loadorder.txt")).unwrap();
for (plugin, _) in load_order {
writeln!(file, "{plugin}").unwrap();
}
}
}
pub struct Fixture {
_temp_dir: TempDir,
pub game_type: GameType,
pub game_path: PathBuf,
pub local_path: PathBuf,
}
impl Fixture {
pub fn new(game_type: GameType) -> Self {
let temp_dir = tempfile::Builder::new()
.prefix("libloot-t\u{00E9}st-")
.tempdir()
.unwrap();
let root_path = temp_dir.path();
let game_path = root_path.join("games/game");
let local_path = root_path.join("local/game");
let data_path = match game_type {
GameType::OpenMW => game_path.join("resources/vfs"),
GameType::TES3 => game_path.join("Data Files"),
_ => game_path.join("Data"),
};
create_dir_all(&data_path).unwrap();
create_dir_all(&local_path).unwrap();
let source_plugins_path = source_plugins_path(game_type);
if game_type == GameType::Starfield {
copy_file(&source_plugins_path, &data_path, BLANK_FULL_ESM);
copy_file(&source_plugins_path, &data_path, BLANK_MEDIUM_ESM);
copy(
source_plugins_path.join(BLANK_FULL_ESM),
data_path.join(BLANK_ESM),
)
.unwrap();
copy(
source_plugins_path.join(BLANK_FULL_ESM),
data_path.join(BLANK_DIFFERENT_ESM),
)
.unwrap();
copy(
source_plugins_path.join("Blank - Override.full.esm"),
data_path.join(BLANK_MASTER_DEPENDENT_ESM),
)
.unwrap();
copy_file(&source_plugins_path, &data_path, BLANK_ESP);
copy(
source_plugins_path.join(BLANK_ESP),
data_path.join(BLANK_DIFFERENT_ESP),
)
.unwrap();
copy(
source_plugins_path.join("Blank - Override.esp"),
data_path.join(BLANK_MASTER_DEPENDENT_ESP),
)
.unwrap();
} else {
copy_file(&source_plugins_path, &data_path, BLANK_ESM);
copy_file(&source_plugins_path, &data_path, BLANK_DIFFERENT_ESM);
copy_file(&source_plugins_path, &data_path, BLANK_MASTER_DEPENDENT_ESM);
copy_file(
&source_plugins_path,
&data_path,
BLANK_DIFFERENT_MASTER_DEPENDENT_ESM,
);
copy_file(&source_plugins_path, &data_path, BLANK_ESP);
copy_file(&source_plugins_path, &data_path, BLANK_DIFFERENT_ESP);
copy_file(&source_plugins_path, &data_path, BLANK_MASTER_DEPENDENT_ESP);
copy_file(
&source_plugins_path,
&data_path,
BLANK_DIFFERENT_MASTER_DEPENDENT_ESP,
);
copy_file(&source_plugins_path, &data_path, BLANK_PLUGIN_DEPENDENT_ESP);
copy_file(
&source_plugins_path,
&data_path,
BLANK_DIFFERENT_PLUGIN_DEPENDENT_ESP,
);
}
if supports_light_plugins(game_type) {
if game_type == GameType::Starfield {
copy(
source_plugins_path.join("Blank.small.esm"),
data_path.join(BLANK_ESL),
)
.unwrap();
} else {
copy_file(&source_plugins_path, &data_path, BLANK_ESL);
}
}
let master_file = master_file(game_type);
copy(data_path.join(BLANK_ESM), data_path.join(master_file)).unwrap();
set_load_order(
game_type,
&data_path,
&local_path,
&initial_load_order(game_type),
);
if game_type == GameType::OpenMW {
touch(&game_path.join("openmw.cfg"));
} else {
std::fs::rename(
data_path.join(BLANK_MASTER_DEPENDENT_ESM),
data_path.join(BLANK_MASTER_DEPENDENT_ESM.to_string() + ".ghost"),
)
.unwrap();
}
std::fs::write(
data_path.join(NON_PLUGIN_FILE),
"This isn't a valid plugin file.",
)
.unwrap();
Self {
_temp_dir: temp_dir,
game_type,
game_path,
local_path,
}
}
}