@@ -908,6 +908,13 @@ mod tests {
908908 py_name: None ,
909909 } )
910910 pub attr MESSAGE_TTL_DEFAULT : u8 = 64 ;
911+
912+ /// A test key with no environment variable mapping
913+ @meta( CONFIG = ConfigAttr {
914+ env_name: None ,
915+ py_name: None ,
916+ } )
917+ pub attr CONFIG_KEY_NO_ENV : u32 = 100 ;
911918 }
912919
913920 #[ test]
@@ -1369,4 +1376,202 @@ mod tests {
13691376
13701377 assert_eq ! ( get( MESSAGE_TTL_DEFAULT ) , 42 ) ;
13711378 }
1379+
1380+ #[ test]
1381+ fn test_clientoverride_precedence_loses_to_all_other_layers ( ) {
1382+ let _lock = lock ( ) ;
1383+ reset_to_defaults ( ) ;
1384+
1385+ // ClientOverride sets a baseline value.
1386+ let mut client = Attrs :: new ( ) ;
1387+ client[ MESSAGE_TTL_DEFAULT ] = 10 ;
1388+ set ( Source :: ClientOverride , client) ;
1389+ assert_eq ! ( get( MESSAGE_TTL_DEFAULT ) , 10 ) ;
1390+
1391+ // File should beat ClientOverride.
1392+ let mut file = Attrs :: new ( ) ;
1393+ file[ MESSAGE_TTL_DEFAULT ] = 20 ;
1394+ set ( Source :: File , file) ;
1395+ assert_eq ! ( get( MESSAGE_TTL_DEFAULT ) , 20 ) ;
1396+
1397+ // Runtime should beat both File and ClientOverride.
1398+ let mut runtime = Attrs :: new ( ) ;
1399+ runtime[ MESSAGE_TTL_DEFAULT ] = 30 ;
1400+ set ( Source :: Runtime , runtime) ;
1401+ assert_eq ! ( get( MESSAGE_TTL_DEFAULT ) , 30 ) ;
1402+
1403+ // Env should beat Runtime, File, and ClientOverride.
1404+ let mut env = Attrs :: new ( ) ;
1405+ env[ MESSAGE_TTL_DEFAULT ] = 40 ;
1406+ set ( Source :: Env , env) ;
1407+ assert_eq ! ( get( MESSAGE_TTL_DEFAULT ) , 40 ) ;
1408+
1409+ // Clear higher layers one by one to verify fallback.
1410+ clear ( Source :: Env ) ;
1411+ assert_eq ! ( get( MESSAGE_TTL_DEFAULT ) , 30 ) ; // Runtime
1412+
1413+ clear ( Source :: Runtime ) ;
1414+ assert_eq ! ( get( MESSAGE_TTL_DEFAULT ) , 20 ) ; // File
1415+
1416+ clear ( Source :: File ) ;
1417+ assert_eq ! ( get( MESSAGE_TTL_DEFAULT ) , 10 ) ; // ClientOverride
1418+ }
1419+
1420+ #[ test]
1421+ fn test_create_or_merge_clientoverride ( ) {
1422+ let _lock = lock ( ) ;
1423+ reset_to_defaults ( ) ;
1424+
1425+ // Seed ClientOverride with one key.
1426+ let mut client = Attrs :: new ( ) ;
1427+ client[ MESSAGE_TTL_DEFAULT ] = 10 ;
1428+ set ( Source :: ClientOverride , client) ;
1429+
1430+ // Merge in a different key.
1431+ let mut update = Attrs :: new ( ) ;
1432+ update[ MESSAGE_ACK_EVERY_N_MESSAGES ] = 123 ;
1433+ create_or_merge ( Source :: ClientOverride , update) ;
1434+
1435+ // Both keys should now be visible.
1436+ assert_eq ! ( get( MESSAGE_TTL_DEFAULT ) , 10 ) ;
1437+ assert_eq ! ( get( MESSAGE_ACK_EVERY_N_MESSAGES ) , 123 ) ;
1438+ }
1439+
1440+ #[ test]
1441+ fn test_override_or_global_returns_override_when_present ( ) {
1442+ let _lock = lock ( ) ;
1443+ reset_to_defaults ( ) ;
1444+
1445+ // Set a global value via Env.
1446+ let mut env = Attrs :: new ( ) ;
1447+ env[ MESSAGE_TTL_DEFAULT ] = 99 ;
1448+ set ( Source :: Env , env) ;
1449+
1450+ // Create an override Attrs with a different value.
1451+ let mut overrides = Attrs :: new ( ) ;
1452+ overrides[ MESSAGE_TTL_DEFAULT ] = 42 ;
1453+
1454+ // Should return the override value, not global.
1455+ assert_eq ! ( override_or_global( & overrides, MESSAGE_TTL_DEFAULT ) , 42 ) ;
1456+ }
1457+
1458+ #[ test]
1459+ fn test_override_or_global_returns_global_when_not_present ( ) {
1460+ let _lock = lock ( ) ;
1461+ reset_to_defaults ( ) ;
1462+
1463+ // Set a global value via Env.
1464+ let mut env = Attrs :: new ( ) ;
1465+ env[ MESSAGE_TTL_DEFAULT ] = 99 ;
1466+ set ( Source :: Env , env) ;
1467+
1468+ // Empty overrides.
1469+ let overrides = Attrs :: new ( ) ;
1470+
1471+ // Should return the global value.
1472+ assert_eq ! ( override_or_global( & overrides, MESSAGE_TTL_DEFAULT ) , 99 ) ;
1473+ }
1474+
1475+ #[ test]
1476+ fn test_runtime_attrs_returns_only_runtime_layer ( ) {
1477+ let _lock = lock ( ) ;
1478+ reset_to_defaults ( ) ;
1479+
1480+ // Set values in multiple layers.
1481+ let mut file = Attrs :: new ( ) ;
1482+ file[ MESSAGE_TTL_DEFAULT ] = 10 ;
1483+ set ( Source :: File , file) ;
1484+
1485+ let mut env = Attrs :: new ( ) ;
1486+ env[ SPLIT_MAX_BUFFER_SIZE ] = 20 ;
1487+ set ( Source :: Env , env) ;
1488+
1489+ let mut runtime = Attrs :: new ( ) ;
1490+ runtime[ MESSAGE_ACK_EVERY_N_MESSAGES ] = 123 ;
1491+ set ( Source :: Runtime , runtime) ;
1492+
1493+ // runtime_attrs() should return only Runtime layer contents.
1494+ let rt = runtime_attrs ( ) ;
1495+
1496+ // Should have the Runtime key.
1497+ assert_eq ! ( rt[ MESSAGE_ACK_EVERY_N_MESSAGES ] , 123 ) ;
1498+
1499+ // Should NOT have File or Env keys.
1500+ assert ! ( !rt. contains_key( MESSAGE_TTL_DEFAULT ) ) ;
1501+ assert ! ( !rt. contains_key( SPLIT_MAX_BUFFER_SIZE ) ) ;
1502+ }
1503+
1504+ #[ test]
1505+ fn test_override_key_without_env_name_does_not_mirror_to_env ( ) {
1506+ let lock = lock ( ) ;
1507+ reset_to_defaults ( ) ;
1508+
1509+ // Verify default value.
1510+ assert_eq ! ( get( CONFIG_KEY_NO_ENV ) , 100 ) ;
1511+
1512+ // Override the key (which has no env_name).
1513+ let _guard = lock. override_key ( CONFIG_KEY_NO_ENV , 999 ) ;
1514+
1515+ // Should see the override value.
1516+ assert_eq ! ( get( CONFIG_KEY_NO_ENV ) , 999 ) ;
1517+
1518+ // No env var should have been set (test doesn't crash,
1519+ // behavior is clean). This test mainly ensures no panic
1520+ // occurs during override/restore.
1521+
1522+ drop ( _guard) ;
1523+
1524+ // Should restore to default.
1525+ assert_eq ! ( get( CONFIG_KEY_NO_ENV ) , 100 ) ;
1526+ }
1527+
1528+ #[ test]
1529+ fn test_multiple_different_keys_overridden_simultaneously ( ) {
1530+ let lock = lock ( ) ;
1531+ reset_to_defaults ( ) ;
1532+
1533+ // SAFETY: single-threaded test.
1534+ unsafe {
1535+ std:: env:: remove_var ( "HYPERACTOR_CODEC_MAX_FRAME_LENGTH" ) ;
1536+ std:: env:: remove_var ( "HYPERACTOR_MESSAGE_TTL_DEFAULT" ) ;
1537+ }
1538+
1539+ // Override multiple different keys at once.
1540+ let guard1 = lock. override_key ( CODEC_MAX_FRAME_LENGTH , 1111 ) ;
1541+ let guard2 = lock. override_key ( MESSAGE_TTL_DEFAULT , 42 ) ;
1542+ let guard3 = lock. override_key ( CHANNEL_MULTIPART , false ) ;
1543+
1544+ // All should reflect their override values.
1545+ assert_eq ! ( get( CODEC_MAX_FRAME_LENGTH ) , 1111 ) ;
1546+ assert_eq ! ( get( MESSAGE_TTL_DEFAULT ) , 42 ) ;
1547+ assert_eq ! ( get( CHANNEL_MULTIPART ) , false ) ;
1548+
1549+ // Env vars should be mirrored.
1550+ assert_eq ! (
1551+ std:: env:: var( "HYPERACTOR_CODEC_MAX_FRAME_LENGTH" ) . unwrap( ) ,
1552+ "1111"
1553+ ) ;
1554+ assert_eq ! (
1555+ std:: env:: var( "HYPERACTOR_MESSAGE_TTL_DEFAULT" ) . unwrap( ) ,
1556+ "42"
1557+ ) ;
1558+
1559+ // Drop guards in arbitrary order.
1560+ drop ( guard2) ; // Drop MESSAGE_TTL_DEFAULT first
1561+
1562+ // MESSAGE_TTL_DEFAULT should restore, others should remain.
1563+ assert_eq ! ( get( MESSAGE_TTL_DEFAULT ) , MESSAGE_TTL_DEFAULT_DEFAULT ) ;
1564+ assert_eq ! ( get( CODEC_MAX_FRAME_LENGTH ) , 1111 ) ;
1565+ assert_eq ! ( get( CHANNEL_MULTIPART ) , false ) ;
1566+
1567+ // Env for MESSAGE_TTL_DEFAULT should be cleared.
1568+ assert ! ( std:: env:: var( "HYPERACTOR_MESSAGE_TTL_DEFAULT" ) . is_err( ) ) ;
1569+
1570+ drop ( guard1) ;
1571+ drop ( guard3) ;
1572+
1573+ // All should be restored.
1574+ assert_eq ! ( get( CODEC_MAX_FRAME_LENGTH ) , CODEC_MAX_FRAME_LENGTH_DEFAULT ) ;
1575+ assert_eq ! ( get( CHANNEL_MULTIPART ) , CHANNEL_MULTIPART_DEFAULT ) ;
1576+ }
13721577}
0 commit comments