From b248a7661e7637908ea2e261c41f4d4959eee882 Mon Sep 17 00:00:00 2001 From: Tom <25043847+Douile@users.noreply.github.com> Date: Wed, 10 Jan 2024 23:31:02 +0000 Subject: [PATCH] feat: Move ID tests into their own crate with a CLI (#177) * fix: ID tests not in correct directory * refactor: Move game-id test logic into its own crate * id-tests: Add CLI that reads JSON input * id-tests: Update crate docs * Remove node ID test * id-tests: Don't try to parse unneeded info * id-tests: Enable cli feature by default --- Cargo.toml | 2 +- crates/id-tests/Cargo.toml | 29 +++++ .../game_ids.rs => crates/id-tests/src/lib.rs | 115 +----------------- crates/id-tests/src/main.rs | 31 +++++ crates/id-tests/src/utils.rs | 66 ++++++++++ crates/lib/Cargo.toml | 3 +- crates/lib/tests/game_ids.rs | 11 ++ 7 files changed, 144 insertions(+), 113 deletions(-) create mode 100644 crates/id-tests/Cargo.toml rename tests/game_ids.rs => crates/id-tests/src/lib.rs (82%) create mode 100644 crates/id-tests/src/main.rs create mode 100644 crates/id-tests/src/utils.rs create mode 100644 crates/lib/tests/game_ids.rs diff --git a/Cargo.toml b/Cargo.toml index 70ccb3d..76a0c96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["crates/cli", "crates/lib"] +members = ["crates/cli", "crates/lib", "crates/id-tests"] # Edition 2021, uses resolver = 2 resolver = "2" diff --git a/crates/id-tests/Cargo.toml b/crates/id-tests/Cargo.toml new file mode 100644 index 0000000..22b9a90 --- /dev/null +++ b/crates/id-tests/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "gamedig-id-tests" +version = "0.0.1" +edition = "2021" +authors = [ + "rust-GameDig contributors [https://github.com/gamedig/rust-gamedig/contributors]", + "node-GameDig contributors [https://github.com/gamedig/node-gamedig/contributors]", +] +license = "MIT" +description = "Test if IDs match the gamedig rules" +homepage = "https://github.com/gamedig/rust-gamedig/CONTRIBUTING.md#naming" +repository = "https://github.com/gamedig/rust-gamedig" +readme = "README.md" +rust-version = "1.65.0" + +[features] +cli = ["dep:serde_json", "dep:serde"] +default = ["cli"] + +[[bin]] +name = "gamedig-id-tests" +required-features = ["cli"] + +[dependencies] +number_to_words = "0.1" +roman_numeral = "0.1" + +serde_json = { version = "1", optional = true } +serde = { version = "1", optional = true, features = ["derive"] } \ No newline at end of file diff --git a/tests/game_ids.rs b/crates/id-tests/src/lib.rs similarity index 82% rename from tests/game_ids.rs rename to crates/id-tests/src/lib.rs index 75f170f..6cd7d31 100644 --- a/tests/game_ids.rs +++ b/crates/id-tests/src/lib.rs @@ -1,10 +1,7 @@ -#![cfg(all(test, feature = "game_defs"))] +use std::collections::HashMap; -use std::{collections::HashMap, fs, io::Read}; - -use gamedig::GAMES; - -use utils::*; +mod utils; +use utils::{extract_bracketed_suffix, split_on_switch_between_alpha_numeric}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum IDRule { @@ -384,42 +381,9 @@ pub fn test_game_name_rules<'a, I: Iterator>(games: I wrong_ids } -#[test] -fn check_definitions_match_name_rules() { - let wrong = test_game_name_rules(GAMES.entries().map(|(id, game)| (id.to_owned(), game.name))); - assert!(wrong.is_empty()); -} - -#[test] -#[ignore = "Don't test node by default"] -fn check_node_definitions_match_name_rules() { - let mut file = fs::OpenOptions::new() - .read(true) - .open("./node-gamedig/games.txt") - .unwrap(); - - let mut text = String::new(); - file.read_to_string(&mut text).unwrap(); - - let games = text - .split('\n') - .map(|line| line.trim()) - .filter(|line| !line.starts_with('#') && !line.is_empty()) - .filter_map(|line| { - let parts: Vec<_> = line.splitn(3, '|').collect(); - if parts.len() > 1 { - Some((parts[0].split(',').next().unwrap(), parts[1])) - } else { - None - } - }); - - let wrong = test_game_name_rules(games); - assert!(wrong.is_empty()); -} - -fn test_single_game_rule(id: &str, name: &str) -> Vec { test_game_name_rules(std::iter::once((id, name))) } +pub fn test_single_game_rule(id: &str, name: &str) -> Vec { test_game_name_rules(std::iter::once((id, name))) } +#[cfg(test)] mod id_tests { use super::{test_game_name_rules, test_single_game_rule}; #[test] @@ -485,72 +449,3 @@ mod id_tests { assert!(test_single_game_rule("jc3m", "Just Cause 3 - Multiplayer").is_empty()); } } - -mod utils { - /// Split a str when characters swap between being digits and not digits. - pub fn split_on_switch_between_alpha_numeric(text: &str) -> Vec { - if text.is_empty() { - return vec![]; - } - - let mut parts = Vec::with_capacity(text.len()); - let mut current = Vec::with_capacity(text.len()); - - let mut iter = text.chars(); - let c = iter.next().unwrap(); - let mut last_was_numeric = c.is_ascii_digit(); - current.push(c); - - for c in iter { - if c.is_ascii_digit() == last_was_numeric { - current.push(c); - } else { - parts.push(current.iter().collect()); - current.clear(); - current.push(c); - last_was_numeric = !last_was_numeric; - } - } - - parts.push(current.into_iter().collect()); - - parts - } - - #[test] - fn split_correctly() { - assert_eq!( - split_on_switch_between_alpha_numeric("2D45A"), - &["2", "D", "45", "A"] - ); - } - - #[test] - fn split_symbol_broken_numbers() { - let game_name = super::extract_game_parts_from_name("Darkest Hour: Europe '44-'45"); - assert_eq!(game_name.words, &["Darkest", "Hour", "Europe", "4445"]); - } - - /// Extract parts at end of string enclosed in brackets. - pub fn extract_bracketed_suffix(text: &str) -> (&str, Option<&str>) { - if let Some(text) = text.strip_suffix(')') { - if let Some((text, extra)) = text.rsplit_once('(') { - return (text, Some(extra)); - } - } - - (text, None) - } - - #[test] - fn extract_brackets_correctly() { - assert_eq!( - extract_bracketed_suffix("no brackets here"), - ("no brackets here", None) - ); - assert_eq!( - extract_bracketed_suffix("Game name (with protocol here)"), - ("Game name ", Some("with protocol here")) - ); - } -} diff --git a/crates/id-tests/src/main.rs b/crates/id-tests/src/main.rs new file mode 100644 index 0000000..8dfadaa --- /dev/null +++ b/crates/id-tests/src/main.rs @@ -0,0 +1,31 @@ +#![cfg(feature = "cli")] + +use std::collections::HashMap; + +/// Format for input games (the same as used in node-gamedig/lib/games.js). +type GamesInput = HashMap; + +#[derive(Debug, Clone, PartialEq, serde::Deserialize)] +struct Game { + name: String, +} + +use gamedig_id_tests::test_game_name_rules; + +fn main() { + let games: GamesInput = if let Some(file) = std::env::args_os().skip(1).next() { + let file = std::fs::OpenOptions::new().read(true).open(file).unwrap(); + + serde_json::from_reader(file).unwrap() + } else { + serde_json::from_reader(std::io::stdin().lock()).unwrap() + }; + + let failed = test_game_name_rules( + games + .iter() + .map(|(key, game)| (key.as_str(), game.name.as_str())), + ); + + assert!(failed.is_empty()); +} diff --git a/crates/id-tests/src/utils.rs b/crates/id-tests/src/utils.rs new file mode 100644 index 0000000..71dfe71 --- /dev/null +++ b/crates/id-tests/src/utils.rs @@ -0,0 +1,66 @@ +/// Split a str when characters swap between being digits and not digits. +pub fn split_on_switch_between_alpha_numeric(text: &str) -> Vec { + if text.is_empty() { + return vec![]; + } + + let mut parts = Vec::with_capacity(text.len()); + let mut current = Vec::with_capacity(text.len()); + + let mut iter = text.chars(); + let c = iter.next().unwrap(); + let mut last_was_numeric = c.is_ascii_digit(); + current.push(c); + + for c in iter { + if c.is_ascii_digit() == last_was_numeric { + current.push(c); + } else { + parts.push(current.iter().collect()); + current.clear(); + current.push(c); + last_was_numeric = !last_was_numeric; + } + } + + parts.push(current.into_iter().collect()); + + parts +} + +#[test] +fn split_correctly() { + assert_eq!( + split_on_switch_between_alpha_numeric("2D45A"), + &["2", "D", "45", "A"] + ); +} + +#[test] +fn split_symbol_broken_numbers() { + let game_name = super::extract_game_parts_from_name("Darkest Hour: Europe '44-'45"); + assert_eq!(game_name.words, &["Darkest", "Hour", "Europe", "4445"]); +} + +/// Extract parts at end of string enclosed in brackets. +pub fn extract_bracketed_suffix(text: &str) -> (&str, Option<&str>) { + if let Some(text) = text.strip_suffix(')') { + if let Some((text, extra)) = text.rsplit_once('(') { + return (text, Some(extra)); + } + } + + (text, None) +} + +#[test] +fn extract_brackets_correctly() { + assert_eq!( + extract_bracketed_suffix("no brackets here"), + ("no brackets here", None) + ); + assert_eq!( + extract_bracketed_suffix("Game name (with protocol here)"), + ("Game name ", Some("with protocol here")) + ); +} diff --git a/crates/lib/Cargo.toml b/crates/lib/Cargo.toml index 59cdbc8..563aab8 100644 --- a/crates/lib/Cargo.toml +++ b/crates/lib/Cargo.toml @@ -38,8 +38,7 @@ phf = { version = "0.11", optional = true, features = ["macros"] } clap = { version = "4.1.11", optional = true, features = ["derive"] } [dev-dependencies] -number_to_words = "0.1" -roman_numeral = "0.1" +gamedig-id-tests = { path = "../id-tests", no-default-features = true } # Examples [[example]] diff --git a/crates/lib/tests/game_ids.rs b/crates/lib/tests/game_ids.rs new file mode 100644 index 0000000..d7925a9 --- /dev/null +++ b/crates/lib/tests/game_ids.rs @@ -0,0 +1,11 @@ +#![cfg(all(test, feature = "game_defs"))] + +use gamedig::GAMES; + +use gamedig_id_tests::test_game_name_rules; + +#[test] +fn check_definitions_match_name_rules() { + let wrong = test_game_name_rules(GAMES.entries().map(|(id, game)| (id.to_owned(), game.name))); + assert!(wrong.is_empty()); +}