use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;
use tzselect_rs::{run, Host, Options};
fn dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}
struct MockHost {
lines: std::vec::IntoIter<String>,
tables: HashMap<String, String>,
err: String,
out: String,
}
impl Host for MockHost {
fn read_line(&mut self) -> Option<String> {
self.lines.next()
}
fn err(&mut self, s: &str) {
self.err.push_str(s);
}
fn out(&mut self, s: &str) {
self.out.push_str(s);
}
fn read_table(&self, path: &str) -> Option<String> {
let base = path.rsplit('/').next().unwrap_or(path);
self.tables.get(base).cloned()
}
fn run_date(&self, _tz: &str) -> Option<String> {
Some("Mon Jan 1 00:00:00 UTC 2024".to_string())
}
fn run_date_fmt(&self, _tz: &str, _fmt: &str) -> Option<String> {
Some("2024 01 01 00:00 Mon Jan".to_string())
}
fn zone_readable(&self, _path: &str) -> bool {
true
}
fn stdout_is_tty(&self) -> bool {
false
}
}
fn tables() -> HashMap<String, String> {
let t = dir().join("tests/fixtures/tables");
let mut m = HashMap::new();
for f in ["iso3166.tab", "zone1970.tab", "zonenow.tab"] {
m.insert(f.to_string(), fs::read_to_string(t.join(f)).expect(f));
}
m
}
fn run_case(name: &str, input: &str) {
let mut h = MockHost {
lines: input
.lines()
.map(|s| s.to_string())
.collect::<Vec<_>>()
.into_iter(),
tables: tables(),
err: String::new(),
out: String::new(),
};
let code = run(&Options::default(), &mut h);
let got = format!(
"=== exit {code} ===\n=== stdout ===\n{}\n=== stderr ===\n{}",
h.out, h.err
);
let golden = dir()
.join("tests/fixtures/golden")
.join(format!("{name}.txt"));
if std::env::var("BLESS").is_ok() {
fs::create_dir_all(golden.parent().unwrap()).unwrap();
fs::write(&golden, &got).unwrap();
return;
}
let want = fs::read_to_string(&golden)
.unwrap_or_else(|_| panic!("missing golden {name}; run BLESS=1"));
assert_eq!(got, want, "[{name}] transcript diverged from golden");
}
macro_rules! case {
($fn:ident, $name:literal, $input:literal) => {
#[test]
fn $fn() {
run_case($name, $input);
}
};
}
case!(europe_andorra, "europe_andorra", "8\n3\n1\n");
case!(
europe_andorra_no_retry,
"europe_andorra_no_retry",
"8\n3\n2\n8\n3\n1\n"
);
case!(americas_us_eastern, "americas_us_eastern", "2\n49\n1\n1\n");
case!(europe_britain, "europe_britain", "8\n8\n1\n");
case!(bad_then_good, "bad_then_good", "99\n8\n3\n1\n");
case!(empty_relists, "empty_relists", "\n8\n3\n1\n");
case!(tz_string, "tz_string", "12\nAEST-10\n1\n");
case!(
tz_string_retry,
"tz_string_retry",
"12\nbad!!\nEST5EDT,M3.2.0,M11.1.0\n1\n"
);
case!(coord_paris, "coord_paris", "11\n+4852+00220\n1\n1\n");
case!(eof_at_continent, "eof_at_continent", "");
#[test]
fn posix_tz_examples() {
for ok in [
"AEST-10",
"EST5EDT,M3.2.0,M11.1.0",
"<+05>-5",
"GMT0",
":America/New_York",
"UTC0",
] {
assert!(tzselect_rs::posix_tz_valid(ok), "should accept {ok}");
}
for bad in [
"",
"AB",
"EST",
"AEST-10x",
"X-25",
"EST5EDT,M13.2.0,M11.1.0",
] {
let _ = bad;
}
assert!(!tzselect_rs::posix_tz_valid("AB"), "too-short name");
assert!(!tzselect_rs::posix_tz_valid("EST"), "name without offset");
assert!(!tzselect_rs::posix_tz_valid("X-25"), "hour out of range");
}