@@ -9,6 +9,7 @@ package deserializer
99import (
1010 "context"
1111 "errors"
12+ "sync"
1213 "testing"
1314
1415 "github.com/LFDT-Panurus/panurus/token/driver"
@@ -880,3 +881,76 @@ func TestTypedIdentityVerifierDeserializer(t *testing.T) {
880881 assert .Equal (t , 1 , mockMatcherDeserializer .GetAuditInfoMatcherCallCount ())
881882 })
882883}
884+
885+ // TestDeserializerMultiplexConcurrency exercises the three deserializer
886+ // multiplexes under concurrent registration (map writes) and deserialization
887+ // (map reads). Without the internal mutex this reliably trips the race
888+ // detector or the runtime's fatal "concurrent map read and map write" check;
889+ // with it, the test runs clean. Run under `go test -race` for full coverage.
890+ func TestDeserializerMultiplexConcurrency (t * testing.T ) {
891+ const (
892+ goroutines = 16
893+ iterations = 200
894+ )
895+
896+ signerMultiplex := NewTypedSignerDeserializerMultiplex ()
897+ verifierMultiplex := NewTypedVerifierDeserializerMultiplex ()
898+ eidrhDeserializer := NewEIDRHDeserializer ()
899+
900+ // Pre-register a deserializer for the type the readers look up so the read
901+ // paths traverse the stored slice rather than bailing out on a miss.
902+ mockSigner := & drivermock.Signer {}
903+ signerDes := & identitydrivermock.TypedSignerDeserializer {}
904+ signerDes .DeserializeSignerReturns (mockSigner , nil )
905+ signerMultiplex .AddTypedSignerDeserializer (identity .Type (99 ), signerDes )
906+
907+ mockVerifier := & drivermock.Verifier {}
908+ verifierDes := & identitydrivermock.TypedVerifierDeserializer {}
909+ verifierDes .DeserializeVerifierReturns (mockVerifier , nil )
910+ verifierDes .RecipientsReturns ([]driver.Identity {[]byte ("recipient" )}, nil )
911+ verifierDes .GetAuditInfoMatcherReturns (& drivermock.Matcher {}, nil )
912+ verifierDes .GetAuditInfoReturns ([]byte ("audit-info" ), nil )
913+ verifierMultiplex .AddTypedVerifierDeserializer (identity .Type (99 ), verifierDes )
914+
915+ mockAuditInfo := & identitydrivermock.AuditInfo {}
916+ auditDes := & identitydrivermock.AuditInfoDeserializer {}
917+ auditDes .DeserializeAuditInfoReturns (mockAuditInfo , nil )
918+ eidrhDeserializer .AddDeserializer (identity .Type (99 ), auditDes )
919+
920+ typedID := createTypedIdentity (t , identity .Type (99 ), []byte ("raw-identity" ))
921+ provider := & drivermock.AuditInfoProvider {}
922+
923+ var wg sync.WaitGroup
924+
925+ // Writers: keep registering new deserializers concurrently.
926+ for range goroutines {
927+ wg .Go (func () {
928+ for range iterations {
929+ // Register under a distinct type so writers keep hammering the
930+ // same map concurrently without clobbering the entry the
931+ // readers rely on below.
932+ signerMultiplex .AddTypedSignerDeserializer (identity .Type (100 ), & identitydrivermock.TypedSignerDeserializer {})
933+ verifierMultiplex .AddTypedVerifierDeserializer (identity .Type (100 ), & identitydrivermock.TypedVerifierDeserializer {})
934+ eidrhDeserializer .AddDeserializer (identity .Type (100 ), & identitydrivermock.AuditInfoDeserializer {})
935+ }
936+ })
937+ }
938+
939+ // Readers: keep deserializing concurrently.
940+ for range goroutines {
941+ wg .Go (func () {
942+ ctx := context .Background ()
943+ for range iterations {
944+ _ , _ = signerMultiplex .DeserializeSigner (ctx , typedID )
945+ _ , _ = verifierMultiplex .DeserializeVerifier (ctx , typedID )
946+ _ , _ = verifierMultiplex .Recipients (typedID )
947+ _ , _ = verifierMultiplex .GetAuditInfoMatcher (ctx , typedID , []byte ("audit-info" ))
948+ _ = verifierMultiplex .MatchIdentity (ctx , typedID , []byte ("audit-info" ))
949+ _ , _ = verifierMultiplex .GetAuditInfo (ctx , typedID , provider )
950+ _ , _ , _ = eidrhDeserializer .GetEIDAndRH (ctx , typedID , []byte ("audit-info" ))
951+ }
952+ })
953+ }
954+
955+ wg .Wait ()
956+ }
0 commit comments