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:
xiang17 2021-01-06 10:38:49 -08:00 committed by GitHub
parent e24dccbe9f
commit c188edb48f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 525 additions and 194 deletions

View File

@ -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');
}
}
}

View File

@ -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);

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}