tests.rs 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. use std::{env, panic};
  2. use std::collections::HashMap;
  3. use std::fmt::Debug;
  4. use std::panic::{RefUnwindSafe, UnwindSafe};
  5. use std::path::Path;
  6. use std::sync::Mutex;
  7. use config::{ConfigError, Map, Value, ValueKind};
  8. use glob::PatternError;
  9. use lazy_static::lazy_static;
  10. use log::{Level, LevelFilter, Log, Metadata, Record};
  11. use rand::prelude::ThreadRng;
  12. use rand::Rng;
  13. use rstest::rstest;
  14. use tempfile::TempDir;
  15. use crate::*;
  16. use crate::tiles::{Connectors, Tile, TileConnection};
  17. use crate::tiles::tile_list::TileList;
  18. use crate::tiling::{Direction, MissingTilePolicy, VacancyPolicy};
  19. lazy_static! {
  20. pub(crate) static ref RANDOM_USIZE: usize = rand_usize(0, 3);
  21. pub(crate) static ref RANDOM_F64: f64 = rand_float();
  22. pub(crate) static ref RANDOM_TILES: Vec<Tile> = (0..10).map(
  23. |_| random_tile_text(random_connectors())
  24. ).collect();
  25. pub(crate) static ref RANDOM_STRINGS: Vec<String> =
  26. (0..10).map(|x| rand_string(rand_usize(x * 5, x * 5 + 5))).collect();
  27. pub(crate) static ref TILE_CONNECTION_TILES: Vec<Tile> = vec![
  28. random_tile_text(Connectors { north: 1, east: 1, south: 1, west: 1}),
  29. random_tile_text(Connectors { north: 1, east: 2, south: 2, west: 1}),
  30. random_tile_text(Connectors { north: 1, east: 1, south: 2, west: 1}),
  31. random_tile_text(Connectors { north: 2, east: 3, south: 1, west: 4}),
  32. random_tile_text(Connectors { north: 3, east: 3, south: 2, west: 4}),
  33. ];
  34. // Taken from https://stackoverflow.com/a/67433684
  35. pub(crate) static ref SERIAL_TEST: Mutex<()> = Mutex::default();
  36. pub(crate) static ref TEMPORARY_DIRECTORY: TempDir =
  37. tempfile::tempdir().expect("the temporary directory could not be created");
  38. pub(crate) static ref TEST_VALUE_KIND_VECTOR: Vec<ValueKind> = vec![
  39. ValueKind::Table(Map::new()),
  40. ValueKind::String("öß".to_string()),
  41. ValueKind::Array(vec![Value::new(Some(&RANDOM_STRINGS[6]), ValueKind::Nil)]),
  42. ValueKind::Boolean(false)
  43. ];
  44. pub(crate) static ref TEST_VALUE_VECTOR: Vec<Value> = vec![
  45. Value::new(Some(&RANDOM_STRINGS[2]), ValueKind::Table(Map::new())),
  46. Value::new(Some(&"".to_string()), ValueKind::String("öß".to_string())),
  47. Value::new(
  48. None, ValueKind::Array(vec![Value::new(Some(&RANDOM_STRINGS[6]), ValueKind::Nil)])
  49. ),
  50. Value::new(Some(&RANDOM_STRINGS[7]), ValueKind::Boolean(false))
  51. ];
  52. pub(crate) static ref TEST_VALUE_KIND_ARRAY: ValueKind =
  53. ValueKind::Array(TEST_VALUE_VECTOR.clone());
  54. }
  55. pub(crate) static TEST_LOGGER: TestLogger = TestLogger {
  56. messages: Mutex::new(vec![]),
  57. };
  58. pub(crate) struct TestLogger {
  59. pub messages: Mutex<Vec<String>>,
  60. }
  61. impl Log for TestLogger {
  62. fn enabled(&self, metadata: &Metadata) -> bool {
  63. metadata.level() <= Level::Trace
  64. }
  65. fn log(&self, record: &Record) {
  66. let mut messages = self.messages.lock().unwrap_or_else(|e| e.into_inner());
  67. messages.push(format!("{} ⸙ {}", record.level(), record.args()));
  68. }
  69. fn flush(&self) {}
  70. }
  71. /// Initialize the custom logger for testing.
  72. pub(crate) fn init_logging() {
  73. // Clear the message list to isolate logs of consecutive test cases.
  74. let mut messages = TEST_LOGGER.messages.lock().unwrap();
  75. messages.clear();
  76. // log::set_logger may only be called once, it returns an Err in subsequent calls.
  77. if let Ok(()) = log::set_logger(&TEST_LOGGER) {
  78. log::set_max_level(LevelFilter::Trace);
  79. }
  80. }
  81. pub(crate) fn test_data(path: &str) -> String {
  82. format!("{}/tests/data/{}", env!("CARGO_MANIFEST_DIR"), path)
  83. }
  84. pub(crate) fn rand_int(min: i32, max: i32) -> i32 {
  85. rand::thread_rng().gen_range(min..max)
  86. }
  87. pub(crate) fn rand_usize(min: usize, max: usize) -> usize {
  88. rand::thread_rng().gen_range(min..max)
  89. }
  90. pub(crate) fn rand_float() -> f64 {
  91. rand::random()
  92. }
  93. pub(crate) fn rand_string(length: usize) -> String {
  94. let charset = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\
  95. ‐‑‒–—―‖‗‘’‚‛“”„‟†‡•‣․‥…‧‰‱′″‴‵‶‷‸‹›※‼‽‾‿⁀⁁⁂⁃⁄⁅⁆⁇⁈⁉⁊⁋⁌⁍⁎⁏⁐⁑⁒⁓⁔⁕⁖⁗\
  96. ─━│┃┄┅┆┇┈┉┊┋┌┍┎┏";
  97. random_string::generate(length, charset)
  98. }
  99. pub(crate) fn random_text_tile_content_vector() -> Vec<String> {
  100. //! Create a vector of random length with random strings of a uniform random length.
  101. let mut content = Vec::new();
  102. let string_length = rand_usize(0, 19);
  103. for _ in 0..rand_usize(2, 12) {
  104. content.push(rand_string(string_length))
  105. }
  106. content
  107. }
  108. pub(crate) fn random_connectors() -> Connectors {
  109. Connectors {
  110. north: rand_usize(1, 10),
  111. east: rand_usize(1, 10),
  112. south: rand_usize(1, 10),
  113. west: rand_usize(1, 10),
  114. }
  115. }
  116. pub(crate) fn random_tile_text(connectors: Connectors) -> Tile {
  117. random_tile_text_with_weight(connectors, rand_float())
  118. }
  119. pub(crate) fn random_tile_text_with_weight(connectors: Connectors, weight: f64) -> Tile {
  120. Tile::new_text(
  121. rand_usize(67, 90),
  122. rand_string(rand_usize(0, 13)),
  123. random_text_tile_content_vector(),
  124. connectors,
  125. weight,
  126. )
  127. .unwrap()
  128. }
  129. /// Helper function that enforces that T and U are the same type.
  130. pub(crate) fn enforce_types_are_equal<T: Sized>(_t: T, _u: T) {}
  131. // Taken from https://stackoverflow.com/a/67433684
  132. /// Changes the current working directory to the specified path.
  133. pub(crate) fn with_cwd<F, P: AsRef<Path>>(path: P, closure: F)
  134. where
  135. F: Fn() + UnwindSafe + RefUnwindSafe,
  136. {
  137. let _guard = SERIAL_TEST.lock().unwrap_or_else(|e| e.into_inner());
  138. let old_cwd = env::current_dir();
  139. env::set_current_dir(path).unwrap();
  140. panic::catch_unwind(|| closure()).unwrap();
  141. if let Ok(cwd) = old_cwd {
  142. env::set_current_dir(cwd).expect("Resetting cwd failed");
  143. }
  144. }
  145. pub(crate) fn assert_expected_result_or_error<T: Sized + PartialEq + Debug>(
  146. result: Result<T, Error>,
  147. expected_result: Option<T>,
  148. expected_error: Option<Error>,
  149. ) {
  150. if let Some(expected_error) = expected_error {
  151. assert_expected_error(result, expected_error);
  152. } else {
  153. assert_eq!(
  154. expected_result.expect("expected Some, got None"),
  155. result.expect("expected Ok, got Err")
  156. );
  157. }
  158. }
  159. pub(crate) fn assert_expected_error<T: Sized + PartialEq + Debug>(
  160. result: Result<T, Error>,
  161. expected_error: Error,
  162. ) {
  163. let error = result.expect_err("Expected an Err, got Ok");
  164. assert_eq!(expected_error, error);
  165. }
  166. #[test]
  167. fn test_connectors_get_connector_is_public() {
  168. let connectors: Connectors = random_connectors();
  169. let _ = connectors.get_connector(&Direction::None);
  170. }
  171. #[test]
  172. fn test_tile_connectors_is_public() {
  173. let tile = random_tile_text(random_connectors());
  174. let _ = tile.connectors;
  175. }
  176. #[test]
  177. fn test_tile_connection_select_tile_by_priority_is_public() {
  178. let tile_connection = TileConnection {
  179. tiles: vec![0],
  180. weights: vec![0.0],
  181. };
  182. let _ = tile_connection.select_tile_by_priority();
  183. }
  184. #[test]
  185. fn test_tile_connection_select_tile_randomly_is_public() {
  186. let mut rng: ThreadRng = rand::thread_rng();
  187. let tile_connection = TileConnection {
  188. tiles: vec![0],
  189. weights: vec![0.0],
  190. };
  191. let _ = tile_connection.select_tile_randomly(&mut rng);
  192. }
  193. #[test]
  194. fn test_tile_grid_populate_simple_is_public() {
  195. let mut tile_grid = tiling::TileGrid::new(
  196. rand_usize(1, 10),
  197. rand_usize(2, 5),
  198. TileList {
  199. tiles: vec![],
  200. horizontal_connector_name_map: {
  201. let mut map = HashMap::new();
  202. map.insert(0, "".to_string());
  203. map
  204. },
  205. vertical_connector_name_map: {
  206. let mut map = HashMap::new();
  207. map.insert(0, "".to_string());
  208. map
  209. },
  210. },
  211. None,
  212. MissingTilePolicy::Exit,
  213. tiling::TileSelectionPolicy::Random,
  214. VacancyPolicy::Avoid,
  215. )
  216. .unwrap();
  217. let _ = tile_grid.populate_simple();
  218. }
  219. #[rstest]
  220. fn test_error_value_error_raw_initialization(
  221. #[values("", " ", "₪", "ita", "call")] message: String,
  222. ) {
  223. let error = Error::ValueError(message.clone());
  224. if let Error::ValueError(msg) = error {
  225. assert_eq!(msg, message);
  226. } else {
  227. panic!("Expected the ValueError variant, got something else");
  228. }
  229. }
  230. #[rstest]
  231. fn test_error_io_error_raw_initialization(#[values("", " ", "e", "lam", "ola")] message: String) {
  232. let error = Error::IoError(message.clone());
  233. if let Error::IoError(msg) = error {
  234. assert_eq!(msg, message);
  235. } else {
  236. panic!("Expected the IoError variant, got something else");
  237. }
  238. }
  239. #[test]
  240. fn test_error_tile_not_found_error_raw_initialization() {
  241. let error = Error::TileNotFoundException(tests::RANDOM_STRINGS[4].clone());
  242. if let Error::TileNotFoundException(msg) = error {
  243. assert_eq!(msg, tests::RANDOM_STRINGS[4]);
  244. } else {
  245. panic!("Expected the TileNotFoundException variant, got something else");
  246. }
  247. }
  248. #[test]
  249. fn test_error_from_config_error() {
  250. let config_error = ConfigError::Message(tests::RANDOM_STRINGS[0].clone());
  251. let error: Error = config_error.into();
  252. match error {
  253. Error::ValueError(error) => assert_eq!(tests::RANDOM_STRINGS[0].clone(), error),
  254. _ => panic!("Expected ValueError variant, got something else"),
  255. }
  256. }
  257. #[test]
  258. fn test_error_from_pattern_error() {
  259. let pattern_error = PatternError {
  260. pos: tests::RANDOM_USIZE.clone(),
  261. msg: &tests::RANDOM_STRINGS[1],
  262. };
  263. let error: Error = pattern_error.into();
  264. match error {
  265. Error::ValueError(error) => assert_eq!(
  266. format!(
  267. "Pattern syntax error near position {}: {}",
  268. tests::RANDOM_USIZE.clone(),
  269. tests::RANDOM_STRINGS[1].clone()
  270. ),
  271. error
  272. ),
  273. _ => panic!("Expected ValueError variant, got something else"),
  274. }
  275. }