55#include < gmock/gmock.h>
66#include < gtest/gtest.h>
77
8+ #include < turtle_kv/core/testing/generate.hpp>
89#include < turtle_kv/import/env.hpp>
910
1011#include < batteries/stream_util.hpp>
@@ -23,7 +24,10 @@ using namespace batt::int_types;
2324using batt::as_seq;
2425using batt::WorkerPool;
2526
27+ using llfs::StableStringStore;
28+
2629using turtle_kv::CInterval;
30+ using turtle_kv::DecayToItem;
2731using turtle_kv::EditSlice;
2832using turtle_kv::EditView;
2933using turtle_kv::getenv_as;
@@ -39,6 +43,8 @@ using turtle_kv::Status;
3943using turtle_kv::StatusOr;
4044using turtle_kv::ValueView;
4145
46+ using turtle_kv::testing::RandomStringGenerator;
47+
4248namespace seq = turtle_kv::seq;
4349
4450constexpr usize kNumKeys = 16 ;
@@ -482,4 +488,182 @@ TEST(MergeCompactor, ResultSetDropKeyRange)
482488 }
483489}
484490
485- } // namespace
491+ // ==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - -
492+ //
493+ class ResultSetConcatTest : public ::testing::Test
494+ {
495+ public:
496+ void generate_edits (usize num_edits)
497+ {
498+ std::unordered_set<KeyView> keys_set;
499+
500+ std::default_random_engine rng{/* seed=*/ 30 };
501+ RandomStringGenerator generate_key;
502+ while (this ->all_edits_ .size () < num_edits) {
503+ KeyView key = generate_key (rng, this ->store_ );
504+ if (keys_set.contains (key)) {
505+ continue ;
506+ }
507+ keys_set.emplace (key);
508+ this ->all_edits_ .emplace_back (key,
509+ ValueView::from_str (this ->store_ .store (std::string (100 , ' a' ))));
510+ }
511+
512+ std::sort (this ->all_edits_ .begin (), this ->all_edits_ .end (), KeyOrder{});
513+ }
514+
515+ template <bool kDecayToItems >
516+ MergeCompactor::ResultSet<kDecayToItems > concat (std::vector<EditView>&& first,
517+ std::vector<EditView>&& second,
518+ DecayToItem<kDecayToItems > decay_to_item)
519+ {
520+ usize first_size = first.size ();
521+ usize second_size = second.size ();
522+
523+ MergeCompactor::ResultSet<kDecayToItems > first_result_set;
524+ first_result_set.append (std::move (first));
525+ MergeCompactor::ResultSet<kDecayToItems > second_result_set;
526+ second_result_set.append (std::move (second));
527+
528+ EXPECT_EQ (first_result_set.size (), first_size);
529+ EXPECT_EQ (second_result_set.size (), second_size);
530+
531+ MergeCompactor::ResultSet<kDecayToItems > concatenated_result_set =
532+ MergeCompactor::ResultSet<kDecayToItems >::concat (std::move (first_result_set),
533+ std::move (second_result_set));
534+
535+ return concatenated_result_set;
536+ }
537+
538+ template <bool kDecayToItems >
539+ void verify_result_set (const MergeCompactor::ResultSet<kDecayToItems >& result_set,
540+ const std::vector<EditView>& edits)
541+ {
542+ EXPECT_EQ (result_set.size (), edits.size ());
543+
544+ usize i = 0 ;
545+ for (const EditView& edit : result_set.get ()) {
546+ EXPECT_EQ (edit, edits[i]);
547+ ++i;
548+ }
549+ }
550+
551+ llfs::StableStringStore store_;
552+ std::vector<EditView> all_edits_;
553+ };
554+
555+ // ==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - -
556+ //
557+ TEST_F (ResultSetConcatTest, Concat)
558+ {
559+ // Generate an edit batch of size 200.
560+ //
561+ usize n = 200 ;
562+ this ->generate_edits (n);
563+
564+ // Divide the edit batch in half, and create ResultSet objects out of each half.
565+ //
566+ std::vector<EditView> first{this ->all_edits_ .begin (), this ->all_edits_ .begin () + (n / 2 )};
567+ std::vector<EditView> second{this ->all_edits_ .begin () + (n / 2 ), this ->all_edits_ .end ()};
568+
569+ MergeCompactor::ResultSet<false > concatenated_result_set =
570+ this ->concat (std::move (first), std::move (second), DecayToItem<false >{});
571+
572+ // Concatenated ResultSet should have the same size as the original edit batch, and should
573+ // also contain the same items in the same order.
574+ //
575+ this ->verify_result_set (concatenated_result_set, this ->all_edits_ );
576+
577+ // Now, repeat the process with unequal sized inputs.
578+ //
579+ first.assign (this ->all_edits_ .begin (), this ->all_edits_ .begin () + (n / 4 ));
580+ second.assign (this ->all_edits_ .begin () + (n / 4 ), this ->all_edits_ .end ());
581+
582+ concatenated_result_set = this ->concat (std::move (first), std::move (second), DecayToItem<false >{});
583+
584+ this ->verify_result_set (concatenated_result_set, this ->all_edits_ );
585+
586+ // Finally, test with empty input.
587+ //
588+ first = {};
589+ second.assign (this ->all_edits_ .begin (), this ->all_edits_ .begin () + (n / 4 ));
590+
591+ concatenated_result_set = this ->concat (std::move (first), std::move (second), DecayToItem<false >{});
592+
593+ this ->verify_result_set (concatenated_result_set,
594+ {this ->all_edits_ .begin (), this ->all_edits_ .begin () + (n / 4 )});
595+
596+ first.assign (this ->all_edits_ .begin (), this ->all_edits_ .begin () + (n / 4 ));
597+ second = {};
598+
599+ concatenated_result_set = this ->concat (std::move (first), std::move (second), DecayToItem<false >{});
600+
601+ this ->verify_result_set (concatenated_result_set,
602+ {this ->all_edits_ .begin (), this ->all_edits_ .begin () + (n / 4 )});
603+
604+ first = {};
605+ second = {};
606+ concatenated_result_set = this ->concat (std::move (first), std::move (second), DecayToItem<false >{});
607+ EXPECT_EQ (concatenated_result_set.size (), 0 );
608+ }
609+
610+ // ==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - -
611+ //
612+ TEST_F (ResultSetConcatTest, FragmentedConcat)
613+ {
614+ usize n = 200 ;
615+ this ->generate_edits (n);
616+
617+ std::vector<EditView> first{this ->all_edits_ .begin (), this ->all_edits_ .begin () + (n / 2 )};
618+ std::vector<EditView> second{this ->all_edits_ .begin () + (n / 2 ), this ->all_edits_ .end ()};
619+
620+ MergeCompactor::ResultSet<false > first_result_set;
621+ first_result_set.append (std::move (first));
622+ MergeCompactor::ResultSet<false > second_result_set;
623+ second_result_set.append (std::move (second));
624+
625+ // Drop some keys fron the beginning of the ResultSet.
626+ //
627+ first_result_set.drop_before_n (n / 10 );
628+
629+ // Drop some keys in the middle of the ResultSet.
630+ //
631+ auto second_range_begin = this ->all_edits_ .begin () + (3 * n / 5 );
632+ auto second_range_end = this ->all_edits_ .begin () + (3 * n / 4 );
633+ Interval<KeyView> second_range{second_range_begin->key , second_range_end->key };
634+ second_result_set.drop_key_range_half_open (second_range);
635+
636+ MergeCompactor::ResultSet<false > concatenated_result_set =
637+ MergeCompactor::ResultSet<false >::concat (std::move (first_result_set),
638+ std::move (second_result_set));
639+
640+ std::vector<EditView> concat_edits{this ->all_edits_ .begin () + (n / 10 ),
641+ this ->all_edits_ .begin () + (3 * n / 5 )};
642+ concat_edits.insert (concat_edits.end (),
643+ this ->all_edits_ .begin () + (3 * n / 4 ),
644+ this ->all_edits_ .end ());
645+ this ->verify_result_set (concatenated_result_set, concat_edits);
646+ }
647+
648+ // ==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - -
649+ //
650+ TEST_F (ResultSetConcatTest, ConcatDeath)
651+ {
652+ usize n = 200 ;
653+ this ->generate_edits (n);
654+
655+ std::vector<EditView> first{this ->all_edits_ .begin (), this ->all_edits_ .begin () + (n / 2 )};
656+ std::vector<EditView> second{this ->all_edits_ .begin () + (n / 2 ), this ->all_edits_ .end ()};
657+
658+ // Undo the sorting.
659+ //
660+ std::swap (first.back (), second.front ());
661+
662+ // We should panic since first and second have overlapping key ranges.
663+ //
664+ EXPECT_DEATH (this ->concat (std::move (first), std::move (second), DecayToItem<false >{}),
665+ " All elements in the first ResultSet should be strictly less than the elements in "
666+ " the second ResultSet!" );
667+ }
668+
669+ } // namespace
0 commit comments