Add tests for sort_utils
This commit is contained in:
parent
dd44cd4d15
commit
54eef49922
|
@ -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;
|
||||
|
||||
|
|
|
@ -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()),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user