@@ -402,7 +402,7 @@ TEST(RNTuple, SerializeLocator)
402402 // Multi locator round-trip with 32-bit nBytesOnStorage
403403 locator = RNTupleLocator{};
404404 locator.SetType (RNTupleLocator::kTypeMulti );
405- locator.SetPosition (RNTupleLocatorObject64{ 42U });
405+ locator.SetPosition (RNTupleLocatorMulti{ 7 , 1024 });
406406 locator.SetNBytesOnStorage (1024U );
407407 locator.SetReserved (0 );
408408 EXPECT_EQ (16u , RNTupleSerializer::SerializeLocator (locator, buffer).Unwrap ());
@@ -411,7 +411,9 @@ TEST(RNTuple, SerializeLocator)
411411 EXPECT_EQ (locator.GetType (), RNTupleLocator::kTypeMulti );
412412 EXPECT_EQ (locator.GetNBytesOnStorage (), 1024U );
413413 EXPECT_EQ (locator.GetReserved (), 0 );
414- EXPECT_EQ (42U , locator.GetPosition <RNTupleLocatorObject64>().GetLocation ());
414+ auto multi = locator.GetPosition <RNTupleLocatorMulti>();
415+ EXPECT_EQ (7U , multi.GetObjectId ());
416+ EXPECT_EQ (1024U , multi.GetOffset ());
415417
416418 // Multi locator round-trip with 64-bit nBytesOnStorage and reserved bit
417419 locator.SetNBytesOnStorage (static_cast <std::uint64_t >(std::numeric_limits<std::uint32_t >::max ()) + 1 );
@@ -422,7 +424,9 @@ TEST(RNTuple, SerializeLocator)
422424 EXPECT_EQ (locator.GetType (), RNTupleLocator::kTypeMulti );
423425 EXPECT_EQ (locator.GetNBytesOnStorage (), static_cast <std::uint64_t >(std::numeric_limits<std::uint32_t >::max ()) + 1 );
424426 EXPECT_EQ (locator.GetReserved (), 1 );
425- EXPECT_EQ (42U , locator.GetPosition <RNTupleLocatorObject64>().GetLocation ());
427+ multi = locator.GetPosition <RNTupleLocatorMulti>();
428+ EXPECT_EQ (7U , multi.GetObjectId ());
429+ EXPECT_EQ (1024U , multi.GetOffset ());
426430
427431 std::int32_t *head = reinterpret_cast <std::int32_t *>(buffer);
428432#ifndef R__BYTESWAP
@@ -435,6 +439,101 @@ TEST(RNTuple, SerializeLocator)
435439 EXPECT_EQ (locator.GetType (), RNTupleLocator::kTypeUnknown );
436440}
437441
442+ TEST (RNTuple, RNTupleLocatorMultiPackUnpack)
443+ {
444+ // Default-constructed: all zero
445+ RNTupleLocatorMulti zero;
446+ EXPECT_EQ (0U , zero.GetObjectId ());
447+ EXPECT_EQ (0U , zero.GetOffset ());
448+ EXPECT_EQ (0U , zero.GetReserved ());
449+ EXPECT_EQ (0ULL , zero.GetLocation ());
450+
451+ // Non-trivial values within the 30-bit range
452+ RNTupleLocatorMulti m (0x12345 , 0xABCDE );
453+ EXPECT_EQ (0x12345U , m.GetObjectId ());
454+ EXPECT_EQ (0xABCDEU , m.GetOffset ());
455+ EXPECT_EQ (0U , m.GetReserved ());
456+ // GetLocation() packs the three fields per the documented layout:
457+ // bits [63..60] reserved, [59..30] object id, [29..0] offset
458+ EXPECT_EQ ((static_cast <std::uint64_t >(0x12345 ) << 30 ) | 0xABCDE , m.GetLocation ());
459+
460+ // Round-trip via the raw uint64 constructor
461+ RNTupleLocatorMulti round{m.GetLocation ()};
462+ EXPECT_EQ (m, round);
463+ EXPECT_EQ (m.GetObjectId (), round.GetObjectId ());
464+ EXPECT_EQ (m.GetOffset (), round.GetOffset ());
465+
466+ // Max 30-bit values
467+ RNTupleLocatorMulti maxVals (RNTupleLocatorMulti::kMaxObjectId , RNTupleLocatorMulti::kMaxOffset );
468+ EXPECT_EQ (0x3FFFFFFFU , maxVals.GetObjectId ());
469+ EXPECT_EQ (0x3FFFFFFFU , maxVals.GetOffset ());
470+ EXPECT_EQ (0U , maxVals.GetReserved ());
471+
472+ // Object id only: no bit leak into offset or reserved
473+ RNTupleLocatorMulti idOnly (RNTupleLocatorMulti::kMaxObjectId , 0U );
474+ EXPECT_EQ (RNTupleLocatorMulti::kMaxObjectId , idOnly.GetObjectId ());
475+ EXPECT_EQ (0U , idOnly.GetOffset ());
476+ EXPECT_EQ (0U , idOnly.GetReserved ());
477+
478+ // Offset only: no bit leak into object id or reserved
479+ RNTupleLocatorMulti offsetOnly (0U , RNTupleLocatorMulti::kMaxOffset );
480+ EXPECT_EQ (0U , offsetOnly.GetObjectId ());
481+ EXPECT_EQ (RNTupleLocatorMulti::kMaxOffset , offsetOnly.GetOffset ());
482+ EXPECT_EQ (0U , offsetOnly.GetReserved ());
483+
484+ // Constructor enforces the 30-bit range on both fields
485+ EXPECT_THROW (RNTupleLocatorMulti (1U << 30 , 0 ), ROOT ::RException);
486+ EXPECT_THROW (RNTupleLocatorMulti (0 , 1U << 30 ), ROOT ::RException);
487+
488+ // Reserved bits round-trip without corrupting the other fields
489+ RNTupleLocatorMulti withReserved (7 , 42 );
490+ withReserved.SetReserved (0xF );
491+ EXPECT_EQ (0xF , withReserved.GetReserved ());
492+ EXPECT_EQ (7U , withReserved.GetObjectId ());
493+ EXPECT_EQ (42U , withReserved.GetOffset ());
494+
495+ // SetReserved enforces the 4-bit range
496+ EXPECT_THROW (withReserved.SetReserved (0x10 ), ROOT ::RException);
497+
498+ // Raw uint64 construction preserves bits without validation; the masks
499+ // correctly carve out each field from a fully populated location.
500+ RNTupleLocatorMulti raw{0xFFFFFFFFFFFFFFFFULL };
501+ EXPECT_EQ (RNTupleLocatorMulti::kMaxObjectId , raw.GetObjectId ());
502+ EXPECT_EQ (RNTupleLocatorMulti::kMaxOffset , raw.GetOffset ());
503+ EXPECT_EQ (RNTupleLocatorMulti::kMaxReserved , raw.GetReserved ());
504+ }
505+
506+ TEST (RNTuple, RNTupleLocatorMultiTypeEnforcement)
507+ {
508+ RNTupleLocator locator;
509+
510+ // SetPosition(Multi) requires kTypeMulti.
511+ locator.SetType (RNTupleLocator::kTypeObject64 );
512+ EXPECT_THROW (locator.SetPosition (RNTupleLocatorMulti (1 , 2 )), ROOT ::RException);
513+ locator.SetType (RNTupleLocator::kTypeFile );
514+ EXPECT_THROW (locator.SetPosition (RNTupleLocatorMulti (1 , 2 )), ROOT ::RException);
515+
516+ // SetPosition(Object64) no longer accepts kTypeMulti after the split.
517+ locator = RNTupleLocator{};
518+ locator.SetType (RNTupleLocator::kTypeMulti );
519+ EXPECT_THROW (locator.SetPosition (RNTupleLocatorObject64{1 }), ROOT ::RException);
520+
521+ // Correct usage round-trip.
522+ locator.SetPosition (RNTupleLocatorMulti (1 , 2 ));
523+ auto m = locator.GetPosition <RNTupleLocatorMulti>();
524+ EXPECT_EQ (1U , m.GetObjectId ());
525+ EXPECT_EQ (2U , m.GetOffset ());
526+
527+ // GetPosition<Object64>() no longer accepts kTypeMulti after the split.
528+ EXPECT_THROW (locator.GetPosition <RNTupleLocatorObject64>(), ROOT ::RException);
529+
530+ // GetPosition<Multi>() rejects non-Multi types.
531+ locator = RNTupleLocator{};
532+ locator.SetType (RNTupleLocator::kTypeObject64 );
533+ locator.SetPosition (RNTupleLocatorObject64{1 });
534+ EXPECT_THROW (locator.GetPosition <RNTupleLocatorMulti>(), ROOT ::RException);
535+ }
536+
438537TEST (RNTuple, SerializeEnvelopeLink)
439538{
440539 RNTupleSerializer::REnvelopeLink link;
0 commit comments