11// Copyright The OpenTelemetry Authors
22// SPDX-License-Identifier: Apache-2.0
33
4+ using System . Collections ;
45using System . Diagnostics ;
56using Xunit ;
67
@@ -14,25 +15,11 @@ public class TraceContextPropagatorTests
1415 private const string SpanId = "b9c7c989f97918e1" ;
1516
1617 private static readonly string [ ] Empty = [ ] ;
17- private static readonly Func < IDictionary < string , string > , string , IEnumerable < string > > Getter = ( headers , name ) =>
18- {
19- if ( headers . TryGetValue ( name , out var value ) )
20- {
21- return [ value ] ;
22- }
23-
24- return Empty ;
25- } ;
18+ private static readonly Func < IDictionary < string , string > , string , IEnumerable < string > > Getter =
19+ static ( headers , name ) => headers . TryGetValue ( name , out var value ) ? [ value ] : ( IEnumerable < string > ) Empty ;
2620
27- private static readonly Func < IDictionary < string , string [ ] > , string , IEnumerable < string > > ArrayGetter = ( headers , name ) =>
28- {
29- if ( headers . TryGetValue ( name , out var value ) )
30- {
31- return value ;
32- }
33-
34- return [ ] ;
35- } ;
21+ private static readonly Func < IDictionary < string , string [ ] > , string , IEnumerable < string > > ArrayGetter =
22+ static ( headers , name ) => headers . TryGetValue ( name , out var value ) ? value : ( IEnumerable < string > ) [ ] ;
3623
3724 private static readonly Action < IDictionary < string , string > , string , string > Setter = ( carrier , name , value ) =>
3825 {
@@ -56,7 +43,7 @@ public void CanParseExampleFromSpec()
5643
5744 Assert . True ( ctx . ActivityContext . IsRemote ) ;
5845 Assert . True ( ctx . ActivityContext . IsValid ( ) ) ;
59- Assert . True ( ( ctx . ActivityContext . TraceFlags & ActivityTraceFlags . Recorded ) != 0 ) ;
46+ Assert . NotEqual ( 0 , ( int ) ( ctx . ActivityContext . TraceFlags & ActivityTraceFlags . Recorded ) ) ;
6047
6148 Assert . Equal ( $ "congo=lZWRzIHRoNhcm5hbCBwbGVhc3VyZS4,rojo=00-{ TraceId } -00f067aa0ba902b7-01", ctx . ActivityContext . TraceState ) ;
6249 }
@@ -74,7 +61,7 @@ public void NotSampled()
7461
7562 Assert . Equal ( ActivityTraceId . CreateFromString ( TraceId . AsSpan ( ) ) , ctx . ActivityContext . TraceId ) ;
7663 Assert . Equal ( ActivitySpanId . CreateFromString ( SpanId . AsSpan ( ) ) , ctx . ActivityContext . SpanId ) ;
77- Assert . True ( ( ctx . ActivityContext . TraceFlags & ActivityTraceFlags . Recorded ) == 0 ) ;
64+ Assert . Equal ( 0 , ( int ) ( ctx . ActivityContext . TraceFlags & ActivityTraceFlags . Recorded ) ) ;
7865
7966 Assert . True ( ctx . ActivityContext . IsRemote ) ;
8067 Assert . True ( ctx . ActivityContext . IsValid ( ) ) ;
@@ -138,6 +125,111 @@ public void TracestateToString()
138125 Assert . Equal ( "k1=v1,k2=v2,k3=v3" , ctx . ActivityContext . TraceState ) ;
139126 }
140127
128+ [ Fact ]
129+ public void Extract_SupportsReadOnlyListCarrierValues ( )
130+ {
131+ var headers = new Dictionary < string , ReadOnlyCarrierValues >
132+ {
133+ [ TraceParent ] = new ( [ $ "00-{ TraceId } -{ SpanId } -01"] ) ,
134+ [ TraceState ] = new ( [ "k1=v1" ] ) ,
135+ } ;
136+
137+ var target = new TraceContextPropagator ( ) ;
138+ var actual = target . Extract ( default , headers , static ( carrier , name ) =>
139+ carrier . TryGetValue ( name , out var value ) ? value : new ReadOnlyCarrierValues ( [ ] ) ) ;
140+
141+ Assert . Equal ( ActivityTraceId . CreateFromString ( TraceId . AsSpan ( ) ) , actual . ActivityContext . TraceId ) ;
142+ Assert . Equal ( ActivitySpanId . CreateFromString ( SpanId . AsSpan ( ) ) , actual . ActivityContext . SpanId ) ;
143+ Assert . Equal ( "k1=v1" , actual . ActivityContext . TraceState ) ;
144+ }
145+
146+ [ Fact ]
147+ public void Extract_SupportsEnumerableCarrierValues ( )
148+ {
149+ var headers = new Dictionary < string , EnumerableCarrierValues >
150+ {
151+ [ TraceParent ] = new ( [ $ "00-{ TraceId } -{ SpanId } -01"] ) ,
152+ [ TraceState ] = new ( [ " k1=v1 , k2=v2 " ] ) ,
153+ } ;
154+
155+ var target = new TraceContextPropagator ( ) ;
156+ var actual = target . Extract ( default , headers , static ( carrier , name ) =>
157+ carrier . TryGetValue ( name , out var value ) ? value : new EnumerableCarrierValues ( [ ] ) ) ;
158+
159+ Assert . Equal ( ActivityTraceId . CreateFromString ( TraceId . AsSpan ( ) ) , actual . ActivityContext . TraceId ) ;
160+ Assert . Equal ( ActivitySpanId . CreateFromString ( SpanId . AsSpan ( ) ) , actual . ActivityContext . SpanId ) ;
161+ Assert . Equal ( "k1=v1,k2=v2" , actual . ActivityContext . TraceState ) ;
162+ }
163+
164+ [ Fact ]
165+ public void Extract_IgnoresMultipleEnumerableTraceparentValues ( )
166+ {
167+ var headers = new Dictionary < string , EnumerableCarrierValues >
168+ {
169+ [ TraceParent ] = new ( [ $ "00-{ TraceId } -{ SpanId } -01", $ "00-{ TraceId } -{ SpanId } -00"] ) ,
170+ } ;
171+
172+ var target = new TraceContextPropagator ( ) ;
173+ var context = target . Extract ( default , headers , static ( carrier , name ) =>
174+ carrier . TryGetValue ( name , out var value ) ? value : new EnumerableCarrierValues ( [ ] ) ) ;
175+
176+ Assert . False ( context . ActivityContext . IsValid ( ) ) ;
177+ }
178+
179+ [ Fact ]
180+ public void Extract_IgnoresEmptyEnumerableTracestateValues ( )
181+ {
182+ var headers = new Dictionary < string , EnumerableCarrierValues >
183+ {
184+ [ TraceParent ] = new ( [ $ "00-{ TraceId } -{ SpanId } -01"] ) ,
185+ [ TraceState ] = new ( [ ] ) ,
186+ } ;
187+
188+ var target = new TraceContextPropagator ( ) ;
189+ var context = target . Extract ( default , headers , static ( carrier , name ) =>
190+ carrier . TryGetValue ( name , out var value ) ? value : new EnumerableCarrierValues ( [ ] ) ) ;
191+
192+ Assert . Equal ( ActivityTraceId . CreateFromString ( TraceId . AsSpan ( ) ) , context . ActivityContext . TraceId ) ;
193+ Assert . Null ( context . ActivityContext . TraceState ) ;
194+ }
195+
196+ [ Fact ]
197+ public void TryExtractTracestate_SingleHeaderReturnsOriginalString ( )
198+ {
199+ Assert . True ( TraceContextPropagator . TryExtractTracestate ( [ "k1=v1,k2=v2" ] , out var actual ) ) ;
200+ Assert . Equal ( "k1=v1,k2=v2" , actual ) ;
201+ }
202+
203+ [ Fact ]
204+ public void TryExtractTracestate_SingleHeaderReturnsEmptyForWhitespaceOnly ( )
205+ {
206+ Assert . True ( TraceContextPropagator . TryExtractTracestate ( [ " , " ] , out var actual ) ) ;
207+ Assert . Empty ( actual ) ;
208+ }
209+
210+ [ Fact ]
211+ public void TryExtractTracestate_SingleHeaderRejectsTooManyMembers ( )
212+ {
213+ var tracestate = string . Join ( "," , Enumerable . Range ( 1 , 33 ) . Select ( static i => $ "k{ i : D2} =v{ i : D2} ") ) ;
214+
215+ Assert . False ( TraceContextPropagator . TryExtractTracestate ( [ tracestate ] , out _ ) ) ;
216+ }
217+
218+ [ Fact ]
219+ public void TryExtractTracestate_SingleHeaderRejectsDuplicateLongKeys ( )
220+ {
221+ var key = new string ( 'a' , 33 ) ;
222+
223+ Assert . False ( TraceContextPropagator . TryExtractTracestate ( [ $ "{ key } =1,{ key } =2"] , out _ ) ) ;
224+ }
225+
226+ [ Fact ]
227+ public void TryExtractTracestate_NullCollectionReturnsEmpty ( )
228+ {
229+ Assert . True ( TraceContextPropagator . TryExtractTracestate ( ( IEnumerable < string > ) null ! , out var actual ) ) ;
230+ Assert . Empty ( actual ) ;
231+ }
232+
141233 [ Fact ]
142234 public void Inject_NoTracestate ( )
143235 {
@@ -333,4 +425,34 @@ private static string CallTraceContextPropagator(string[] tracestate)
333425 Assert . NotNull ( traceState ) ;
334426 return traceState ;
335427 }
428+
429+ private sealed class ReadOnlyCarrierValues ( params string [ ] values ) : IReadOnlyList < string >
430+ {
431+ public int Count => values . Length ;
432+
433+ public string this [ int index ] => values [ index ] ;
434+
435+ public IEnumerator < string > GetEnumerator ( )
436+ {
437+ foreach ( var value in values )
438+ {
439+ yield return value ;
440+ }
441+ }
442+
443+ IEnumerator IEnumerable . GetEnumerator ( ) => this . GetEnumerator ( ) ;
444+ }
445+
446+ private sealed class EnumerableCarrierValues ( params string [ ] values ) : IEnumerable < string >
447+ {
448+ public IEnumerator < string > GetEnumerator ( )
449+ {
450+ foreach ( var value in values )
451+ {
452+ yield return value ;
453+ }
454+ }
455+
456+ IEnumerator IEnumerable . GetEnumerator ( ) => this . GetEnumerator ( ) ;
457+ }
336458}
0 commit comments