mirror of
https://github.com/tribufu/rust-gamedig
synced 2026-06-01 09:42:41 +00:00
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
This commit is contained in:
parent
88bf996a5e
commit
b248a7661e
7 changed files with 144 additions and 113 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["crates/cli", "crates/lib"]
|
members = ["crates/cli", "crates/lib", "crates/id-tests"]
|
||||||
|
|
||||||
# Edition 2021, uses resolver = 2
|
# Edition 2021, uses resolver = 2
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
|
||||||
29
crates/id-tests/Cargo.toml
Normal file
29
crates/id-tests/Cargo.toml
Normal file
|
|
@ -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"] }
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
#![cfg(all(test, feature = "game_defs"))]
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use std::{collections::HashMap, fs, io::Read};
|
mod utils;
|
||||||
|
use utils::{extract_bracketed_suffix, split_on_switch_between_alpha_numeric};
|
||||||
use gamedig::GAMES;
|
|
||||||
|
|
||||||
use utils::*;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub enum IDRule {
|
pub enum IDRule {
|
||||||
|
|
@ -384,42 +381,9 @@ pub fn test_game_name_rules<'a, I: Iterator<Item = (&'a str, &'a str)>>(games: I
|
||||||
wrong_ids
|
wrong_ids
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
pub fn test_single_game_rule(id: &str, name: &str) -> Vec<IDFail> { test_game_name_rules(std::iter::once((id, name))) }
|
||||||
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<IDFail> { test_game_name_rules(std::iter::once((id, name))) }
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
mod id_tests {
|
mod id_tests {
|
||||||
use super::{test_game_name_rules, test_single_game_rule};
|
use super::{test_game_name_rules, test_single_game_rule};
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -485,72 +449,3 @@ mod id_tests {
|
||||||
assert!(test_single_game_rule("jc3m", "Just Cause 3 - Multiplayer").is_empty());
|
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<String> {
|
|
||||||
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"))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
31
crates/id-tests/src/main.rs
Normal file
31
crates/id-tests/src/main.rs
Normal file
|
|
@ -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<String, Game>;
|
||||||
|
|
||||||
|
#[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());
|
||||||
|
}
|
||||||
66
crates/id-tests/src/utils.rs
Normal file
66
crates/id-tests/src/utils.rs
Normal file
|
|
@ -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<String> {
|
||||||
|
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"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -38,8 +38,7 @@ phf = { version = "0.11", optional = true, features = ["macros"] }
|
||||||
clap = { version = "4.1.11", optional = true, features = ["derive"] }
|
clap = { version = "4.1.11", optional = true, features = ["derive"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
number_to_words = "0.1"
|
gamedig-id-tests = { path = "../id-tests", no-default-features = true }
|
||||||
roman_numeral = "0.1"
|
|
||||||
|
|
||||||
# Examples
|
# Examples
|
||||||
[[example]]
|
[[example]]
|
||||||
|
|
|
||||||
11
crates/lib/tests/game_ids.rs
Normal file
11
crates/lib/tests/game_ids.rs
Normal file
|
|
@ -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());
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue