Add tests for sort_utils

This commit is contained in:
132ikl 2024-07-30 22:26:03 -04:00
parent dd44cd4d15
commit 54eef49922
2 changed files with 272 additions and 109 deletions

View File

@ -1,12 +1,5 @@
use alphanumeric_sort::compare_str;
use nu_engine::command_prelude::*;
use nu_protocol::{ast::PathMember, IntoValue};
use nu_utils::IgnoreCaseExt;
use std::cmp::Ordering;
use crate::{compare_by, compare_values, Comparator};
#[derive(Clone)]
pub struct Sort;

View File

@ -1,4 +1,3 @@
use alphanumeric_sort::compare_str;
use nu_engine::ClosureEval;
use nu_protocol::{
ast::CellPath,
@ -177,7 +176,7 @@ pub fn compare_values(
let right_str = right.coerce_string()?;
Ok(compare_strings(&left_str, &right_str, insensitive, natural))
} else {
Ok(left.partial_cmp(&right).unwrap_or(Ordering::Equal))
Ok(left.partial_cmp(right).unwrap_or(Ordering::Equal))
}
}
@ -243,119 +242,290 @@ pub fn compare_closure(
#[cfg(test)]
mod tests {
use super::*;
use nu_protocol::{record, Value};
use nu_protocol::Value;
#[test]
fn test_sort_value() {
let val = Value::test_list(vec![
Value::test_record(record! {
"fruit" => Value::test_string("pear"),
"count" => Value::test_int(3),
}),
Value::test_record(record! {
"fruit" => Value::test_string("orange"),
"count" => Value::test_int(7),
}),
Value::test_record(record! {
"fruit" => Value::test_string("apple"),
"count" => Value::test_int(9),
}),
]);
fn test_sort_basic() {
let mut list = vec![
Value::test_string("foo"),
Value::test_int(2),
Value::test_int(3),
Value::test_string("bar"),
Value::test_int(1),
Value::test_string("baz"),
];
let sorted_alphabetically =
sort_value(&val, vec!["fruit".to_string()], true, false, false).unwrap();
assert!(sort(&mut list, false, false).is_ok());
assert_eq!(
sorted_alphabetically,
Value::test_list(vec![
Value::test_record(record! {
"fruit" => Value::test_string("apple"),
"count" => Value::test_int(9),
}),
Value::test_record(record! {
"fruit" => Value::test_string("orange"),
"count" => Value::test_int(7),
}),
Value::test_record(record! {
"fruit" => Value::test_string("pear"),
"count" => Value::test_int(3),
}),
],)
);
let sorted_by_count_desc =
sort_value(&val, vec!["count".to_string()], false, false, false).unwrap();
assert_eq!(
sorted_by_count_desc,
Value::test_list(vec![
Value::test_record(record! {
"fruit" => Value::test_string("apple"),
"count" => Value::test_int(9),
}),
Value::test_record(record! {
"fruit" => Value::test_string("orange"),
"count" => Value::test_int(7),
}),
Value::test_record(record! {
"fruit" => Value::test_string("pear"),
"count" => Value::test_int(3),
}),
],)
list,
vec![
Value::test_int(1),
Value::test_int(2),
Value::test_int(3),
Value::test_string("bar"),
Value::test_string("baz"),
Value::test_string("foo")
]
);
}
#[test]
fn test_sort_value_in_place() {
let mut val = Value::test_list(vec![
Value::test_record(record! {
"fruit" => Value::test_string("pear"),
"count" => Value::test_int(3),
}),
Value::test_record(record! {
"fruit" => Value::test_string("orange"),
"count" => Value::test_int(7),
}),
Value::test_record(record! {
"fruit" => Value::test_string("apple"),
"count" => Value::test_int(9),
}),
]);
fn test_sort_nothing() {
// Nothing values should always be sorted to the end of any list
let mut list = vec![
Value::test_int(1),
Value::test_nothing(),
Value::test_int(2),
Value::test_string("foo"),
Value::test_nothing(),
Value::test_string("bar"),
];
sort_value_in_place(&mut val, vec!["fruit".to_string()], true, false, false).unwrap();
assert!(sort(&mut list, false, false).is_ok());
assert_eq!(
val,
Value::test_list(vec![
Value::test_record(record! {
"fruit" => Value::test_string("apple"),
"count" => Value::test_int(9),
}),
Value::test_record(record! {
"fruit" => Value::test_string("orange"),
"count" => Value::test_int(7),
}),
Value::test_record(record! {
"fruit" => Value::test_string("pear"),
"count" => Value::test_int(3),
}),
],)
list,
vec![
Value::test_int(1),
Value::test_int(2),
Value::test_string("bar"),
Value::test_string("foo"),
Value::test_nothing(),
Value::test_nothing()
]
);
sort_value_in_place(&mut val, vec!["count".to_string()], false, false, false).unwrap();
// Ensure that nothing values are sorted after *all* types,
// even types which may follow `Nothing` in the PartialOrd order
// unstable_name_collision
// can be switched to std intersperse when stabilized
let mut values: Vec<_> =
itertools::intersperse(Value::test_values().into_iter(), Value::test_nothing())
.collect();
let nulls = values
.iter()
.filter(|item| item == &&Value::test_nothing())
.count();
assert!(sort(&mut values, false, false).is_ok());
// check if the last `nulls` values of the sorted list are indeed null
assert_eq!(&values[..nulls], vec![Value::test_nothing(); nulls])
}
#[test]
fn test_sort_natural_basic() {
let mut list = vec![
Value::test_string("99"),
Value::test_string("9"),
Value::test_string("1"),
Value::test_string("100"),
Value::test_string("10"),
];
assert!(sort(&mut list, false, false).is_ok());
assert_eq!(
val,
Value::test_list(vec![
Value::test_record(record! {
"fruit" => Value::test_string("apple"),
"count" => Value::test_int(9),
}),
Value::test_record(record! {
"fruit" => Value::test_string("orange"),
"count" => Value::test_int(7),
}),
Value::test_record(record! {
"fruit" => Value::test_string("pear"),
"count" => Value::test_int(3),
}),
],)
list,
vec![
Value::test_string("1"),
Value::test_string("10"),
Value::test_string("100"),
Value::test_string("9"),
Value::test_string("99"),
]
);
assert!(sort(&mut list, false, true).is_ok());
assert_eq!(
list,
vec![
Value::test_string("1"),
Value::test_string("9"),
Value::test_string("10"),
Value::test_string("99"),
Value::test_string("100"),
]
);
}
#[test]
fn test_sort_natural_mixed_types() {
let mut list = vec![
Value::test_string("1"),
Value::test_int(99),
Value::test_int(1),
Value::test_int(9),
Value::test_string("9"),
Value::test_int(100),
Value::test_string("99"),
Value::test_string("100"),
Value::test_int(10),
Value::test_string("10"),
];
assert!(sort(&mut list, false, false).is_ok());
assert_eq!(
list,
vec![
Value::test_int(1),
Value::test_int(9),
Value::test_int(10),
Value::test_int(99),
Value::test_int(100),
Value::test_string("1"),
Value::test_string("10"),
Value::test_string("100"),
Value::test_string("9"),
Value::test_string("99")
]
);
assert!(sort(&mut list, false, true).is_ok());
assert_eq!(
list,
vec![
Value::test_int(1),
Value::test_string("1"),
Value::test_int(9),
Value::test_string("9"),
Value::test_int(10),
Value::test_string("10"),
Value::test_int(99),
Value::test_string("99"),
Value::test_int(100),
Value::test_string("100"),
]
);
}
#[test]
fn test_sort_natural_no_numeric_values() {
// If list contains no numeric values (numeric strings, ints, floats),
// it should be sorted the same with or without natural sorting
let mut normal = vec![
Value::test_string("golf"),
Value::test_bool(false),
Value::test_string("alfa"),
Value::test_string("echo"),
Value::test_int(7),
Value::test_int(10),
Value::test_bool(true),
Value::test_string("uniform"),
Value::test_int(3),
Value::test_string("tango"),
];
let mut natural = normal.clone();
assert!(sort(&mut normal, false, false).is_ok());
assert!(sort(&mut natural, false, true).is_ok());
assert_eq!(normal, natural);
}
#[test]
fn test_sort_natural_type_order() {
// This test is to prevent regression to a previous natural sort behavior
// where values of different types would be intermixed.
// Only numeric values (ints, floats, and numeric strings) should be intermixed
//
// This list would previously be incorrectly sorted like this:
// ╭────┬─────────╮
// │ 0 │ 1 │
// │ 1 │ golf │
// │ 2 │ false │
// │ 3 │ 7 │
// │ 4 │ 10 │
// │ 5 │ alfa │
// │ 6 │ true │
// │ 7 │ uniform │
// │ 8 │ true │
// │ 9 │ 3 │
// │ 10 │ false │
// │ 11 │ tango │
// ╰────┴─────────╯
let mut list = vec![
Value::test_string("golf"),
Value::test_int(1),
Value::test_bool(false),
Value::test_string("alfa"),
Value::test_int(7),
Value::test_int(10),
Value::test_bool(true),
Value::test_string("uniform"),
Value::test_bool(true),
Value::test_int(3),
Value::test_bool(false),
Value::test_string("tango"),
];
assert!(sort(&mut list, false, true).is_ok());
assert_eq!(
list,
vec![
Value::test_int(1),
Value::test_int(3),
Value::test_int(7),
Value::test_int(10),
Value::test_bool(false),
Value::test_bool(false),
Value::test_bool(true),
Value::test_bool(true),
Value::test_string("alfa"),
Value::test_string("golf"),
Value::test_string("tango"),
Value::test_string("uniform")
]
);
// Only ints, floats, and numeric strings should be intermixed
// While binary primitives and datetimes can be coerced into strings, it doesn't make sense to sort them with numbers
// Binary primitives can hold multiple values, not just one, so shouldn't be compared to single values
// Datetimes don't have a single obvious numeric representation, and if we chose one it would be ambigious to the user
let year_three = chrono::NaiveDate::from_ymd_opt(3, 1, 1)
.unwrap()
.and_hms_opt(0, 0, 0)
.unwrap()
.and_utc();
let mut list = vec![
Value::test_int(10),
Value::test_float(6.0),
Value::test_int(1),
Value::test_binary([3]),
Value::test_string("2"),
Value::test_date(year_three.into()),
Value::test_int(4),
Value::test_binary([52]),
Value::test_float(9.0),
Value::test_string("5"),
Value::test_date(chrono::DateTime::UNIX_EPOCH.into()),
Value::test_int(7),
Value::test_string("8"),
Value::test_float(3.0),
];
assert!(sort(&mut list, false, true).is_ok());
assert_eq!(
list,
vec![
Value::test_int(1),
Value::test_string("2"),
Value::test_float(3.0),
Value::test_int(4),
Value::test_string("5"),
Value::test_float(6.0),
Value::test_int(7),
Value::test_string("8"),
Value::test_float(9.0),
Value::test_int(10),
// the ordering of date and binary here may change if the PartialOrd order is changed,
// but they should not be intermixed with the above
Value::test_binary([3]),
Value::test_binary([52]),
Value::test_date(year_three.into()),
Value::test_date(chrono::DateTime::UNIX_EPOCH.into()),
]
);
}
}