|
12 | 12 | // See the License for the specific language governing permissions and |
13 | 13 | // limitations under the License. |
14 | 14 |
|
15 | | -use std::{collections::BTreeSet, sync::Arc}; |
| 15 | +use std::{cmp::Ordering, collections::BTreeSet, sync::Arc}; |
16 | 16 |
|
17 | 17 | use futures_util::{StreamExt as _, stream}; |
18 | 18 | use matrix_sdk_base::{ |
@@ -537,13 +537,92 @@ impl PinnedEventCache { |
537 | 537 | // them, since we've lost all ordering information from using /event or |
538 | 538 | // /relations. Resort to sorting using chronological ordering (oldest -> |
539 | 539 | // newest). |
540 | | - loaded_events.sort_by_key(|item| { |
541 | | - item.raw() |
542 | | - .deserialize() |
543 | | - .map(|e| e.origin_server_ts()) |
544 | | - .unwrap_or_else(|_| MilliSecondsSinceUnixEpoch::now()) |
545 | | - }); |
| 540 | + loaded_events.sort_by(compare_pinned_items); |
546 | 541 |
|
547 | 542 | Ok(Some(loaded_events)) |
548 | 543 | } |
549 | 544 | } |
| 545 | + |
| 546 | +fn compare_pinned_items(a: &Event, b: &Event) -> Ordering { |
| 547 | + let a_time: Option<MilliSecondsSinceUnixEpoch> = a.timestamp_raw(); |
| 548 | + let b_time: Option<MilliSecondsSinceUnixEpoch> = b.timestamp_raw(); |
| 549 | + |
| 550 | + compare_by_optional_timestamp(a_time, b_time) |
| 551 | +} |
| 552 | + |
| 553 | +fn compare_by_optional_timestamp( |
| 554 | + a: Option<MilliSecondsSinceUnixEpoch>, |
| 555 | + b: Option<MilliSecondsSinceUnixEpoch>, |
| 556 | +) -> Ordering { |
| 557 | + match (a, b) { |
| 558 | + (None, None) => Ordering::Equal, |
| 559 | + (None, Some(_)) => Ordering::Greater, |
| 560 | + (Some(_), None) => Ordering::Less, |
| 561 | + (Some(a), Some(b)) => a.cmp(&b), |
| 562 | + } |
| 563 | +} |
| 564 | + |
| 565 | +#[cfg(not(target_family = "wasm"))] |
| 566 | +#[cfg(test)] |
| 567 | +mod tests { |
| 568 | + use proptest::prelude::*; |
| 569 | + use ruma::UInt; |
| 570 | + |
| 571 | + use super::*; |
| 572 | + |
| 573 | + fn any_timestamp() -> impl Strategy<Value = Option<MilliSecondsSinceUnixEpoch>> { |
| 574 | + prop::option::of( |
| 575 | + any::<u32>().prop_map(|value| MilliSecondsSinceUnixEpoch(UInt::from(value))), |
| 576 | + ) |
| 577 | + } |
| 578 | + |
| 579 | + #[test] |
| 580 | + fn sort_pinned_events_never_panics_only_nones() { |
| 581 | + let mut vec = vec![None; 100_000]; |
| 582 | + vec.sort_by(|a, b| compare_by_optional_timestamp(*a, *b)) |
| 583 | + } |
| 584 | + |
| 585 | + proptest! { |
| 586 | + #[test] |
| 587 | + fn sort_pinned_events_never_panics(mut v in prop::collection::vec(any_timestamp(), 0..1000)) { |
| 588 | + v.sort_by( |
| 589 | + |a, b| compare_by_optional_timestamp(*a, *b)) |
| 590 | + } |
| 591 | + |
| 592 | + #[test] |
| 593 | + fn compare_pinned_events_reflexive(a in any_timestamp()) { |
| 594 | + prop_assert_eq!(compare_by_optional_timestamp(a, a), Ordering::Equal); |
| 595 | + } |
| 596 | + |
| 597 | + #[test] |
| 598 | + fn compare_pinned_events_antisymmetric(a in any_timestamp(), b in any_timestamp()) { |
| 599 | + let ab = compare_by_optional_timestamp(a, b); |
| 600 | + let ba = compare_by_optional_timestamp(b, a); |
| 601 | + |
| 602 | + prop_assert_eq!(ab, ba.reverse()); |
| 603 | + } |
| 604 | + |
| 605 | + #[test] |
| 606 | + fn compare_pinned_events_transitive( |
| 607 | + a in any_timestamp(), |
| 608 | + b in any_timestamp(), |
| 609 | + c in any_timestamp() |
| 610 | + ) { |
| 611 | + let ab = compare_by_optional_timestamp(a, b); |
| 612 | + let bc = compare_by_optional_timestamp(b, c); |
| 613 | + let ac = compare_by_optional_timestamp(a, c); |
| 614 | + |
| 615 | + if ab == Ordering::Less && bc == Ordering::Less { |
| 616 | + prop_assert_eq!(ac, Ordering::Less); |
| 617 | + } |
| 618 | + |
| 619 | + if ab == Ordering::Equal && bc == Ordering::Equal { |
| 620 | + prop_assert_eq!(ac, Ordering::Equal); |
| 621 | + } |
| 622 | + |
| 623 | + if ab == Ordering::Greater && bc == Ordering::Greater { |
| 624 | + prop_assert_eq!(ac, Ordering::Greater); |
| 625 | + } |
| 626 | + } |
| 627 | + } |
| 628 | +} |
0 commit comments