@@ -45,6 +45,21 @@ internal void SetContracts(ContractRegistry contracts)
4545 _contractRegistry = contracts ;
4646 }
4747
48+ /// <summary>
49+ /// Creates a <see cref="TestContractRegistry"/> with <see cref="CoreCLRContracts.Register"/>
50+ /// applied, and sets it as the contract registry for this target. Returns the registry so
51+ /// callers can call <see cref="TestContractRegistry.SetVersion{TContract}"/> and
52+ /// <see cref="TestContractRegistry.SetMock{TContract}"/>.
53+ /// </summary>
54+ internal TestContractRegistry SetupContractRegistry ( Action < ContractRegistry > ? registrations = null )
55+ {
56+ var registry = new TestContractRegistry ( ) ;
57+ registry . SetTarget ( this ) ;
58+ ( registrations ?? CoreCLRContracts . Register ) ( registry ) ;
59+ _contractRegistry = registry ;
60+ return registry ;
61+ }
62+
4863 /// <summary>
4964 /// Fluent builder for <see cref="TestPlaceholderTarget"/>. Accumulates types,
5065 /// globals, and contract factories from mock descriptors, then materializes the
@@ -57,7 +72,8 @@ internal class Builder
5772 private readonly Dictionary < DataType , Target . TypeInfo > _types = new ( ) ;
5873 private readonly List < ( string Name , ulong Value ) > _globals = new ( ) ;
5974 private readonly List < ( string Name , string Value ) > _globalStrings = new ( ) ;
60- private readonly List < ( Type Type , Func < Target , IContract > Factory ) > _contractFactories = new ( ) ;
75+ private readonly List < Action < TestContractRegistry > > _contractSetups = new ( ) ;
76+ private Action < ContractRegistry > _registrations = CoreCLRContracts . Register ;
6177
6278 public Builder ( MockTarget . Architecture arch )
6379 {
@@ -86,9 +102,21 @@ public Builder AddGlobalStrings(params (string Name, string Value)[] globalStrin
86102 return this ;
87103 }
88104
89- public Builder AddContract < TContract > ( Func < Target , TContract > factory ) where TContract : IContract
105+ public Builder UseRegistrations ( Action < ContractRegistry > registrations )
90106 {
91- _contractFactories . Add ( ( typeof ( TContract ) , target => factory ( target ) ) ) ;
107+ _registrations = registrations ;
108+ return this ;
109+ }
110+
111+ public Builder AddContract < TContract > ( int version ) where TContract : IContract
112+ {
113+ _contractSetups . Add ( registry => registry . SetVersion < TContract > ( version ) ) ;
114+ return this ;
115+ }
116+
117+ public Builder AddMockContract < TContract > ( TContract mock ) where TContract : IContract
118+ {
119+ _contractSetups . Add ( registry => registry . SetMock ( mock ) ) ;
92120 return this ;
93121 }
94122
@@ -102,8 +130,12 @@ public TestPlaceholderTarget Build()
102130 _globalStrings . ToArray ( ) ) ;
103131
104132 var registry = new TestContractRegistry ( ) ;
105- foreach ( var ( type , factory ) in _contractFactories )
106- registry . Add ( type , new Lazy < IContract > ( ( ) => factory ( target ) ) ) ;
133+ registry . SetTarget ( target ) ;
134+ _registrations ( registry ) ;
135+
136+ foreach ( var setup in _contractSetups )
137+ setup ( registry ) ;
138+
107139 target . SetContracts ( registry ) ;
108140
109141 return target ;
@@ -455,22 +487,50 @@ public void Clear()
455487 }
456488 }
457489
458- private sealed class TestContractRegistry : ContractRegistry
490+ internal sealed class TestContractRegistry : ContractRegistry
459491 {
460- private readonly Dictionary < Type , Lazy < IContract > > _contracts = new ( ) ;
492+ private readonly Dictionary < ( Type , int ) , Func < Target , IContract > > _creators = new ( ) ;
493+ private readonly Dictionary < Type , int > _versions = new ( ) ;
494+ private readonly Dictionary < Type , IContract > _mocks = new ( ) ;
495+ private readonly Dictionary < Type , IContract > _resolved = new ( ) ;
496+ private Target _target ;
461497
462- public void Add ( Type type , Lazy < IContract > contract ) => _contracts [ type ] = contract ;
498+ public void SetTarget ( Target target ) => _target = target ;
499+
500+ public void SetVersion < TContract > ( int version ) where TContract : IContract
501+ => _versions [ typeof ( TContract ) ] = version ;
502+
503+ public void SetMock < TContract > ( TContract mock ) where TContract : IContract
504+ => _mocks [ typeof ( TContract ) ] = mock ;
505+
506+ public override void Register < TContract > ( int version , Func < Target , TContract > creator )
507+ => _creators [ ( typeof ( TContract ) , version ) ] = t => creator ( t ) ;
463508
464509 public override TContract GetContract < TContract > ( )
465510 {
466- if ( _contracts . TryGetValue ( typeof ( TContract ) , out var lazy ) )
467- return ( TContract ) lazy . Value ;
511+ if ( _resolved . TryGetValue ( typeof ( TContract ) , out var cached ) )
512+ return ( TContract ) cached ;
468513
469- throw new NotImplementedException ( $ "Contract { typeof ( TContract ) . Name } is not registered.") ;
470- }
514+ IContract contract ;
515+ if ( _mocks . TryGetValue ( typeof ( TContract ) , out var mock ) )
516+ {
517+ contract = mock ;
518+ }
519+ else if ( _versions . TryGetValue ( typeof ( TContract ) , out int version ) )
520+ {
521+ if ( ! _creators . TryGetValue ( ( typeof ( TContract ) , version ) , out var creator ) )
522+ throw new NotImplementedException ( $ "No implementation registered for contract '{ typeof ( TContract ) . Name } ' version { version } .") ;
471523
472- public override void Register < TContract > ( int version , Func < Target , TContract > creator )
473- => throw new NotSupportedException ( "TestContractRegistry does not support dynamic registration. Use SetContracts to configure contracts for testing." ) ;
524+ contract = creator ( _target ) ;
525+ }
526+ else
527+ {
528+ throw new NotImplementedException ( $ "Contract { typeof ( TContract ) . Name } is not registered. Use AddContract<T>(version) or AddMockContract<T>(mock) on the Builder.") ;
529+ }
530+
531+ _resolved [ typeof ( TContract ) ] = contract ;
532+ return ( TContract ) contract ;
533+ }
474534
475535 public override void Flush ( ) { }
476536 }
0 commit comments