Make all test cases in W3C trace-context test suite pass (#1668)
* Fixing errors in W3CTraceContextTests * Fix test_tracestate_key_illegal_vendor_format and test_tracestate_key_length_limit (cherry picked from commit 5a2bd56c8f83b8e9094534084988992184f4fe7e) * update test run result (cherry picked from commit 4e23f9aec69c59affb27327e0a624b32a82317cc) * Fix test_traceparent_version_0x00 and test_traceparent_version_0xff Note: the bug was introduced in https://github.com/open-telemetry/opentelemetry-dotnet/pull/923/files#diff-670edb2ea7fa1212aab16a9f732dad8b1e2f15801a3eac1bd0824385355d7d97L262 * Validate key with Trace Context v1 https://www.w3.org/TR/trace-context-1/ which has W3C Recommendation status. * Refactor validator for lower case alpha and digit * Fix bug in vendor parsing and vendor valid character check. * Fix typo and add link where magic numbers are defined. * Adding comments * Update comment * Reuse OWS (Optional Whitespace characters). Utilize return value of HashSet.Add. Forbid upper case in traceparent string. * Use Span and Slice instead of string.Split * Remove unused OptionalWhiteSpaceCharacters variable Co-authored-by: Cijo Thomas <cithomas@microsoft.com>
This commit is contained in:
parent
e24dccbe9f
commit
c188edb48f
|
|
@ -18,6 +18,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using OpenTelemetry.Internal;
|
||||
|
||||
|
|
@ -39,6 +40,12 @@ namespace OpenTelemetry.Context.Propagation
|
|||
private static readonly int OptionsLength = "00".Length;
|
||||
private static readonly int TraceparentLengthV0 = "00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-00".Length;
|
||||
|
||||
// The following length limits are from Trace Context v1 https://www.w3.org/TR/trace-context-1/#key
|
||||
private static readonly int TraceStateKeyMaxLength = 256;
|
||||
private static readonly int TraceStateKeyTenantMaxLength = 241;
|
||||
private static readonly int TraceStateKeyVendorMaxLength = 14;
|
||||
private static readonly int TraceStateValueMaxLength = 256;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ISet<string> Fields => new HashSet<string> { TraceState, TraceParent };
|
||||
|
||||
|
|
@ -230,18 +237,79 @@ namespace OpenTelemetry.Context.Propagation
|
|||
|
||||
if (tracestateCollection != null)
|
||||
{
|
||||
var keySet = new HashSet<string>();
|
||||
var result = new StringBuilder();
|
||||
|
||||
// Iterate in reverse order because when call builder set the elements is added in the
|
||||
// front of the list.
|
||||
for (int i = tracestateCollection.Length - 1; i >= 0; i--)
|
||||
for (int i = 0; i < tracestateCollection.Length; ++i)
|
||||
{
|
||||
if (string.IsNullOrEmpty(tracestateCollection[i]))
|
||||
var tracestate = tracestateCollection[i].AsSpan();
|
||||
int begin = 0;
|
||||
while (begin < tracestate.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int length = tracestate.Slice(begin).IndexOf(',');
|
||||
ReadOnlySpan<char> listMember;
|
||||
if (length != -1)
|
||||
{
|
||||
listMember = tracestate.Slice(begin, length).Trim();
|
||||
begin += length + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
listMember = tracestate.Slice(begin).Trim();
|
||||
begin = tracestate.Length;
|
||||
}
|
||||
|
||||
result.Append(tracestateCollection[i]);
|
||||
// https://github.com/w3c/trace-context/blob/master/spec/20-http_request_header_format.md#tracestate-header-field-values
|
||||
if (listMember.IsEmpty)
|
||||
{
|
||||
// Empty and whitespace - only list members are allowed.
|
||||
// Vendors MUST accept empty tracestate headers but SHOULD avoid sending them.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (keySet.Count >= 32)
|
||||
{
|
||||
// https://github.com/w3c/trace-context/blob/master/spec/20-http_request_header_format.md#list
|
||||
// test_tracestate_member_count_limit
|
||||
return false;
|
||||
}
|
||||
|
||||
int keyLength = listMember.IndexOf('=');
|
||||
if (keyLength == listMember.Length || keyLength == -1)
|
||||
{
|
||||
// Missing key or value in tracestate
|
||||
return false;
|
||||
}
|
||||
|
||||
var key = listMember.Slice(0, keyLength);
|
||||
if (!ValidateKey(key))
|
||||
{
|
||||
// test_tracestate_key_illegal_characters in https://github.com/w3c/trace-context/blob/master/test/test.py
|
||||
// test_tracestate_key_length_limit
|
||||
// test_tracestate_key_illegal_vendor_format
|
||||
return false;
|
||||
}
|
||||
|
||||
var value = listMember.Slice(keyLength + 1);
|
||||
if (!ValidateValue(value))
|
||||
{
|
||||
// test_tracestate_value_illegal_characters
|
||||
return false;
|
||||
}
|
||||
|
||||
// ValidateKey() call above has ensured the key does not contain upper case letters.
|
||||
if (!keySet.Add(key.ToString()))
|
||||
{
|
||||
// test_tracestate_duplicated_keys
|
||||
return false;
|
||||
}
|
||||
|
||||
if (result.Length > 0)
|
||||
{
|
||||
result.Append(',');
|
||||
}
|
||||
|
||||
result.Append(listMember.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
tracestateResult = result.ToString();
|
||||
|
|
@ -252,14 +320,125 @@ namespace OpenTelemetry.Context.Propagation
|
|||
|
||||
private static byte HexCharToByte(char c)
|
||||
{
|
||||
if (((c >= '0') && (c <= '9'))
|
||||
|| ((c >= 'a') && (c <= 'f'))
|
||||
|| ((c >= 'A') && (c <= 'F')))
|
||||
if ((c >= '0') && (c <= '9'))
|
||||
{
|
||||
return Convert.ToByte(c);
|
||||
return (byte)(c - '0');
|
||||
}
|
||||
|
||||
if ((c >= 'a') && (c <= 'f'))
|
||||
{
|
||||
return (byte)(c - 'a' + 10);
|
||||
}
|
||||
|
||||
throw new ArgumentOutOfRangeException(nameof(c), c, $"Invalid character: {c}.");
|
||||
}
|
||||
|
||||
private static bool ValidateKey(ReadOnlySpan<char> key)
|
||||
{
|
||||
// This implementation follows Trace Context v1 which has W3C Recommendation.
|
||||
// https://www.w3.org/TR/trace-context-1/#key
|
||||
// It will be slightly differently from the next version of specification in GitHub repository.
|
||||
|
||||
// There are two format for the key. The length rule applies to both.
|
||||
if (key.Length <= 0 || key.Length > TraceStateKeyMaxLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// The first format:
|
||||
// key = lcalpha 0*255( lcalpha / DIGIT / "_" / "-"/ "*" / "/" )
|
||||
// lcalpha = % x61 - 7A; a - z
|
||||
// (There is an inconsistency in the expression above and the description in note.
|
||||
// Here is following the description in note:
|
||||
// "Identifiers MUST begin with a lowercase letter or a digit.")
|
||||
if (!IsLowerAlphaDigit(key[0]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int tenantLength = -1;
|
||||
for (int i = 1; i < key.Length; ++i)
|
||||
{
|
||||
char ch = key[i];
|
||||
if (ch == '@')
|
||||
{
|
||||
tenantLength = i;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!(IsLowerAlphaDigit(ch)
|
||||
|| ch == '_'
|
||||
|| ch == '-'
|
||||
|| ch == '*'
|
||||
|| ch == '/'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (tenantLength == -1)
|
||||
{
|
||||
// There is no "@" sign. The key follow the first format.
|
||||
return true;
|
||||
}
|
||||
|
||||
// The second format:
|
||||
// key = (lcalpha / DIGIT) 0 * 240(lcalpha / DIGIT / "_" / "-" / "*" / "/") "@" lcalpha 0 * 13(lcalpha / DIGIT / "_" / "-" / "*" / "/")
|
||||
if (tenantLength == 0 || tenantLength > TraceStateKeyTenantMaxLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int vendorLength = key.Length - tenantLength - 1;
|
||||
if (vendorLength == 0 || vendorLength > TraceStateKeyVendorMaxLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = tenantLength + 1; i < key.Length; ++i)
|
||||
{
|
||||
char ch = key[i];
|
||||
if (!(IsLowerAlphaDigit(ch)
|
||||
|| ch == '_'
|
||||
|| ch == '-'
|
||||
|| ch == '*'
|
||||
|| ch == '/'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool ValidateValue(ReadOnlySpan<char> value)
|
||||
{
|
||||
// https://github.com/w3c/trace-context/blob/master/spec/20-http_request_header_format.md#value
|
||||
// value = 0*255(chr) nblk-chr
|
||||
// nblk - chr = % x21 - 2B / % x2D - 3C / % x3E - 7E
|
||||
// chr = % x20 / nblk - chr
|
||||
if (value.Length <= 0 || value.Length > TraceStateValueMaxLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < value.Length - 1; ++i)
|
||||
{
|
||||
char c = value[i];
|
||||
if (!(c >= 0x20 && c <= 0x7E && c != 0x2C && c != 0x3D))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
char last = value[value.Length - 1];
|
||||
return last >= 0x21 && last <= 0x7E && last != 0x2C && last != 0x3D;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsLowerAlphaDigit(char c)
|
||||
{
|
||||
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,8 +52,9 @@ namespace OpenTelemetry.Instrumentation.W3cTraceContext.Tests
|
|||
// Assert
|
||||
// Assert on the last line
|
||||
// TODO: fix W3C Trace Context test suite
|
||||
// ASP NET Core 2.1: FAILED (failures=4, errors=7)
|
||||
// ASP NET Core 3.1: FAILED (failures=6, errors=7)
|
||||
// ASP NET Core 2.1: FAILED (failures=1)
|
||||
// ASP NET Core 3.1: FAILED (failures=3)
|
||||
// ASP NET Core 5.0: FAILED (failures=3)
|
||||
string lastLine = ParseLastLine(result);
|
||||
this.output.WriteLine("result:" + result);
|
||||
Assert.StartsWith("FAILED", lastLine);
|
||||
|
|
|
|||
|
|
@ -1,180 +0,0 @@
|
|||
// <copyright file="TextMapPropagatorTest.cs" company="OpenTelemetry Authors">
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Xunit;
|
||||
|
||||
namespace OpenTelemetry.Context.Propagation.Tests
|
||||
{
|
||||
public class TextMapPropagatorTest
|
||||
{
|
||||
private const string TraceParent = "traceparent";
|
||||
private const string TraceState = "tracestate";
|
||||
private const string TraceId = "0af7651916cd43dd8448eb211c80319c";
|
||||
private const string SpanId = "b9c7c989f97918e1";
|
||||
|
||||
private static readonly string[] Empty = new string[0];
|
||||
private static readonly Func<IDictionary<string, string>, string, IEnumerable<string>> Getter = (headers, name) =>
|
||||
{
|
||||
if (headers.TryGetValue(name, out var value))
|
||||
{
|
||||
return new[] { value };
|
||||
}
|
||||
|
||||
return Empty;
|
||||
};
|
||||
|
||||
private static readonly Action<IDictionary<string, string>, string, string> Setter = (carrier, name, value) =>
|
||||
{
|
||||
carrier[name] = value;
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void CanParseExampleFromSpec()
|
||||
{
|
||||
var headers = new Dictionary<string, string>
|
||||
{
|
||||
{ TraceParent, $"00-{TraceId}-{SpanId}-01" },
|
||||
{ TraceState, $"congo=lZWRzIHRoNhcm5hbCBwbGVhc3VyZS4,rojo=00-{TraceId}-00f067aa0ba902b7-01" },
|
||||
};
|
||||
|
||||
var f = new TraceContextPropagator();
|
||||
var ctx = f.Extract(default, headers, Getter);
|
||||
|
||||
Assert.Equal(ActivityTraceId.CreateFromString(TraceId.AsSpan()), ctx.ActivityContext.TraceId);
|
||||
Assert.Equal(ActivitySpanId.CreateFromString(SpanId.AsSpan()), ctx.ActivityContext.SpanId);
|
||||
|
||||
Assert.True(ctx.ActivityContext.IsRemote);
|
||||
Assert.True(ctx.ActivityContext.IsValid());
|
||||
Assert.True((ctx.ActivityContext.TraceFlags & ActivityTraceFlags.Recorded) != 0);
|
||||
|
||||
Assert.Equal($"congo=lZWRzIHRoNhcm5hbCBwbGVhc3VyZS4,rojo=00-{TraceId}-00f067aa0ba902b7-01", ctx.ActivityContext.TraceState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NotSampled()
|
||||
{
|
||||
var headers = new Dictionary<string, string>
|
||||
{
|
||||
{ TraceParent, $"00-{TraceId}-{SpanId}-00" },
|
||||
};
|
||||
|
||||
var f = new TraceContextPropagator();
|
||||
var ctx = f.Extract(default, headers, Getter);
|
||||
|
||||
Assert.Equal(ActivityTraceId.CreateFromString(TraceId.AsSpan()), ctx.ActivityContext.TraceId);
|
||||
Assert.Equal(ActivitySpanId.CreateFromString(SpanId.AsSpan()), ctx.ActivityContext.SpanId);
|
||||
Assert.True((ctx.ActivityContext.TraceFlags & ActivityTraceFlags.Recorded) == 0);
|
||||
|
||||
Assert.True(ctx.ActivityContext.IsRemote);
|
||||
Assert.True(ctx.ActivityContext.IsValid());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsBlankIfNoHeader()
|
||||
{
|
||||
var headers = new Dictionary<string, string>();
|
||||
|
||||
var f = new TraceContextPropagator();
|
||||
var ctx = f.Extract(default, headers, Getter);
|
||||
|
||||
Assert.False(ctx.ActivityContext.IsValid());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsBlankIfInvalid()
|
||||
{
|
||||
var headers = new Dictionary<string, string>
|
||||
{
|
||||
{ TraceParent, $"00-xyz7651916cd43dd8448eb211c80319c-{SpanId}-01" },
|
||||
};
|
||||
|
||||
var f = new TraceContextPropagator();
|
||||
var ctx = f.Extract(default, headers, Getter);
|
||||
|
||||
Assert.False(ctx.ActivityContext.IsValid());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TracestateToStringEmpty()
|
||||
{
|
||||
var headers = new Dictionary<string, string>
|
||||
{
|
||||
{ TraceParent, $"00-{TraceId}-{SpanId}-01" },
|
||||
};
|
||||
|
||||
var f = new TraceContextPropagator();
|
||||
var ctx = f.Extract(default, headers, Getter);
|
||||
|
||||
Assert.Null(ctx.ActivityContext.TraceState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TracestateToString()
|
||||
{
|
||||
var headers = new Dictionary<string, string>
|
||||
{
|
||||
{ TraceParent, $"00-{TraceId}-{SpanId}-01" },
|
||||
{ TraceState, "k1=v1,k2=v2,k3=v3" },
|
||||
};
|
||||
|
||||
var f = new TraceContextPropagator();
|
||||
var ctx = f.Extract(default, headers, Getter);
|
||||
|
||||
Assert.Equal("k1=v1,k2=v2,k3=v3", ctx.ActivityContext.TraceState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Inject_NoTracestate()
|
||||
{
|
||||
var traceId = ActivityTraceId.CreateRandom();
|
||||
var spanId = ActivitySpanId.CreateRandom();
|
||||
var expectedHeaders = new Dictionary<string, string>
|
||||
{
|
||||
{ TraceParent, $"00-{traceId}-{spanId}-01" },
|
||||
};
|
||||
|
||||
var activityContext = new ActivityContext(traceId, spanId, ActivityTraceFlags.Recorded, traceState: null);
|
||||
PropagationContext propagationContext = new PropagationContext(activityContext, default);
|
||||
var carrier = new Dictionary<string, string>();
|
||||
var f = new TraceContextPropagator();
|
||||
f.Inject(propagationContext, carrier, Setter);
|
||||
|
||||
Assert.Equal(expectedHeaders, carrier);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Inject_WithTracestate()
|
||||
{
|
||||
var traceId = ActivityTraceId.CreateRandom();
|
||||
var spanId = ActivitySpanId.CreateRandom();
|
||||
var expectedHeaders = new Dictionary<string, string>
|
||||
{
|
||||
{ TraceParent, $"00-{traceId}-{spanId}-01" },
|
||||
{ TraceState, $"congo=lZWRzIHRoNhcm5hbCBwbGVhc3VyZS4,rojo=00-{traceId}-00f067aa0ba902b7-01" },
|
||||
};
|
||||
|
||||
var activityContext = new ActivityContext(traceId, spanId, ActivityTraceFlags.Recorded, expectedHeaders[TraceState]);
|
||||
PropagationContext propagationContext = new PropagationContext(activityContext, default);
|
||||
var carrier = new Dictionary<string, string>();
|
||||
var f = new TraceContextPropagator();
|
||||
f.Inject(propagationContext, carrier, Setter);
|
||||
|
||||
Assert.Equal(expectedHeaders, carrier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,331 @@
|
|||
// <copyright file="TraceContextPropagatorTest.cs" company="OpenTelemetry Authors">
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Xunit;
|
||||
|
||||
namespace OpenTelemetry.Context.Propagation.Tests
|
||||
{
|
||||
public class TraceContextPropagatorTest
|
||||
{
|
||||
private const string TraceParent = "traceparent";
|
||||
private const string TraceState = "tracestate";
|
||||
private const string TraceId = "0af7651916cd43dd8448eb211c80319c";
|
||||
private const string SpanId = "b9c7c989f97918e1";
|
||||
|
||||
private static readonly string[] Empty = new string[0];
|
||||
private static readonly Func<IDictionary<string, string>, string, IEnumerable<string>> Getter = (headers, name) =>
|
||||
{
|
||||
if (headers.TryGetValue(name, out var value))
|
||||
{
|
||||
return new[] { value };
|
||||
}
|
||||
|
||||
return Empty;
|
||||
};
|
||||
|
||||
private static readonly Func<IDictionary<string, string[]>, string, IEnumerable<string>> ArrayGetter = (headers, name) =>
|
||||
{
|
||||
if (headers.TryGetValue(name, out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return new string[] { };
|
||||
};
|
||||
|
||||
private static readonly Action<IDictionary<string, string>, string, string> Setter = (carrier, name, value) =>
|
||||
{
|
||||
carrier[name] = value;
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void CanParseExampleFromSpec()
|
||||
{
|
||||
var headers = new Dictionary<string, string>
|
||||
{
|
||||
{ TraceParent, $"00-{TraceId}-{SpanId}-01" },
|
||||
{ TraceState, $"congo=lZWRzIHRoNhcm5hbCBwbGVhc3VyZS4,rojo=00-{TraceId}-00f067aa0ba902b7-01" },
|
||||
};
|
||||
|
||||
var f = new TraceContextPropagator();
|
||||
var ctx = f.Extract(default, headers, Getter);
|
||||
|
||||
Assert.Equal(ActivityTraceId.CreateFromString(TraceId.AsSpan()), ctx.ActivityContext.TraceId);
|
||||
Assert.Equal(ActivitySpanId.CreateFromString(SpanId.AsSpan()), ctx.ActivityContext.SpanId);
|
||||
|
||||
Assert.True(ctx.ActivityContext.IsRemote);
|
||||
Assert.True(ctx.ActivityContext.IsValid());
|
||||
Assert.True((ctx.ActivityContext.TraceFlags & ActivityTraceFlags.Recorded) != 0);
|
||||
|
||||
Assert.Equal($"congo=lZWRzIHRoNhcm5hbCBwbGVhc3VyZS4,rojo=00-{TraceId}-00f067aa0ba902b7-01", ctx.ActivityContext.TraceState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NotSampled()
|
||||
{
|
||||
var headers = new Dictionary<string, string>
|
||||
{
|
||||
{ TraceParent, $"00-{TraceId}-{SpanId}-00" },
|
||||
};
|
||||
|
||||
var f = new TraceContextPropagator();
|
||||
var ctx = f.Extract(default, headers, Getter);
|
||||
|
||||
Assert.Equal(ActivityTraceId.CreateFromString(TraceId.AsSpan()), ctx.ActivityContext.TraceId);
|
||||
Assert.Equal(ActivitySpanId.CreateFromString(SpanId.AsSpan()), ctx.ActivityContext.SpanId);
|
||||
Assert.True((ctx.ActivityContext.TraceFlags & ActivityTraceFlags.Recorded) == 0);
|
||||
|
||||
Assert.True(ctx.ActivityContext.IsRemote);
|
||||
Assert.True(ctx.ActivityContext.IsValid());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsBlankIfNoHeader()
|
||||
{
|
||||
var headers = new Dictionary<string, string>();
|
||||
|
||||
var f = new TraceContextPropagator();
|
||||
var ctx = f.Extract(default, headers, Getter);
|
||||
|
||||
Assert.False(ctx.ActivityContext.IsValid());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsBlankIfInvalid()
|
||||
{
|
||||
var headers = new Dictionary<string, string>
|
||||
{
|
||||
{ TraceParent, $"00-xyz7651916cd43dd8448eb211c80319c-{SpanId}-01" },
|
||||
};
|
||||
|
||||
var f = new TraceContextPropagator();
|
||||
var ctx = f.Extract(default, headers, Getter);
|
||||
|
||||
Assert.False(ctx.ActivityContext.IsValid());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TracestateToStringEmpty()
|
||||
{
|
||||
var headers = new Dictionary<string, string>
|
||||
{
|
||||
{ TraceParent, $"00-{TraceId}-{SpanId}-01" },
|
||||
};
|
||||
|
||||
var f = new TraceContextPropagator();
|
||||
var ctx = f.Extract(default, headers, Getter);
|
||||
|
||||
Assert.Null(ctx.ActivityContext.TraceState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TracestateToString()
|
||||
{
|
||||
var headers = new Dictionary<string, string>
|
||||
{
|
||||
{ TraceParent, $"00-{TraceId}-{SpanId}-01" },
|
||||
{ TraceState, "k1=v1,k2=v2,k3=v3" },
|
||||
};
|
||||
|
||||
var f = new TraceContextPropagator();
|
||||
var ctx = f.Extract(default, headers, Getter);
|
||||
|
||||
Assert.Equal("k1=v1,k2=v2,k3=v3", ctx.ActivityContext.TraceState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Inject_NoTracestate()
|
||||
{
|
||||
var traceId = ActivityTraceId.CreateRandom();
|
||||
var spanId = ActivitySpanId.CreateRandom();
|
||||
var expectedHeaders = new Dictionary<string, string>
|
||||
{
|
||||
{ TraceParent, $"00-{traceId}-{spanId}-01" },
|
||||
};
|
||||
|
||||
var activityContext = new ActivityContext(traceId, spanId, ActivityTraceFlags.Recorded, traceState: null);
|
||||
PropagationContext propagationContext = new PropagationContext(activityContext, default);
|
||||
var carrier = new Dictionary<string, string>();
|
||||
var f = new TraceContextPropagator();
|
||||
f.Inject(propagationContext, carrier, Setter);
|
||||
|
||||
Assert.Equal(expectedHeaders, carrier);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Inject_WithTracestate()
|
||||
{
|
||||
var traceId = ActivityTraceId.CreateRandom();
|
||||
var spanId = ActivitySpanId.CreateRandom();
|
||||
var expectedHeaders = new Dictionary<string, string>
|
||||
{
|
||||
{ TraceParent, $"00-{traceId}-{spanId}-01" },
|
||||
{ TraceState, $"congo=lZWRzIHRoNhcm5hbCBwbGVhc3VyZS4,rojo=00-{traceId}-00f067aa0ba902b7-01" },
|
||||
};
|
||||
|
||||
var activityContext = new ActivityContext(traceId, spanId, ActivityTraceFlags.Recorded, expectedHeaders[TraceState]);
|
||||
PropagationContext propagationContext = new PropagationContext(activityContext, default);
|
||||
var carrier = new Dictionary<string, string>();
|
||||
var f = new TraceContextPropagator();
|
||||
f.Inject(propagationContext, carrier, Setter);
|
||||
|
||||
Assert.Equal(expectedHeaders, carrier);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DuplicateKeys()
|
||||
{
|
||||
// test_tracestate_duplicated_keys
|
||||
Assert.Empty(CallTraceContextPropagator("foo=1,foo=1"));
|
||||
Assert.Empty(CallTraceContextPropagator("foo=1,foo=2"));
|
||||
Assert.Empty(CallTraceContextPropagator(new string[] { "foo=1", "foo=1" }));
|
||||
Assert.Empty(CallTraceContextPropagator(new string[] { "foo=1", "foo=2" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Key_IllegalCharacters()
|
||||
{
|
||||
// test_tracestate_key_illegal_characters
|
||||
Assert.Empty(CallTraceContextPropagator("foo =1"));
|
||||
Assert.Empty(CallTraceContextPropagator("FOO =1"));
|
||||
Assert.Empty(CallTraceContextPropagator("foo.bar=1"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Key_IllegalVendorFormat()
|
||||
{
|
||||
// test_tracestate_key_illegal_vendor_format
|
||||
Assert.Empty(CallTraceContextPropagator("foo@=1,bar=2"));
|
||||
Assert.Empty(CallTraceContextPropagator("@foo=1,bar=2"));
|
||||
Assert.Empty(CallTraceContextPropagator("foo@@bar=1,bar=2"));
|
||||
Assert.Empty(CallTraceContextPropagator("foo@bar@baz=1,bar=2"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MemberCountLimit()
|
||||
{
|
||||
// test_tracestate_member_count_limit
|
||||
var output1 = CallTraceContextPropagator(new string[]
|
||||
{
|
||||
"bar01=01,bar02=02,bar03=03,bar04=04,bar05=05,bar06=06,bar07=07,bar08=08,bar09=09,bar10=10",
|
||||
"bar11=11,bar12=12,bar13=13,bar14=14,bar15=15,bar16=16,bar17=17,bar18=18,bar19=19,bar20=20",
|
||||
"bar21=21,bar22=22,bar23=23,bar24=24,bar25=25,bar26=26,bar27=27,bar28=28,bar29=29,bar30=30",
|
||||
"bar31=31,bar32=32",
|
||||
});
|
||||
var expected =
|
||||
"bar01=01,bar02=02,bar03=03,bar04=04,bar05=05,bar06=06,bar07=07,bar08=08,bar09=09,bar10=10" + "," +
|
||||
"bar11=11,bar12=12,bar13=13,bar14=14,bar15=15,bar16=16,bar17=17,bar18=18,bar19=19,bar20=20" + "," +
|
||||
"bar21=21,bar22=22,bar23=23,bar24=24,bar25=25,bar26=26,bar27=27,bar28=28,bar29=29,bar30=30" + "," +
|
||||
"bar31=31,bar32=32";
|
||||
Assert.Equal(expected, output1);
|
||||
|
||||
var output2 = CallTraceContextPropagator(new string[]
|
||||
{
|
||||
"bar01=01,bar02=02,bar03=03,bar04=04,bar05=05,bar06=06,bar07=07,bar08=08,bar09=09,bar10=10",
|
||||
"bar11=11,bar12=12,bar13=13,bar14=14,bar15=15,bar16=16,bar17=17,bar18=18,bar19=19,bar20=20",
|
||||
"bar21=21,bar22=22,bar23=23,bar24=24,bar25=25,bar26=26,bar27=27,bar28=28,bar29=29,bar30=30",
|
||||
"bar31=31,bar32=32,bar33=33",
|
||||
});
|
||||
Assert.Empty(output2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Key_KeyLengthLimit()
|
||||
{
|
||||
// test_tracestate_key_length_limit
|
||||
string input1 = new string('z', 256) + "=1";
|
||||
Assert.Equal(input1, CallTraceContextPropagator(input1));
|
||||
Assert.Empty(CallTraceContextPropagator(new string('z', 257) + "=1"));
|
||||
string input2 = new string('t', 241) + "@" + new string('v', 14) + "=1";
|
||||
Assert.Equal(input2, CallTraceContextPropagator(input2));
|
||||
Assert.Empty(CallTraceContextPropagator(new string('t', 242) + "@v=1"));
|
||||
Assert.Empty(CallTraceContextPropagator("t@" + new string('v', 15) + "=1"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Value_IllegalCharacters()
|
||||
{
|
||||
// test_tracestate_value_illegal_characters
|
||||
Assert.Empty(CallTraceContextPropagator("foo=bar=baz"));
|
||||
Assert.Empty(CallTraceContextPropagator("foo=,bar=3"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Traceparent_Version()
|
||||
{
|
||||
// test_traceparent_version_0x00
|
||||
Assert.NotEqual(
|
||||
"12345678901234567890123456789012",
|
||||
CallTraceContextPropagatorWithTraceParent("00-12345678901234567890123456789012-1234567890123456-01."));
|
||||
Assert.NotEqual(
|
||||
"12345678901234567890123456789012",
|
||||
CallTraceContextPropagatorWithTraceParent("00-12345678901234567890123456789012-1234567890123456-01-what-the-future-will-be-like"));
|
||||
|
||||
// test_traceparent_version_0xcc
|
||||
Assert.Equal(
|
||||
"12345678901234567890123456789012",
|
||||
CallTraceContextPropagatorWithTraceParent("cc-12345678901234567890123456789012-1234567890123456-01"));
|
||||
Assert.Equal(
|
||||
"12345678901234567890123456789012",
|
||||
CallTraceContextPropagatorWithTraceParent("cc-12345678901234567890123456789012-1234567890123456-01-what-the-future-will-be-like"));
|
||||
Assert.NotEqual(
|
||||
"12345678901234567890123456789012",
|
||||
CallTraceContextPropagatorWithTraceParent("cc-12345678901234567890123456789012-1234567890123456-01.what-the-future-will-be-like"));
|
||||
|
||||
// test_traceparent_version_0xff
|
||||
Assert.NotEqual(
|
||||
"12345678901234567890123456789012",
|
||||
CallTraceContextPropagatorWithTraceParent("ff-12345678901234567890123456789012-1234567890123456-01"));
|
||||
}
|
||||
|
||||
private static string CallTraceContextPropagatorWithTraceParent(string traceparent)
|
||||
{
|
||||
var headers = new Dictionary<string, string>
|
||||
{
|
||||
{ TraceParent, traceparent },
|
||||
};
|
||||
var f = new TraceContextPropagator();
|
||||
var ctx = f.Extract(default, headers, Getter);
|
||||
return ctx.ActivityContext.TraceId.ToString();
|
||||
}
|
||||
|
||||
private static string CallTraceContextPropagator(string tracestate)
|
||||
{
|
||||
var headers = new Dictionary<string, string>
|
||||
{
|
||||
{ TraceParent, $"00-{TraceId}-{SpanId}-01" },
|
||||
{ TraceState, tracestate },
|
||||
};
|
||||
var f = new TraceContextPropagator();
|
||||
var ctx = f.Extract(default, headers, Getter);
|
||||
return ctx.ActivityContext.TraceState;
|
||||
}
|
||||
|
||||
private static string CallTraceContextPropagator(string[] tracestate)
|
||||
{
|
||||
var headers = new Dictionary<string, string[]>
|
||||
{
|
||||
{ TraceParent, new string[] { $"00-{TraceId}-{SpanId}-01" } },
|
||||
{ TraceState, tracestate },
|
||||
};
|
||||
var f = new TraceContextPropagator();
|
||||
var ctx = f.Extract(default, headers, ArrayGetter);
|
||||
return ctx.ActivityContext.TraceState;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue