#![allow(clippy::cast_possible_truncation)]
use ferray_strings::{self, StringArray1, StringArray2};
use ferray_test_oracle::{fixtures_dir, load_fixture, parse_bool_data, parse_string_data};
fn strings_fx(name: &str) -> std::path::PathBuf {
fixtures_dir().join("strings").join(name)
}
fn make_array(value: &ferray_test_oracle::serde_json::Value) -> StringArray1 {
let data = parse_string_data(&value["data"]);
ferray_strings::array(&data.iter().map(String::as_str).collect::<Vec<_>>()).unwrap()
}
#[test]
fn upper_matches_numpy() {
let suite = load_fixture(&strings_fx("upper.json"));
for case in &suite.test_cases {
assert_eq!(case.tolerance_ulps, 0, "case '{}' tolerance", case.name);
let arr = make_array(&case.inputs["x"]);
let out = ferray_strings::upper(&arr).unwrap();
let expected = parse_string_data(&case.expected["data"]);
assert_eq!(out.as_slice(), expected.as_slice(), "case '{}'", case.name);
}
}
#[test]
fn lower_matches_numpy() {
let suite = load_fixture(&strings_fx("lower.json"));
for case in &suite.test_cases {
assert_eq!(case.tolerance_ulps, 0);
let arr = make_array(&case.inputs["x"]);
let out = ferray_strings::lower(&arr).unwrap();
let expected = parse_string_data(&case.expected["data"]);
assert_eq!(out.as_slice(), expected.as_slice(), "case '{}'", case.name);
}
}
#[test]
fn capitalize_matches_numpy() {
let suite = load_fixture(&strings_fx("capitalize.json"));
for case in &suite.test_cases {
assert_eq!(case.tolerance_ulps, 0);
let arr = make_array(&case.inputs["x"]);
let out = ferray_strings::capitalize(&arr).unwrap();
let expected = parse_string_data(&case.expected["data"]);
assert_eq!(out.as_slice(), expected.as_slice(), "case '{}'", case.name);
}
}
#[test]
fn title_matches_numpy() {
let suite = load_fixture(&strings_fx("title.json"));
for case in &suite.test_cases {
assert_eq!(case.tolerance_ulps, 0);
let arr = make_array(&case.inputs["x"]);
let out = ferray_strings::title(&arr).unwrap();
let expected = parse_string_data(&case.expected["data"]);
assert_eq!(out.as_slice(), expected.as_slice(), "case '{}'", case.name);
}
}
#[test]
fn swapcase_matches_numpy() {
let suite = load_fixture(&strings_fx("swapcase.json"));
for case in &suite.test_cases {
assert_eq!(case.tolerance_ulps, 0);
let arr = make_array(&case.inputs["x"]);
let out = ferray_strings::swapcase(&arr).unwrap();
let expected = parse_string_data(&case.expected["data"]);
assert_eq!(out.as_slice(), expected.as_slice(), "case '{}'", case.name);
}
}
#[test]
fn str_len_matches_python() {
let arr = ferray_strings::array(&["", "a", "abc", "café", "🦀"]).unwrap();
let lens = ferray_strings::str_len(&arr).unwrap();
let expected = [0i64, 1, 3, 4, 1];
let slc = lens.as_slice().unwrap();
assert_eq!(slc, expected);
}
#[test]
fn comparison_ufuncs_match_lex_order() {
let a = ferray_strings::array(&["abc", "abd", "abc", ""]).unwrap();
let b = ferray_strings::array(&["abc", "abc", "abd", "x"]).unwrap();
let eq = ferray_strings::equal(&a, &b).unwrap();
assert_eq!(eq.as_slice().unwrap(), &[true, false, false, false]);
let ne = ferray_strings::not_equal(&a, &b).unwrap();
assert_eq!(ne.as_slice().unwrap(), &[false, true, true, true]);
let lt = ferray_strings::less(&a, &b).unwrap();
assert_eq!(lt.as_slice().unwrap(), &[false, false, true, true]);
let gt = ferray_strings::greater(&a, &b).unwrap();
assert_eq!(gt.as_slice().unwrap(), &[false, true, false, false]);
let le = ferray_strings::less_equal(&a, &b).unwrap();
assert_eq!(le.as_slice().unwrap(), &[true, false, true, true]);
let ge = ferray_strings::greater_equal(&a, &b).unwrap();
assert_eq!(ge.as_slice().unwrap(), &[true, true, false, false]);
}
#[test]
fn add_matches_numpy() {
let suite = load_fixture(&strings_fx("add.json"));
for case in &suite.test_cases {
assert_eq!(case.tolerance_ulps, 0);
let x = make_array(&case.inputs["x"]);
let y = make_array(&case.inputs["y"]);
let out = ferray_strings::add(&x, &y).unwrap();
let expected = parse_string_data(&case.expected["data"]);
assert_eq!(out.as_slice(), expected.as_slice(), "case '{}'", case.name);
}
}
#[test]
fn add_same_matches_add_for_equal_shapes() {
let x = ferray_strings::array(&["foo", "bar", "baz"]).unwrap();
let y = ferray_strings::array(&["1", "22", "333"]).unwrap();
let out = ferray_strings::add_same(&x, &y).unwrap();
assert_eq!(out.as_slice(), &["foo1", "bar22", "baz333"]);
}
#[test]
fn multiply_matches_numpy() {
let suite = load_fixture(&strings_fx("multiply.json"));
for case in &suite.test_cases {
assert_eq!(case.tolerance_ulps, 0);
let arr = make_array(&case.inputs["x"]);
let n = case.inputs["n"].as_u64().unwrap() as usize;
let out = ferray_strings::multiply(&arr, n).unwrap();
let expected = parse_string_data(&case.expected["data"]);
assert_eq!(out.as_slice(), expected.as_slice(), "case '{}'", case.name);
}
}
#[test]
fn strip_matches_numpy() {
let suite = load_fixture(&strings_fx("strip.json"));
for case in &suite.test_cases {
assert_eq!(case.tolerance_ulps, 0);
let arr = make_array(&case.inputs["x"]);
let chars = case.inputs.get("chars").and_then(|v| v.as_str());
let out = ferray_strings::strip::strip(&arr, chars).unwrap();
let expected = parse_string_data(&case.expected["data"]);
assert_eq!(out.as_slice(), expected.as_slice(), "case '{}'", case.name);
}
}
#[test]
fn lstrip_matches_numpy() {
let suite = load_fixture(&strings_fx("lstrip.json"));
for case in &suite.test_cases {
assert_eq!(case.tolerance_ulps, 0);
let arr = make_array(&case.inputs["x"]);
let chars = case.inputs.get("chars").and_then(|v| v.as_str());
let out = ferray_strings::lstrip(&arr, chars).unwrap();
let expected = parse_string_data(&case.expected["data"]);
assert_eq!(out.as_slice(), expected.as_slice(), "case '{}'", case.name);
}
}
#[test]
fn rstrip_matches_numpy() {
let suite = load_fixture(&strings_fx("rstrip.json"));
for case in &suite.test_cases {
assert_eq!(case.tolerance_ulps, 0);
let arr = make_array(&case.inputs["x"]);
let chars = case.inputs.get("chars").and_then(|v| v.as_str());
let out = ferray_strings::rstrip(&arr, chars).unwrap();
let expected = parse_string_data(&case.expected["data"]);
assert_eq!(out.as_slice(), expected.as_slice(), "case '{}'", case.name);
}
}
#[test]
fn split_produces_padded_2d_array() {
let arr = ferray_strings::array(&["a-b-c", "x-y", "lone"]).unwrap();
let out: StringArray2 = ferray_strings::split(&arr, "-").unwrap();
assert_eq!(out.shape(), &[3, 3]);
assert_eq!(
out.as_slice(),
&["a", "b", "c", "x", "y", "", "lone", "", ""]
);
}
#[test]
fn rsplit_with_maxsplit_keeps_leading_remainder() {
let arr = ferray_strings::array(&["a-b-c-d"]).unwrap();
let out = ferray_strings::rsplit(&arr, "-", Some(1)).unwrap();
assert_eq!(out.shape(), &[1, 2]);
assert_eq!(out.as_slice(), &["a-b-c", "d"]);
}
#[test]
fn splitlines_handles_universal_newlines() {
let arr = ferray_strings::array(&["a\nb\nc", "single"]).unwrap();
let out = ferray_strings::splitlines(&arr, false).unwrap();
assert_eq!(out.shape(), &[2, 3]);
assert_eq!(out.as_slice(), &["a", "b", "c", "single", "", ""]);
}
#[test]
fn split_ragged_preserves_uneven_rows() {
let arr = ferray_strings::array(&["a-b-c", "x"]).unwrap();
let out = ferray_strings::split_ragged(&arr, "-").unwrap();
assert_eq!(out.len(), 2);
assert_eq!(
out[0],
vec!["a".to_string(), "b".to_string(), "c".to_string()]
);
assert_eq!(out[1], vec!["x".to_string()]);
}
#[test]
fn join_concatenates_with_separator() {
let items = vec![
vec!["a".to_string(), "b".to_string()],
vec!["x".to_string(), "y".to_string(), "z".to_string()],
];
let out = ferray_strings::join("-", &items).unwrap();
assert_eq!(out.as_slice(), &["a-b", "x-y-z"]);
}
#[test]
fn join_array_flattens_all_elements() {
let arr = ferray_strings::array(&["a", "b", "c"]).unwrap();
let out = ferray_strings::join_array("|", &arr).unwrap();
assert_eq!(out.as_slice(), &["a|b|c"]);
}
#[test]
fn center_matches_numpy() {
let suite = load_fixture(&strings_fx("center.json"));
for case in &suite.test_cases {
assert_eq!(case.tolerance_ulps, 0);
let arr = make_array(&case.inputs["x"]);
let width = case.inputs["width"].as_u64().unwrap() as usize;
let out = ferray_strings::center(&arr, width, ' ').unwrap();
let expected = parse_string_data(&case.expected["data"]);
assert_eq!(out.as_slice(), expected.as_slice(), "case '{}'", case.name);
}
}
#[test]
fn ljust_matches_numpy() {
let suite = load_fixture(&strings_fx("ljust.json"));
for case in &suite.test_cases {
assert_eq!(case.tolerance_ulps, 0);
let arr = make_array(&case.inputs["x"]);
let width = case.inputs["width"].as_u64().unwrap() as usize;
let out = ferray_strings::ljust(&arr, width).unwrap();
let expected = parse_string_data(&case.expected["data"]);
assert_eq!(out.as_slice(), expected.as_slice(), "case '{}'", case.name);
}
}
#[test]
fn ljust_with_uses_custom_fillchar() {
let arr = ferray_strings::array(&["hi", "abc"]).unwrap();
let out = ferray_strings::ljust_with(&arr, 5, '*').unwrap();
assert_eq!(out.as_slice(), &["hi***", "abc**"]);
}
#[test]
fn rjust_matches_numpy() {
let suite = load_fixture(&strings_fx("rjust.json"));
for case in &suite.test_cases {
assert_eq!(case.tolerance_ulps, 0);
let arr = make_array(&case.inputs["x"]);
let width = case.inputs["width"].as_u64().unwrap() as usize;
let out = ferray_strings::rjust(&arr, width).unwrap();
let expected = parse_string_data(&case.expected["data"]);
assert_eq!(out.as_slice(), expected.as_slice(), "case '{}'", case.name);
}
}
#[test]
fn rjust_with_uses_custom_fillchar() {
let arr = ferray_strings::array(&["hi", "abc"]).unwrap();
let out = ferray_strings::rjust_with(&arr, 5, '*').unwrap();
assert_eq!(out.as_slice(), &["***hi", "**abc"]);
}
#[test]
fn zfill_matches_numpy() {
let suite = load_fixture(&strings_fx("zfill.json"));
for case in &suite.test_cases {
assert_eq!(case.tolerance_ulps, 0);
let arr = make_array(&case.inputs["x"]);
let width = case.inputs["width"].as_u64().unwrap() as usize;
let out = ferray_strings::zfill(&arr, width).unwrap();
let expected = parse_string_data(&case.expected["data"]);
assert_eq!(out.as_slice(), expected.as_slice(), "case '{}'", case.name);
}
}
#[test]
fn parse_bool_data_smoke() {
let val = ferray_test_oracle::serde_json::json!([true, false, true]);
let v = parse_bool_data(&val);
assert_eq!(v, vec![true, false, true]);
}