@@ -2915,6 +2915,100 @@ TEST(RNTupleMerger, MergeLMExtBig)
29152915 }
29162916}
29172917
2918+ TEST (RNTupleMerger, MergeFirstNoEntries)
2919+ {
2920+ // Try merging three ntuples with different schemas, the first two of which have no entries.
2921+ FileRaii fileGuard1 (" test_ntuple_merge_first_noentries_1.root" );
2922+ FileRaii fileGuard2 (" test_ntuple_merge_first_noentries_2.root" );
2923+ FileRaii fileGuard3 (" test_ntuple_merge_first_noentries_3.root" );
2924+ {
2925+ auto model = RNTupleModel::Create ();
2926+ model->MakeField <float >(" foo" );
2927+ model->MakeField <int >(" bar" );
2928+ auto ntuple = RNTupleWriter::Recreate (std::move (model), " ntuple" , fileGuard1.GetPath ());
2929+ }
2930+ {
2931+ auto model = RNTupleModel::Create ();
2932+ model->MakeField <std::string>(" asd" );
2933+ auto ntuple = RNTupleWriter::Recreate (std::move (model), " ntuple" , fileGuard2.GetPath ());
2934+ }
2935+ {
2936+ auto model = RNTupleModel::Create ();
2937+ auto p = model->MakeField <double >(" baz" );
2938+ auto ntuple = RNTupleWriter::Recreate (std::move (model), " ntuple" , fileGuard3.GetPath ());
2939+ for (int i = 0 ; i < 10 ; ++i) {
2940+ *p = i;
2941+ ntuple->Fill ();
2942+ }
2943+ }
2944+
2945+ // Now merge the inputs
2946+ FileRaii fileGuardOut (" test_ntuple_merge_first_noentries_out.root" );
2947+ {
2948+ // Gather the input sources
2949+ std::vector<std::unique_ptr<RPageSource>> sources;
2950+ sources.push_back (RPageSource::Create (" ntuple" , fileGuard1.GetPath (), RNTupleReadOptions ()));
2951+ sources.push_back (RPageSource::Create (" ntuple" , fileGuard2.GetPath (), RNTupleReadOptions ()));
2952+ sources.push_back (RPageSource::Create (" ntuple" , fileGuard3.GetPath (), RNTupleReadOptions ()));
2953+ std::vector<RPageSource *> sourcePtrs;
2954+ for (const auto &s : sources) {
2955+ sourcePtrs.push_back (s.get ());
2956+ }
2957+
2958+ RNTupleMergeOptions opts;
2959+ {
2960+ auto destination = std::make_unique<RPageSinkFile>(" ntuple" , fileGuardOut.GetPath (), RNTupleWriteOptions ());
2961+ opts.fMergingMode = ENTupleMergingMode::kFilter ;
2962+ RNTupleMerger merger{std::move (destination)};
2963+ auto res = merger.Merge (sourcePtrs, opts);
2964+ // Should fail because the second input doesn't have all the fields of the first
2965+ EXPECT_FALSE (bool (res));
2966+ }
2967+ {
2968+ auto destination = std::make_unique<RPageSinkFile>(" ntuple" , fileGuardOut.GetPath (), RNTupleWriteOptions ());
2969+ opts.fMergingMode = ENTupleMergingMode::kStrict ;
2970+ RNTupleMerger merger{std::move (destination)};
2971+ auto res = merger.Merge (sourcePtrs, opts);
2972+ EXPECT_FALSE (bool (res));
2973+ }
2974+ {
2975+ auto destination = std::make_unique<RPageSinkFile>(" ntuple" , fileGuardOut.GetPath (), RNTupleWriteOptions ());
2976+ opts.fMergingMode = ENTupleMergingMode::kUnion ;
2977+ RNTupleMerger merger{std::move (destination)};
2978+ auto res = merger.Merge (sourcePtrs, opts);
2979+ EXPECT_TRUE (bool (res));
2980+ }
2981+ }
2982+
2983+ {
2984+ auto ntupleOut = RNTupleReader::Open (" ntuple" , fileGuardOut.GetPath ());
2985+ EXPECT_EQ (ntupleOut->GetNEntries (), 10 );
2986+ EXPECT_EQ (ntupleOut->GetDescriptor ().GetNFields (), 5 ); // zero field + the ones we added
2987+ auto pFoo = ntupleOut->GetModel ().GetDefaultEntry ().GetPtr <float >(" foo" );
2988+ auto pBar = ntupleOut->GetModel ().GetDefaultEntry ().GetPtr <int >(" bar" );
2989+ auto pBaz = ntupleOut->GetModel ().GetDefaultEntry ().GetPtr <double >(" baz" );
2990+ auto pAsd = ntupleOut->GetModel ().GetDefaultEntry ().GetPtr <std::string>(" asd" );
2991+ for (int i : ntupleOut->GetEntryRange ()) {
2992+ ntupleOut->LoadEntry (i);
2993+ EXPECT_FLOAT_EQ (*pFoo, 0 );
2994+ EXPECT_EQ (*pBar, 0 );
2995+ EXPECT_EQ (*pAsd, " " );
2996+ EXPECT_DOUBLE_EQ (*pBaz, i);
2997+ }
2998+
2999+ const auto &descFoo = ntupleOut->GetDescriptor ().GetColumnDescriptor (0 );
3000+ const auto &descBar = ntupleOut->GetDescriptor ().GetColumnDescriptor (1 );
3001+ const auto &descAsd = ntupleOut->GetDescriptor ().GetColumnDescriptor (2 );
3002+ const auto &descBaz = ntupleOut->GetDescriptor ().GetColumnDescriptor (3 );
3003+ EXPECT_FALSE (descFoo.IsDeferredColumn ());
3004+ EXPECT_FALSE (descAsd.IsDeferredColumn ());
3005+ // The columns extended from the third input are not deferred as they normally would, because the first two
3006+ // inputs had 0 entries, so they still start at index 0.
3007+ EXPECT_FALSE (descBar.IsDeferredColumn ());
3008+ EXPECT_FALSE (descBaz.IsDeferredColumn ());
3009+ }
3010+ }
3011+
29183012TEST (RNTupleMerger, MergeEmptySchema)
29193013{
29203014 // Try merging two ntuples with an empty schema
@@ -2969,9 +3063,9 @@ TEST(RNTupleMerger, MergeEmptySchema)
29693063 // We expect the output ntuple to have no entries
29703064 {
29713065 auto ntupleOut = RNTupleReader::Open (" ntuple" , fileGuardOut.GetPath ());
2972- ASSERT_EQ (ntupleOut->GetNEntries (), 0 );
3066+ EXPECT_EQ (ntupleOut->GetNEntries (), 0 );
29733067 // We expect to see only the zero field
2974- ASSERT_EQ (ntupleOut->GetDescriptor ().GetNFields (), 1 );
3068+ EXPECT_EQ (ntupleOut->GetDescriptor ().GetNFields (), 1 );
29753069 }
29763070}
29773071
0 commit comments