From 04422c6fbd085214d1faaef2ed6a009e1399328f Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Thu, 25 Jan 2018 22:23:16 -0600 Subject: [PATCH 1/6] added MockTracer and MockTracer.Tests projects --- OpenTracing.sln | 19 +- src/OpenTracing.MockTracer/MockSpan.cs | 344 ++++++++++++++++++ src/OpenTracing.MockTracer/MockTracer.cs | 225 ++++++++++++ .../OpenTracing.MockTracer.csproj | 16 + .../MockSpanSpecs.cs | 66 ++++ .../MockTracerSpecs.cs | 249 +++++++++++++ .../OpenTracing.MockTracer.Tests.csproj | 18 + 7 files changed, 936 insertions(+), 1 deletion(-) create mode 100644 src/OpenTracing.MockTracer/MockSpan.cs create mode 100644 src/OpenTracing.MockTracer/MockTracer.cs create mode 100644 src/OpenTracing.MockTracer/OpenTracing.MockTracer.csproj create mode 100644 test/OpenTracing.MockTracer.Tests/MockSpanSpecs.cs create mode 100644 test/OpenTracing.MockTracer.Tests/MockTracerSpecs.cs create mode 100644 test/OpenTracing.MockTracer.Tests/OpenTracing.MockTracer.Tests.csproj diff --git a/OpenTracing.sln b/OpenTracing.sln index e98dd66..433b64f 100644 --- a/OpenTracing.sln +++ b/OpenTracing.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26403.7 +VisualStudioVersion = 15.0.27004.2010 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{CA88CFB0-F5DF-411B-B083-D54819E9414F}" EndProject @@ -11,6 +11,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTracing", "src\OpenTrac EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTracing.Tests", "test\OpenTracing.Tests\OpenTracing.Tests.csproj", "{F82B4191-F9D1-4656-8585-840E6BA045DD}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenTracing.MockTracer", "src\OpenTracing.MockTracer\OpenTracing.MockTracer.csproj", "{7D6D324D-299D-4BD5-82FF-A887A015770E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenTracing.MockTracer.Tests", "test\OpenTracing.MockTracer.Tests\OpenTracing.MockTracer.Tests.csproj", "{21880C2E-F25F-4D1B-BE70-1BC769B58ABE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -25,6 +29,14 @@ Global {F82B4191-F9D1-4656-8585-840E6BA045DD}.Debug|Any CPU.Build.0 = Debug|Any CPU {F82B4191-F9D1-4656-8585-840E6BA045DD}.Release|Any CPU.ActiveCfg = Release|Any CPU {F82B4191-F9D1-4656-8585-840E6BA045DD}.Release|Any CPU.Build.0 = Release|Any CPU + {7D6D324D-299D-4BD5-82FF-A887A015770E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D6D324D-299D-4BD5-82FF-A887A015770E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D6D324D-299D-4BD5-82FF-A887A015770E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7D6D324D-299D-4BD5-82FF-A887A015770E}.Release|Any CPU.Build.0 = Release|Any CPU + {21880C2E-F25F-4D1B-BE70-1BC769B58ABE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {21880C2E-F25F-4D1B-BE70-1BC769B58ABE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {21880C2E-F25F-4D1B-BE70-1BC769B58ABE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {21880C2E-F25F-4D1B-BE70-1BC769B58ABE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -32,5 +44,10 @@ Global GlobalSection(NestedProjects) = preSolution {60B9BDE0-8203-46C9-9373-E06FC82CECB3} = {CA88CFB0-F5DF-411B-B083-D54819E9414F} {F82B4191-F9D1-4656-8585-840E6BA045DD} = {FC8B6803-6F66-4F1E-9034-5564B2D4FF9C} + {7D6D324D-299D-4BD5-82FF-A887A015770E} = {CA88CFB0-F5DF-411B-B083-D54819E9414F} + {21880C2E-F25F-4D1B-BE70-1BC769B58ABE} = {FC8B6803-6F66-4F1E-9034-5564B2D4FF9C} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {8D3292AC-CCF6-4D6E-B69D-63263056DD87} EndGlobalSection EndGlobal diff --git a/src/OpenTracing.MockTracer/MockSpan.cs b/src/OpenTracing.MockTracer/MockSpan.cs new file mode 100644 index 0000000..16ba9c1 --- /dev/null +++ b/src/OpenTracing.MockTracer/MockSpan.cs @@ -0,0 +1,344 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace OpenTracing.MockTracer +{ + /// + public class MockSpan : ISpan + { + /// + /// Used to monotonically update ids + /// + private static long _nextIdCounter = 0; + + public static long NextId() + { + return Interlocked.Increment(ref _nextIdCounter); + } + + public MockSpan(MockTracer tracer, string operationName, DateTimeOffset startTime, Dictionary initialTags, List refs) + { + StartTime = startTime; + _tracer = tracer; + OperationName = operationName; + + if (initialTags == null) + { + _tags = new Dictionary(); + } + else + { + _tags = initialTags; + } + + if (_references == null) + { + _references = refs; + } + else + { + _references = new List(); + } + + var parentContext = FindPreferredParentRef(_references); + + if (parentContext == null) // we are a root span + { + _context = new MockContext(NextId(), NextId(), new Dictionary()); + ParentId = 0; + } + else // we are a child span + { + _context = new MockContext(parentContext.TraceId, NextId(), MergeBaggages(_references)); + ParentId = parentContext.SpanId; + } + + } + + private MockContext _context; + + /// + /// Id of the parent span. Set to 0 if there is no parent. + /// + public long ParentId { get; } + + public long TraceId => _context.TraceId; + public long SpanId => _context.SpanId; + + private readonly MockTracer _tracer; + + public DateTimeOffset StartTime { get; private set; } + public DateTimeOffset FinishTime { get; private set; } + + public string OperationName { get; private set; } + private readonly Dictionary _tags; + private readonly List _logs = new List(); + private readonly List _references; + private readonly ConcurrentBag _exceptions = new ConcurrentBag(); + + public IReadOnlyCollection Errors => _exceptions.ToList(); // don't need this ToList call in later versions of .NET Standard + + public bool IsFinished { get; private set; } + + public IReadOnlyList Logs => _logs; + + public IReadOnlyDictionary Tags => _tags; + + public void Dispose() + { + Dispose(true); + } + + private void Dispose(bool isDisposing) + { + if (isDisposing && !IsFinished) + { + Finish(); + } + } + + public ISpan SetOperationName(string operationName) + { + CheckForFinished("Setting operationName [{0}] on already finished span", operationName); + OperationName = operationName; + return this; + } + + public ISpan SetTag(string key, bool value) + { + return SetObjectTag(key, value); + } + + public ISpan SetTag(string key, double value) + { + return SetObjectTag(key, value); + } + + public ISpan SetTag(string key, int value) + { + return SetObjectTag(key, value); + } + + public ISpan SetTag(string key, string value) + { + return SetObjectTag(key, value); + } + + private ISpan SetObjectTag(string key, object value) + { + CheckForFinished("Setting tag [{0}:{1}] on already finished span", key, value); + _tags[key] = value; + return this; + } + + public ISpan Log(IEnumerable> fields) + { + return Log(new LogEvent(fields.ToDictionary(k => k.Key, v => v.Value))); + } + public ISpan Log(DateTimeOffset timestamp, IEnumerable> fields) + { + return Log(new LogEvent(timestamp, fields.ToDictionary(k => k.Key, v => v.Value))); + } + + public ISpan Log(string eventName) + { + return Log(new LogEvent(eventName)); + } + + public ISpan Log(DateTimeOffset timestamp, string eventName) + { + return Log(new LogEvent(timestamp, eventName)); + } + + private ISpan Log(LogEvent log) + { + CheckForFinished("Adding logs {0} to already finished span.", log); + _logs.Add(log); + return this; + } + + public ISpan SetBaggageItem(string key, string value) + { + CheckForFinished("Adding baggage [{0}:{1}] to already finished span.", key, value); + _context = _context.WithBaggageItem(key, value); + return this; + } + + public string GetBaggageItem(string key) + { + return _context.GetBaggageItem(key); + } + + public void Finish() + { + Finish(DateTimeOffset.UtcNow); + } + + public void Finish(DateTimeOffset finishTimestamp) + { + CheckForFinished("Tried to finish already finished span"); + FinishTime = finishTimestamp; + _tracer.FinishSpan(this); + IsFinished = true; + } + + public ISpan AddReference(Reference reference) + { + _references.Add(reference); + return this; + } + + public IReadOnlyList References => _references; + + public ISpanContext Context => _context; + + private static MockContext FindPreferredParentRef(IList references) + { + if (!references.Any()) + return null; + + foreach (var reference in references) // return the context of the parent, if applicable + { + if (OpenTracing.References.ChildOf.Equals(reference.ReferenceType)) + return reference.Context; + } + + // otherwise, return the context of the first reference + return references.First().Context; + } + + private static Dictionary MergeBaggages(IList references) + { + var baggage = new Dictionary(); + foreach (var reference in references) + { + if (reference.Context.GetBaggageItems() != null) + { + foreach (var bagItem in reference.Context.GetBaggageItems()) + { + baggage[bagItem.Key] = bagItem.Value; + } + } + } + + return baggage; + } + + private void CheckForFinished(string format, params object[] args) + { + if (IsFinished) + { + var ex = new InvalidOperationException(string.Format(format, args)); + _exceptions.Add(ex); + throw ex; + } + } + + public override string ToString() + { + return $"TraceId: {TraceId}, SpanId: {SpanId}, ParentId: {ParentId}, OperationName: {OperationName}"; + } + + /// + /// INTERNAL API. + /// + /// Internal representation of a log entry inside + /// + public sealed class LogEvent + { + public LogEvent(string eventName) + : this((IReadOnlyDictionary)new Dictionary() { { "event", eventName } }) { } + + public LogEvent(DateTimeOffset timestamp, string eventName) + : this(timestamp, (IReadOnlyDictionary)new Dictionary() { { "event", eventName } }) { } + + public LogEvent(IReadOnlyDictionary fields) + : this(DateTimeOffset.UtcNow, fields) + { + } + + public LogEvent(DateTimeOffset timeStamp, IReadOnlyDictionary fields) + { + TimeStamp = timeStamp; + Fields = fields; + } + + public DateTimeOffset TimeStamp { get; private set; } + + public IReadOnlyDictionary Fields { get; private set; } + } + + public sealed class MockContext : ISpanContext + { + private readonly IDictionary _baggageItems; + + public long TraceId { get; private set; } + + public long SpanId { get; private set; } + + public MockContext(long traceId, long spanId) : this(traceId, spanId, new Dictionary()) { } + + public MockContext(long traceId, long spanId, IDictionary baggageItems) + { + TraceId = traceId; + SpanId = spanId; + _baggageItems = baggageItems; + } + + public IEnumerable> GetBaggageItems() + { + return _baggageItems; + } + + public string GetBaggageItem(string key) + { + if(_baggageItems.ContainsKey(key)) + return _baggageItems[key]; + return null; + } + + public MockContext WithBaggageItem(string key, string val) + { + return new MockContext(TraceId, SpanId, _baggageItems.Concat(new[] { new KeyValuePair(key, val) }) + .ToDictionary(k => k.Key, v => v.Value)); + } + } + + public class Reference + { + public Reference(MockContext context, string referenceType) + { + Context = context; + ReferenceType = referenceType; + } + + public MockContext Context { get; } + + /// + /// Per + /// + public string ReferenceType { get; } + + protected bool Equals(Reference other) + { + return Context.Equals(other.Context); + } + + public override bool Equals(object obj) + { + if (Object.ReferenceEquals(null, obj)) return false; + if (Object.ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((Reference)obj); + } + + public override int GetHashCode() + { + return Context.GetHashCode(); + } + } + } +} \ No newline at end of file diff --git a/src/OpenTracing.MockTracer/MockTracer.cs b/src/OpenTracing.MockTracer/MockTracer.cs new file mode 100644 index 0000000..27d5595 --- /dev/null +++ b/src/OpenTracing.MockTracer/MockTracer.cs @@ -0,0 +1,225 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using OpenTracing.Propagation; + +namespace OpenTracing.MockTracer +{ + /// + /// In-memory implementation designed to make it + /// easy to test assertions against the OpenMetrics API and its use + /// inside frameworks. + /// + public class MockTracer : ITracer + { + private ConcurrentQueue _finishedSpans = new ConcurrentQueue(); + private readonly IPropagator _propagator; + + public static readonly TextPropagator TextMapPropagator = new TextPropagator(); + + public MockTracer() : this(TextMapPropagator) + { + } + + public MockTracer(IPropagator propagator) + { + _propagator = propagator; + } + + public void FinishSpan(MockSpan span) + { + _finishedSpans.Enqueue(span); + } + + public IEnumerable FinishedSpans => _finishedSpans; + + /// + /// Clears all of the current recorded instances. + /// + public void Reset() + { + _finishedSpans = new ConcurrentQueue(); + } + + public ISpanBuilder BuildSpan(string operationName) + { + return new MockSpanBuilder(this, operationName); + } + + public void Inject(ISpanContext spanContext, Format format, TCarrier carrier) + { + _propagator.Inject((MockSpan.MockContext)spanContext, format, carrier); + } + + public ISpanContext Extract(Format format, TCarrier carrier) + { + return _propagator.Extract(format, carrier); + } + + /// + /// Allows the developer to inject into the and calls. + /// + public interface IPropagator + { + void Inject(MockSpan.MockContext context, Format format, TCarrier carrier); + + MockSpan.MockContext Extract(Format format, TCarrier carrier); + } + + /// + /// implementation that uses internally. + /// + public sealed class TextPropagator : IPropagator + { + public const string SpanIdKey = "spanid"; + public const string TraceIdKey = "traceid"; + public const string BaggageKeyPrefix = "baggage-"; + + public void Inject(MockSpan.MockContext context, Format format, TCarrier carrier) + { + if (carrier is ITextMap text) + { + foreach (var entry in context.GetBaggageItems()) + { + text.Set(BaggageKeyPrefix + entry.Key, entry.Value); + } + text.Set(SpanIdKey, context.SpanId.ToString()); + text.Set(TraceIdKey, context.TraceId.ToString()); + } + else + { + throw new UnsupportedFormatException($"unknown carrier [{carrier.GetType()}]"); + } + } + + public MockSpan.MockContext Extract(Format format, TCarrier carrier) + { + long? traceId = null; + long? spanId = null; + Dictionary baggage = new Dictionary(); + + if (carrier is ITextMap text) + { + foreach (var entry in text.GetEntries()) + { + if (TraceIdKey.Equals(entry.Key)) + { + traceId = Convert.ToInt64(entry.Value); + } + else if (SpanIdKey.Equals(entry.Key)) + { + spanId = Convert.ToInt64(entry.Value); + } + else if(entry.Key.StartsWith(BaggageKeyPrefix)) + { + var key = entry.Key.Substring(BaggageKeyPrefix.Length); + baggage[key] = entry.Value; + } + } + } + else + { + throw new UnsupportedFormatException($"unknown carrier [{carrier.GetType()}]"); + } + + if (traceId.HasValue && spanId.HasValue) + { + return new MockSpan.MockContext(traceId.Value, spanId.Value, baggage); + } + + return null; + } + } + + public sealed class MockSpanBuilder : ISpanBuilder + { + private readonly MockTracer _tracer; + private readonly string _operationName; + private DateTimeOffset _startTime = DateTimeOffset.MinValue; + private readonly List _spanReferences = new List(); + private readonly Dictionary _initialTags = new Dictionary(); + + public MockSpanBuilder(MockTracer tracer, string operationName) + { + _tracer = tracer; + _operationName = operationName; + } + + public ISpanBuilder AsChildOf(ISpan parent) + { + if (parent == null) + return this; + return AsChildOf(parent.Context); + } + + public ISpanBuilder AsChildOf(ISpanContext parent) + { + if (parent == null) + return this; + return AddReference(References.ChildOf, parent); + } + + public ISpanBuilder FollowsFrom(ISpan parent) + { + return FollowsFrom(parent.Context); + } + + public ISpanBuilder FollowsFrom(ISpanContext parent) + { + return AddReference(References.FollowsFrom, parent); + } + + public ISpanBuilder AddReference(string referenceType, ISpanContext referencedContext) + { + if (referencedContext != null) + { + _spanReferences.Add(new MockSpan.Reference((MockSpan.MockContext)referencedContext, referenceType)); + } + + return this; + } + + public ISpanBuilder WithTag(string key, bool value) + { + _initialTags[key] = value; + return this; + } + + public ISpanBuilder WithTag(string key, double value) + { + _initialTags[key] = value; + return this; + } + + public ISpanBuilder WithTag(string key, int value) + { + _initialTags[key] = value; + return this; + } + + public ISpanBuilder WithTag(string key, string value) + { + _initialTags[key] = value; + return this; + } + + public ISpanBuilder WithStartTimestamp(DateTimeOffset startTimestamp) + { + _startTime = startTimestamp; + return this; + } + + public ISpan Start() + { + if (_startTime == DateTimeOffset.MinValue) // value was not set by builder + { + _startTime = DateTimeOffset.UtcNow; + } + + return new MockSpan(_tracer, _operationName, _startTime, _initialTags, _spanReferences); + } + } + } + + +} diff --git a/src/OpenTracing.MockTracer/OpenTracing.MockTracer.csproj b/src/OpenTracing.MockTracer/OpenTracing.MockTracer.csproj new file mode 100644 index 0000000..72761e1 --- /dev/null +++ b/src/OpenTracing.MockTracer/OpenTracing.MockTracer.csproj @@ -0,0 +1,16 @@ + + + + netstandard1.1 + + This package implements a MockTracer implementation of the OpenTracing.ITracer interface, intended to be used + for unit-testing ITracer implementations in third party consuming frameworks and drivers. + + opentracing;distributed-tracing;tracing;logging + + + + + + + diff --git a/test/OpenTracing.MockTracer.Tests/MockSpanSpecs.cs b/test/OpenTracing.MockTracer.Tests/MockSpanSpecs.cs new file mode 100644 index 0000000..022d4cf --- /dev/null +++ b/test/OpenTracing.MockTracer.Tests/MockSpanSpecs.cs @@ -0,0 +1,66 @@ +using System; +using System.Linq; +using FluentAssertions; +using Xunit; + +namespace OpenTracing.MockTracer.Tests +{ + public class MockSpanSpecs + { + [Fact] + public void SetOperationNameAfterFinishShouldThrow() + { + var tracer = new OpenTracing.MockTracer.MockTracer(); + var span = tracer.BuildSpan("foo").Start(); + span.Finish(); + + Action act = () => span.SetOperationName("bar"); + + act.ShouldThrow(); + + tracer.FinishedSpans.First().Errors.Count.Should().Be(1); + } + + [Fact] + public void SetTagAFterFinishShouldThrow() + { + var tracer = new MockTracer(); + var span = tracer.BuildSpan("foo").Start(); + span.Finish(); + + Action act = () => span.SetTag("foo", "bar"); + + act.ShouldThrow(); + + tracer.FinishedSpans.First().Errors.Count.Should().Be(1); + } + + [Fact] + public void AddLogAFterFinishShouldThrow() + { + var tracer = new MockTracer(); + var span = tracer.BuildSpan("foo").Start(); + span.Finish(); + + Action act = () => span.Log("test"); + + act.ShouldThrow(); + + tracer.FinishedSpans.First().Errors.Count.Should().Be(1); + } + + [Fact] + public void AddBaggageAFterFinishShouldThrow() + { + var tracer = new MockTracer(); + var span = tracer.BuildSpan("foo").Start(); + span.Finish(); + + Action act = () => span.SetBaggageItem("foo", "bar"); + + act.ShouldThrow(); + + tracer.FinishedSpans.First().Errors.Count.Should().Be(1); + } + } +} \ No newline at end of file diff --git a/test/OpenTracing.MockTracer.Tests/MockTracerSpecs.cs b/test/OpenTracing.MockTracer.Tests/MockTracerSpecs.cs new file mode 100644 index 0000000..9c370eb --- /dev/null +++ b/test/OpenTracing.MockTracer.Tests/MockTracerSpecs.cs @@ -0,0 +1,249 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using OpenTracing.Propagation; +using Xunit; + +namespace OpenTracing.MockTracer.Tests +{ + public class MockTracerSpecs + { + [Fact(DisplayName = "Root span's fields should all be set correctly")] + public void RootSpanShouldHavePropertiesSetCorrectly() + { + var tracer = new MockTracer(); + var span = tracer.BuildSpan("foo").WithTag("actorId", 1) + .WithTag("actorPath", "/user/foo/test") + .WithTag("singleton", false) + .WithStartTimestamp(DateTimeOffset.UtcNow.AddMilliseconds(-10)).Start(); + + var timeStamp = DateTimeOffset.UtcNow.AddMilliseconds(-8); + span.Log(timeStamp, "foo"); + span.Log(timeStamp.AddMilliseconds(1), new[] { new KeyValuePair("foo1", true) }); + span.SetBaggageItem("baggageIsDifferentThanTags", "apparently"); + + span.Finish(); + + var testSpan = (MockSpan)span; + + var recordedSpans = tracer.FinishedSpans; + recordedSpans.First().Should().Be(testSpan); + + testSpan.IsFinished.Should().BeTrue(); + testSpan.References.Should().BeEmpty("We did not set any reference"); + testSpan.Context.As().SpanId.Should() + .NotBe(testSpan.Context.As().TraceId, "traceId and spanId should be set independently"); + testSpan.Errors.Should().BeEmpty(); + testSpan.FinishTime.Should().BeAfter(testSpan.StartTime); + testSpan.Tags.Keys.Should().BeEquivalentTo("actorId", "actorPath", "singleton"); + testSpan.Tags.Values.Should().BeEquivalentTo(1, "/user/foo/test", false); + testSpan.Logs.Count.Should().Be(2); + + var log1 = testSpan.Logs[0]; + log1.Fields.Count.Should().Be(1); + log1.Fields["event"].Should().Be("foo"); + log1.TimeStamp.Should().Be(timeStamp); + + var log2 = testSpan.Logs[1]; + log2.Fields.Count.Should().Be(1); + log2.Fields["foo1"].Should().Be(true); + log2.TimeStamp.Should().Be(timeStamp.AddMilliseconds(1)); + + var baggage = testSpan.Context.GetBaggageItems().ToList(); + baggage.Count.Should().Be(1); + testSpan.GetBaggageItem("baggageIsDifferentThanTags").Should().Be("apparently"); + } + + [Fact(DisplayName = "Child span's fields should be set correctly")] + public void ChildSpanPropertiesShouldBeSetCorrectly() + { + // create and finish a root span + var tracer = new MockTracer(); + using (var parent = tracer.BuildSpan("parent").WithStartTimestamp(DateTimeOffset.UtcNow).Start()) + { + var child = tracer.BuildSpan("child").WithStartTimestamp(DateTimeOffset.UtcNow.AddMilliseconds(100)) + .AsChildOf(parent).Start(); + + child.Finish(DateTimeOffset.UtcNow.AddMilliseconds(900)); + parent.Finish(DateTimeOffset.UtcNow.AddMilliseconds(1000)); + } + + var finishedSpans = tracer.FinishedSpans.ToList(); + + + finishedSpans.Count.Should().Be(2); + var c = finishedSpans[0]; + var p = finishedSpans[1]; + c.OperationName.Should().Be("child"); + p.OperationName.Should().Be("parent"); + p.SpanId.Should().Be(c.ParentId); + p.TraceId.Should().Be(c.TraceId); + c.SpanId.Should().BeGreaterThan(p.SpanId); + } + + [Fact(DisplayName = "Spans should use explicit timestamps when specified")] + public void ExplicitTimeStampShouldBeUsed() + { + // create and finish a root span + var tracer = new MockTracer(); + var startTime = DateTimeOffset.UtcNow.AddMilliseconds(-100); + tracer.BuildSpan("foo").WithStartTimestamp(startTime).Start().Finish(); + + var finishedSpan = tracer.FinishedSpans.First(); + finishedSpan.StartTime.Equals(startTime); + } + + [Fact(DisplayName = "TextMapPropagator should work as expected with TextMap format")] + public void TextMapPropagatorShouldWorkWithTextMap() + { + var tracer = new MockTracer(MockTracer.TextMapPropagator); + var injectMap = new Dictionary {["foobag"] = "donttouch"}; + + var parentSpan = tracer.BuildSpan("foo").Start(); + parentSpan.SetBaggageItem("foobag", "fooitem"); + parentSpan.Finish(); + + tracer.Inject(parentSpan.Context, Formats.TextMap, new DictionaryCarrier(injectMap)); + + var extract = tracer.Extract(Formats.TextMap, new DictionaryCarrier(injectMap)); + + var childSpan = tracer.BuildSpan("bar") + .AsChildOf(extract).Start(); + childSpan.SetBaggageItem("barbag", "baritem"); + childSpan.Finish(); + + var finishedSpans = tracer.FinishedSpans.ToList(); + + finishedSpans.Count.Should().Be(2); + finishedSpans[0].TraceId.Should().Be(finishedSpans[1].TraceId); + finishedSpans[0].SpanId.Should().Be(finishedSpans[1].ParentId); + finishedSpans[0].SpanId.Should().NotBe(finishedSpans[1].SpanId); + finishedSpans[0].GetBaggageItem("foobag").Should().Be("fooitem"); + finishedSpans[0].GetBaggageItem("barbag").Should().BeNullOrEmpty(); + finishedSpans[1].GetBaggageItem("foobag").Should().Be("fooitem"); + finishedSpans[1].GetBaggageItem("barbag").Should().Be("baritem"); + injectMap["foobag"].Should().Be("donttouch"); + } + + [Fact(DisplayName = "TextMapPropagator should work as expected with HTTP Headers format")] + public void TextMapPropagatorShouldWorkWithHttpHeaders() + { + var tracer = new MockTracer(MockTracer.TextMapPropagator); + var injectMap = new Dictionary(); + + var parentSpan = tracer.BuildSpan("foo").Start(); + parentSpan.Finish(); + + tracer.Inject(parentSpan.Context, Formats.HttpHeaders, new DictionaryCarrier(injectMap)); + + var extract = tracer.Extract(Formats.HttpHeaders, new DictionaryCarrier(injectMap)); + + tracer.BuildSpan("bar") + .AsChildOf(extract).Start().Finish(); + + var finishedSpans = tracer.FinishedSpans.ToList(); + + finishedSpans.Count.Should().Be(2); + finishedSpans[0].TraceId.Should().Be(finishedSpans[1].TraceId); + finishedSpans[0].SpanId.Should().Be(finishedSpans[1].ParentId); + finishedSpans[0].SpanId.Should().NotBe(finishedSpans[1].SpanId); + } + + [Fact(DisplayName = "MockTracer.Reset should clear all finished spans from memory.")] + public void MockTracerResetShouldClearFinishedSpans() + { + var tracer = new MockTracer(); + + tracer.BuildSpan("foo").Start().Finish(); + + tracer.FinishedSpans.Count().Should().Be(1); + tracer.Reset(); + tracer.FinishedSpans.Count().Should().Be(0); + } + + [Fact(DisplayName = "FollowFrom references should be set correctly")] + public void FollowFromReferenceSpec() + { + var tracer = new MockTracer(MockTracer.TextMapPropagator); + + var precedent = (MockSpan)tracer.BuildSpan("precedent").Start(); + var following = (MockSpan)tracer.BuildSpan("follows") + .AddReference(References.FollowsFrom, precedent.Context) + .Start(); + + precedent.SpanId.Should().Be(following.ParentId); // should still be parent ID, since there's no explicit childof + following.References.Count.Should().Be(1); + + var followingRef = following.References[0]; + + new MockSpan.Reference((MockSpan.MockContext)precedent.Context, References.FollowsFrom).Should().Be(followingRef); + } + + [Fact(DisplayName = "Spans with multiple references should be set correctly")] + public void MultiReferenceSpec() + { + var tracer = new MockTracer(MockTracer.TextMapPropagator); + var parent = (MockSpan)tracer.BuildSpan("parent").Start(); + var precedent = (MockSpan)tracer.BuildSpan("precedent").Start(); + + var followingSpan = (MockSpan)tracer.BuildSpan("follows") + .AddReference(References.FollowsFrom, precedent.Context) + .AsChildOf(parent.Context) + .Start(); + + parent.SpanId.Should().Be(followingSpan.ParentId); + followingSpan.References.Count.Should().Be(2); + + var followsFromRef = followingSpan.References[0]; + var parentRef = followingSpan.References[1]; + + new MockSpan.Reference((MockSpan.MockContext)precedent.Context, References.FollowsFrom).Should() + .Be(followsFromRef); + new MockSpan.Reference((MockSpan.MockContext)parent.Context, References.ChildOf).Should() + .Be(parentRef); + } + + [Fact(DisplayName = "Spans with multiple references should merge baggage correctly")] + public void MultiReferencesBaggageSpec() + { + var tracer = new MockTracer(MockTracer.TextMapPropagator); + var parent = (MockSpan)tracer.BuildSpan("parent").Start(); + parent.SetBaggageItem("parent", "foo"); + var precedent = (MockSpan)tracer.BuildSpan("precedent").Start(); + precedent.SetBaggageItem("precedent", "bar"); + + var followingSpan = (MockSpan)tracer.BuildSpan("follows") + .AddReference(References.FollowsFrom, precedent.Context) + .AsChildOf(parent.Context) + .Start(); + + followingSpan.GetBaggageItem("parent").Should().Be("foo"); + followingSpan.GetBaggageItem("precedent").Should().Be("bar"); + } + + [Fact(DisplayName = "Spans with non-standard references should flow correctly")] + public void NonStandardReferenceSpec() + { + var tracer = new MockTracer(MockTracer.TextMapPropagator); + var parent = (MockSpan)tracer.BuildSpan("parent").Start(); + + var nextSpan = (MockSpan)tracer.BuildSpan("follows").AddReference("a_reference", parent.Context) + .Start(); + + parent.SpanId.Should().Be(nextSpan.ParentId); + nextSpan.References.Count.Should().Be(1); + nextSpan.References[0].Should().Be(new MockSpan.Reference((MockSpan.MockContext)parent.Context, "a_reference")); + } + + [Fact(DisplayName = "SpanBuilder.ChildOf with null parent should not throw")] + public void ChildOfWithNullParentShouldNotThrow() + { + var tracer = new MockTracer(); + ISpan parent = null; + + var span = tracer.BuildSpan("foo").AsChildOf(parent).Start(); + span.Finish(); + } + } +} diff --git a/test/OpenTracing.MockTracer.Tests/OpenTracing.MockTracer.Tests.csproj b/test/OpenTracing.MockTracer.Tests/OpenTracing.MockTracer.Tests.csproj new file mode 100644 index 0000000..39ec25d --- /dev/null +++ b/test/OpenTracing.MockTracer.Tests/OpenTracing.MockTracer.Tests.csproj @@ -0,0 +1,18 @@ + + + + netcoreapp1.1 + + + + + + + + + + + + + + From e7740fb562144b8e02935193eb9c04d8ee711f3b Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Thu, 25 Jan 2018 22:31:21 -0600 Subject: [PATCH 2/6] modified build.sh to run OpenTracing.MockTracer.Tests --- build.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.sh b/build.sh index fdb7032..ad4172f 100755 --- a/build.sh +++ b/build.sh @@ -32,4 +32,5 @@ echo echo dotnet-test echo ---------------------- -dotnet test test/OpenTracing.Tests/OpenTracing.Tests.csproj -c $CONFIGURATION --no-build \ No newline at end of file +dotnet test test/OpenTracing.Tests/OpenTracing.Tests.csproj -c $CONFIGURATION --no-build +dotnet test test/OpenTracing.MockTracer.Tests/OpenTracing.MockTracer.Tests.csproj -c $CONFIGURATION --no-build \ No newline at end of file From df3b063b52ab42ae4967dbaa5338fd6627a12681 Mon Sep 17 00:00:00 2001 From: Christian Weiss Date: Tue, 13 Feb 2018 14:18:30 +0100 Subject: [PATCH 3/6] Updated to latest Java & C# API, some reorganizations --- OpenTracing.sln | 19 +- src/OpenTracing.MockTracer/MockSpan.cs | 344 ------------------ src/OpenTracing.MockTracer/MockTracer.cs | 225 ------------ .../OpenTracing.MockTracer.csproj | 16 - src/OpenTracing/Mock/MockSpan.cs | 306 ++++++++++++++++ src/OpenTracing/Mock/MockSpanBuilder.cs | 107 ++++++ src/OpenTracing/Mock/MockSpanContext.cs | 61 ++++ src/OpenTracing/Mock/MockTracer.cs | 111 ++++++ src/OpenTracing/Mock/Propagators.cs | 103 ++++++ .../MockSpanSpecs.cs | 66 ---- .../MockTracerSpecs.cs | 249 ------------- .../OpenTracing.MockTracer.Tests.csproj | 18 - test/OpenTracing.Tests/Mock/MockSpanTests.cs | 54 +++ .../OpenTracing.Tests/Mock/MockTracerTests.cs | 319 ++++++++++++++++ 14 files changed, 1062 insertions(+), 936 deletions(-) delete mode 100644 src/OpenTracing.MockTracer/MockSpan.cs delete mode 100644 src/OpenTracing.MockTracer/MockTracer.cs delete mode 100644 src/OpenTracing.MockTracer/OpenTracing.MockTracer.csproj create mode 100644 src/OpenTracing/Mock/MockSpan.cs create mode 100644 src/OpenTracing/Mock/MockSpanBuilder.cs create mode 100644 src/OpenTracing/Mock/MockSpanContext.cs create mode 100644 src/OpenTracing/Mock/MockTracer.cs create mode 100644 src/OpenTracing/Mock/Propagators.cs delete mode 100644 test/OpenTracing.MockTracer.Tests/MockSpanSpecs.cs delete mode 100644 test/OpenTracing.MockTracer.Tests/MockTracerSpecs.cs delete mode 100644 test/OpenTracing.MockTracer.Tests/OpenTracing.MockTracer.Tests.csproj create mode 100644 test/OpenTracing.Tests/Mock/MockSpanTests.cs create mode 100644 test/OpenTracing.Tests/Mock/MockTracerTests.cs diff --git a/OpenTracing.sln b/OpenTracing.sln index 433b64f..e98dd66 100644 --- a/OpenTracing.sln +++ b/OpenTracing.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.27004.2010 +VisualStudioVersion = 15.0.26403.7 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{CA88CFB0-F5DF-411B-B083-D54819E9414F}" EndProject @@ -11,10 +11,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTracing", "src\OpenTrac EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTracing.Tests", "test\OpenTracing.Tests\OpenTracing.Tests.csproj", "{F82B4191-F9D1-4656-8585-840E6BA045DD}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenTracing.MockTracer", "src\OpenTracing.MockTracer\OpenTracing.MockTracer.csproj", "{7D6D324D-299D-4BD5-82FF-A887A015770E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenTracing.MockTracer.Tests", "test\OpenTracing.MockTracer.Tests\OpenTracing.MockTracer.Tests.csproj", "{21880C2E-F25F-4D1B-BE70-1BC769B58ABE}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -29,14 +25,6 @@ Global {F82B4191-F9D1-4656-8585-840E6BA045DD}.Debug|Any CPU.Build.0 = Debug|Any CPU {F82B4191-F9D1-4656-8585-840E6BA045DD}.Release|Any CPU.ActiveCfg = Release|Any CPU {F82B4191-F9D1-4656-8585-840E6BA045DD}.Release|Any CPU.Build.0 = Release|Any CPU - {7D6D324D-299D-4BD5-82FF-A887A015770E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7D6D324D-299D-4BD5-82FF-A887A015770E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7D6D324D-299D-4BD5-82FF-A887A015770E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7D6D324D-299D-4BD5-82FF-A887A015770E}.Release|Any CPU.Build.0 = Release|Any CPU - {21880C2E-F25F-4D1B-BE70-1BC769B58ABE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {21880C2E-F25F-4D1B-BE70-1BC769B58ABE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {21880C2E-F25F-4D1B-BE70-1BC769B58ABE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {21880C2E-F25F-4D1B-BE70-1BC769B58ABE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -44,10 +32,5 @@ Global GlobalSection(NestedProjects) = preSolution {60B9BDE0-8203-46C9-9373-E06FC82CECB3} = {CA88CFB0-F5DF-411B-B083-D54819E9414F} {F82B4191-F9D1-4656-8585-840E6BA045DD} = {FC8B6803-6F66-4F1E-9034-5564B2D4FF9C} - {7D6D324D-299D-4BD5-82FF-A887A015770E} = {CA88CFB0-F5DF-411B-B083-D54819E9414F} - {21880C2E-F25F-4D1B-BE70-1BC769B58ABE} = {FC8B6803-6F66-4F1E-9034-5564B2D4FF9C} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {8D3292AC-CCF6-4D6E-B69D-63263056DD87} EndGlobalSection EndGlobal diff --git a/src/OpenTracing.MockTracer/MockSpan.cs b/src/OpenTracing.MockTracer/MockSpan.cs deleted file mode 100644 index 16ba9c1..0000000 --- a/src/OpenTracing.MockTracer/MockSpan.cs +++ /dev/null @@ -1,344 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading; - -namespace OpenTracing.MockTracer -{ - /// - public class MockSpan : ISpan - { - /// - /// Used to monotonically update ids - /// - private static long _nextIdCounter = 0; - - public static long NextId() - { - return Interlocked.Increment(ref _nextIdCounter); - } - - public MockSpan(MockTracer tracer, string operationName, DateTimeOffset startTime, Dictionary initialTags, List refs) - { - StartTime = startTime; - _tracer = tracer; - OperationName = operationName; - - if (initialTags == null) - { - _tags = new Dictionary(); - } - else - { - _tags = initialTags; - } - - if (_references == null) - { - _references = refs; - } - else - { - _references = new List(); - } - - var parentContext = FindPreferredParentRef(_references); - - if (parentContext == null) // we are a root span - { - _context = new MockContext(NextId(), NextId(), new Dictionary()); - ParentId = 0; - } - else // we are a child span - { - _context = new MockContext(parentContext.TraceId, NextId(), MergeBaggages(_references)); - ParentId = parentContext.SpanId; - } - - } - - private MockContext _context; - - /// - /// Id of the parent span. Set to 0 if there is no parent. - /// - public long ParentId { get; } - - public long TraceId => _context.TraceId; - public long SpanId => _context.SpanId; - - private readonly MockTracer _tracer; - - public DateTimeOffset StartTime { get; private set; } - public DateTimeOffset FinishTime { get; private set; } - - public string OperationName { get; private set; } - private readonly Dictionary _tags; - private readonly List _logs = new List(); - private readonly List _references; - private readonly ConcurrentBag _exceptions = new ConcurrentBag(); - - public IReadOnlyCollection Errors => _exceptions.ToList(); // don't need this ToList call in later versions of .NET Standard - - public bool IsFinished { get; private set; } - - public IReadOnlyList Logs => _logs; - - public IReadOnlyDictionary Tags => _tags; - - public void Dispose() - { - Dispose(true); - } - - private void Dispose(bool isDisposing) - { - if (isDisposing && !IsFinished) - { - Finish(); - } - } - - public ISpan SetOperationName(string operationName) - { - CheckForFinished("Setting operationName [{0}] on already finished span", operationName); - OperationName = operationName; - return this; - } - - public ISpan SetTag(string key, bool value) - { - return SetObjectTag(key, value); - } - - public ISpan SetTag(string key, double value) - { - return SetObjectTag(key, value); - } - - public ISpan SetTag(string key, int value) - { - return SetObjectTag(key, value); - } - - public ISpan SetTag(string key, string value) - { - return SetObjectTag(key, value); - } - - private ISpan SetObjectTag(string key, object value) - { - CheckForFinished("Setting tag [{0}:{1}] on already finished span", key, value); - _tags[key] = value; - return this; - } - - public ISpan Log(IEnumerable> fields) - { - return Log(new LogEvent(fields.ToDictionary(k => k.Key, v => v.Value))); - } - public ISpan Log(DateTimeOffset timestamp, IEnumerable> fields) - { - return Log(new LogEvent(timestamp, fields.ToDictionary(k => k.Key, v => v.Value))); - } - - public ISpan Log(string eventName) - { - return Log(new LogEvent(eventName)); - } - - public ISpan Log(DateTimeOffset timestamp, string eventName) - { - return Log(new LogEvent(timestamp, eventName)); - } - - private ISpan Log(LogEvent log) - { - CheckForFinished("Adding logs {0} to already finished span.", log); - _logs.Add(log); - return this; - } - - public ISpan SetBaggageItem(string key, string value) - { - CheckForFinished("Adding baggage [{0}:{1}] to already finished span.", key, value); - _context = _context.WithBaggageItem(key, value); - return this; - } - - public string GetBaggageItem(string key) - { - return _context.GetBaggageItem(key); - } - - public void Finish() - { - Finish(DateTimeOffset.UtcNow); - } - - public void Finish(DateTimeOffset finishTimestamp) - { - CheckForFinished("Tried to finish already finished span"); - FinishTime = finishTimestamp; - _tracer.FinishSpan(this); - IsFinished = true; - } - - public ISpan AddReference(Reference reference) - { - _references.Add(reference); - return this; - } - - public IReadOnlyList References => _references; - - public ISpanContext Context => _context; - - private static MockContext FindPreferredParentRef(IList references) - { - if (!references.Any()) - return null; - - foreach (var reference in references) // return the context of the parent, if applicable - { - if (OpenTracing.References.ChildOf.Equals(reference.ReferenceType)) - return reference.Context; - } - - // otherwise, return the context of the first reference - return references.First().Context; - } - - private static Dictionary MergeBaggages(IList references) - { - var baggage = new Dictionary(); - foreach (var reference in references) - { - if (reference.Context.GetBaggageItems() != null) - { - foreach (var bagItem in reference.Context.GetBaggageItems()) - { - baggage[bagItem.Key] = bagItem.Value; - } - } - } - - return baggage; - } - - private void CheckForFinished(string format, params object[] args) - { - if (IsFinished) - { - var ex = new InvalidOperationException(string.Format(format, args)); - _exceptions.Add(ex); - throw ex; - } - } - - public override string ToString() - { - return $"TraceId: {TraceId}, SpanId: {SpanId}, ParentId: {ParentId}, OperationName: {OperationName}"; - } - - /// - /// INTERNAL API. - /// - /// Internal representation of a log entry inside - /// - public sealed class LogEvent - { - public LogEvent(string eventName) - : this((IReadOnlyDictionary)new Dictionary() { { "event", eventName } }) { } - - public LogEvent(DateTimeOffset timestamp, string eventName) - : this(timestamp, (IReadOnlyDictionary)new Dictionary() { { "event", eventName } }) { } - - public LogEvent(IReadOnlyDictionary fields) - : this(DateTimeOffset.UtcNow, fields) - { - } - - public LogEvent(DateTimeOffset timeStamp, IReadOnlyDictionary fields) - { - TimeStamp = timeStamp; - Fields = fields; - } - - public DateTimeOffset TimeStamp { get; private set; } - - public IReadOnlyDictionary Fields { get; private set; } - } - - public sealed class MockContext : ISpanContext - { - private readonly IDictionary _baggageItems; - - public long TraceId { get; private set; } - - public long SpanId { get; private set; } - - public MockContext(long traceId, long spanId) : this(traceId, spanId, new Dictionary()) { } - - public MockContext(long traceId, long spanId, IDictionary baggageItems) - { - TraceId = traceId; - SpanId = spanId; - _baggageItems = baggageItems; - } - - public IEnumerable> GetBaggageItems() - { - return _baggageItems; - } - - public string GetBaggageItem(string key) - { - if(_baggageItems.ContainsKey(key)) - return _baggageItems[key]; - return null; - } - - public MockContext WithBaggageItem(string key, string val) - { - return new MockContext(TraceId, SpanId, _baggageItems.Concat(new[] { new KeyValuePair(key, val) }) - .ToDictionary(k => k.Key, v => v.Value)); - } - } - - public class Reference - { - public Reference(MockContext context, string referenceType) - { - Context = context; - ReferenceType = referenceType; - } - - public MockContext Context { get; } - - /// - /// Per - /// - public string ReferenceType { get; } - - protected bool Equals(Reference other) - { - return Context.Equals(other.Context); - } - - public override bool Equals(object obj) - { - if (Object.ReferenceEquals(null, obj)) return false; - if (Object.ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((Reference)obj); - } - - public override int GetHashCode() - { - return Context.GetHashCode(); - } - } - } -} \ No newline at end of file diff --git a/src/OpenTracing.MockTracer/MockTracer.cs b/src/OpenTracing.MockTracer/MockTracer.cs deleted file mode 100644 index 27d5595..0000000 --- a/src/OpenTracing.MockTracer/MockTracer.cs +++ /dev/null @@ -1,225 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using OpenTracing.Propagation; - -namespace OpenTracing.MockTracer -{ - /// - /// In-memory implementation designed to make it - /// easy to test assertions against the OpenMetrics API and its use - /// inside frameworks. - /// - public class MockTracer : ITracer - { - private ConcurrentQueue _finishedSpans = new ConcurrentQueue(); - private readonly IPropagator _propagator; - - public static readonly TextPropagator TextMapPropagator = new TextPropagator(); - - public MockTracer() : this(TextMapPropagator) - { - } - - public MockTracer(IPropagator propagator) - { - _propagator = propagator; - } - - public void FinishSpan(MockSpan span) - { - _finishedSpans.Enqueue(span); - } - - public IEnumerable FinishedSpans => _finishedSpans; - - /// - /// Clears all of the current recorded instances. - /// - public void Reset() - { - _finishedSpans = new ConcurrentQueue(); - } - - public ISpanBuilder BuildSpan(string operationName) - { - return new MockSpanBuilder(this, operationName); - } - - public void Inject(ISpanContext spanContext, Format format, TCarrier carrier) - { - _propagator.Inject((MockSpan.MockContext)spanContext, format, carrier); - } - - public ISpanContext Extract(Format format, TCarrier carrier) - { - return _propagator.Extract(format, carrier); - } - - /// - /// Allows the developer to inject into the and calls. - /// - public interface IPropagator - { - void Inject(MockSpan.MockContext context, Format format, TCarrier carrier); - - MockSpan.MockContext Extract(Format format, TCarrier carrier); - } - - /// - /// implementation that uses internally. - /// - public sealed class TextPropagator : IPropagator - { - public const string SpanIdKey = "spanid"; - public const string TraceIdKey = "traceid"; - public const string BaggageKeyPrefix = "baggage-"; - - public void Inject(MockSpan.MockContext context, Format format, TCarrier carrier) - { - if (carrier is ITextMap text) - { - foreach (var entry in context.GetBaggageItems()) - { - text.Set(BaggageKeyPrefix + entry.Key, entry.Value); - } - text.Set(SpanIdKey, context.SpanId.ToString()); - text.Set(TraceIdKey, context.TraceId.ToString()); - } - else - { - throw new UnsupportedFormatException($"unknown carrier [{carrier.GetType()}]"); - } - } - - public MockSpan.MockContext Extract(Format format, TCarrier carrier) - { - long? traceId = null; - long? spanId = null; - Dictionary baggage = new Dictionary(); - - if (carrier is ITextMap text) - { - foreach (var entry in text.GetEntries()) - { - if (TraceIdKey.Equals(entry.Key)) - { - traceId = Convert.ToInt64(entry.Value); - } - else if (SpanIdKey.Equals(entry.Key)) - { - spanId = Convert.ToInt64(entry.Value); - } - else if(entry.Key.StartsWith(BaggageKeyPrefix)) - { - var key = entry.Key.Substring(BaggageKeyPrefix.Length); - baggage[key] = entry.Value; - } - } - } - else - { - throw new UnsupportedFormatException($"unknown carrier [{carrier.GetType()}]"); - } - - if (traceId.HasValue && spanId.HasValue) - { - return new MockSpan.MockContext(traceId.Value, spanId.Value, baggage); - } - - return null; - } - } - - public sealed class MockSpanBuilder : ISpanBuilder - { - private readonly MockTracer _tracer; - private readonly string _operationName; - private DateTimeOffset _startTime = DateTimeOffset.MinValue; - private readonly List _spanReferences = new List(); - private readonly Dictionary _initialTags = new Dictionary(); - - public MockSpanBuilder(MockTracer tracer, string operationName) - { - _tracer = tracer; - _operationName = operationName; - } - - public ISpanBuilder AsChildOf(ISpan parent) - { - if (parent == null) - return this; - return AsChildOf(parent.Context); - } - - public ISpanBuilder AsChildOf(ISpanContext parent) - { - if (parent == null) - return this; - return AddReference(References.ChildOf, parent); - } - - public ISpanBuilder FollowsFrom(ISpan parent) - { - return FollowsFrom(parent.Context); - } - - public ISpanBuilder FollowsFrom(ISpanContext parent) - { - return AddReference(References.FollowsFrom, parent); - } - - public ISpanBuilder AddReference(string referenceType, ISpanContext referencedContext) - { - if (referencedContext != null) - { - _spanReferences.Add(new MockSpan.Reference((MockSpan.MockContext)referencedContext, referenceType)); - } - - return this; - } - - public ISpanBuilder WithTag(string key, bool value) - { - _initialTags[key] = value; - return this; - } - - public ISpanBuilder WithTag(string key, double value) - { - _initialTags[key] = value; - return this; - } - - public ISpanBuilder WithTag(string key, int value) - { - _initialTags[key] = value; - return this; - } - - public ISpanBuilder WithTag(string key, string value) - { - _initialTags[key] = value; - return this; - } - - public ISpanBuilder WithStartTimestamp(DateTimeOffset startTimestamp) - { - _startTime = startTimestamp; - return this; - } - - public ISpan Start() - { - if (_startTime == DateTimeOffset.MinValue) // value was not set by builder - { - _startTime = DateTimeOffset.UtcNow; - } - - return new MockSpan(_tracer, _operationName, _startTime, _initialTags, _spanReferences); - } - } - } - - -} diff --git a/src/OpenTracing.MockTracer/OpenTracing.MockTracer.csproj b/src/OpenTracing.MockTracer/OpenTracing.MockTracer.csproj deleted file mode 100644 index 72761e1..0000000 --- a/src/OpenTracing.MockTracer/OpenTracing.MockTracer.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - netstandard1.1 - - This package implements a MockTracer implementation of the OpenTracing.ITracer interface, intended to be used - for unit-testing ITracer implementations in third party consuming frameworks and drivers. - - opentracing;distributed-tracing;tracing;logging - - - - - - - diff --git a/src/OpenTracing/Mock/MockSpan.cs b/src/OpenTracing/Mock/MockSpan.cs new file mode 100644 index 0000000..f695d08 --- /dev/null +++ b/src/OpenTracing/Mock/MockSpan.cs @@ -0,0 +1,306 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading; + +namespace OpenTracing.Mock +{ + /// + /// MockSpans are created via MockTracer.BuildSpan(...), but they are also returned via calls to + /// . They provide accessors to all Span state. + /// + /// + public sealed class MockSpan : ISpan + { + /// + /// Used to monotonically update ids + /// + private static long _nextIdCounter = 0; + + /// + /// A simple-as-possible (consecutive for repeatability) id generator. + /// + private static long NextId() + { + return Interlocked.Increment(ref _nextIdCounter); + } + + private readonly object _lock = new object(); + + private readonly MockTracer _mockTracer; + private MockSpanContext _context; + private DateTimeOffset _finishTimestamp; + private bool _finished; + private readonly Dictionary _tags; + private readonly List _references; + private readonly List _logEntries = new List(); + private readonly List _errors = new List(); + + /// + /// The spanId of the Span's first reference, or the first reference of any type, + /// or 0 if no reference exists. + /// + /// + /// + public long ParentId { get; } + + public DateTimeOffset StartTimestamp { get; } + + /// + /// The finish time of the Span; only valid after a call to . + /// + public DateTimeOffset FinishTimestamp + { + get + { + if (_finishTimestamp == DateTimeOffset.MinValue) + throw new InvalidOperationException("Must call Finish() before FinishTimestamp"); + + return _finishTimestamp; + } + } + + public string OperationName { get; private set; } + + /// + /// A copy of all tags set on this span. + /// + public Dictionary Tags => new Dictionary(_tags); + + /// + /// A copy of all log entries added to this span. + /// + public List LogEntries => new List(_logEntries); + + /// + /// A copy of exceptions thrown by this class (e.g. adding a tag after span is finished). + /// + public List GeneratedErrors => new List(_errors); + + public List References => new List(_references); + + public MockSpanContext Context + { + // C# doesn't have "return type covariance" so we use the trick with the explicit interface implementation + // and this separate property. + get + { + lock (_lock) + { + return _context; + } + } + } + + ISpanContext ISpan.Context => Context; + + public MockSpan( + MockTracer tracer, + string operationName, + DateTimeOffset startTimestamp, + Dictionary initialTags, + List references) + { + _mockTracer = tracer; + OperationName = operationName; + StartTimestamp = startTimestamp; + + _tags = initialTags == null + ? new Dictionary() + : initialTags.ToDictionary(k => k.Key, v => v.Value); + + _references = references == null + ? new List() + : references.ToList(); + + var parentContext = FindPreferredParentRef(_references); + + if (parentContext == null) + { + // we are a root span + _context = new MockSpanContext(NextId(), NextId(), new Dictionary()); + ParentId = 0; + } + else + { + // we are a child span + _context = new MockSpanContext(parentContext.TraceId, NextId(), MergeBaggages(_references)); + ParentId = parentContext.SpanId; + } + } + + public ISpan SetOperationName(string operationName) + { + CheckForFinished("Setting operationName [{0}] on already finished span", operationName); + OperationName = operationName; + return this; + } + + public ISpan SetTag(string key, bool value) + { + return SetObjectTag(key, value); + } + + public ISpan SetTag(string key, double value) + { + return SetObjectTag(key, value); + } + + public ISpan SetTag(string key, int value) + { + return SetObjectTag(key, value); + } + + public ISpan SetTag(string key, string value) + { + return SetObjectTag(key, value); + } + + private ISpan SetObjectTag(string key, object value) + { + lock (_lock) + { + CheckForFinished("Setting tag [{0}:{1}] on already finished span", key, value); + _tags[key] = value; + return this; + } + } + + public ISpan Log(IDictionary fields) + { + return Log(DateTimeOffset.UtcNow, fields); + } + + public ISpan Log(DateTimeOffset timestamp, IDictionary fields) + { + lock (_lock) + { + CheckForFinished("Adding logs {0} at {1} to already finished span.", fields, timestamp); + _logEntries.Add(new LogEntry(timestamp, fields)); + return this; + } + } + + public ISpan Log(string @event) + { + return Log(DateTimeOffset.UtcNow, @event); + } + + public ISpan Log(DateTimeOffset timestamp, string @event) + { + return Log(timestamp, new Dictionary { { "event", @event } }); + } + + public ISpan SetBaggageItem(string key, string value) + { + lock (_lock) + { + CheckForFinished("Adding baggage [{0}:{1}] to already finished span.", key, value); + _context = _context.WithBaggageItem(key, value); + return this; + } + } + + public string GetBaggageItem(string key) + { + lock (_lock) + { + return _context.GetBaggageItem(key); + } + } + + public void Finish() + { + Finish(DateTimeOffset.UtcNow); + } + + public void Finish(DateTimeOffset finishTimestamp) + { + lock (_lock) + { + CheckForFinished("Tried to finish already finished span"); + _finishTimestamp = finishTimestamp; + _mockTracer.AppendFinishedSpan(this); + _finished = true; + } + } + + private static MockSpanContext FindPreferredParentRef(IList references) + { + if (!references.Any()) + return null; + + // return the context of the parent, if applicable + foreach (var reference in references) + { + if (OpenTracing.References.ChildOf.Equals(reference.ReferenceType)) + return reference.Context; + } + + // otherwise, return the context of the first reference + return references.First().Context; + } + + private static Dictionary MergeBaggages(IList references) + { + var baggage = new Dictionary(); + foreach (var reference in references) + { + if (reference.Context.GetBaggageItems() != null) + { + foreach (var bagItem in reference.Context.GetBaggageItems()) + { + baggage[bagItem.Key] = bagItem.Value; + } + } + } + + return baggage; + } + + private void CheckForFinished(string format, params object[] args) + { + if (_finished) + { + var ex = new InvalidOperationException(string.Format(format, args)); + _errors.Add(ex); + throw ex; + } + } + + public override string ToString() + { + return $"TraceId: {_context.TraceId}, SpanId: {_context.SpanId}, ParentId: {ParentId}, OperationName: {OperationName}"; + } + + public sealed class LogEntry + { + public DateTimeOffset Timestamp { get; } + + public IReadOnlyDictionary Fields { get; } + + public LogEntry(DateTimeOffset timestamp, IDictionary fields) + { + Timestamp = timestamp; + Fields = new ReadOnlyDictionary(fields); + } + } + + public struct Reference + { + public MockSpanContext Context { get; } + + /// + /// See . + /// + public string ReferenceType { get; } + + public Reference(MockSpanContext context, string referenceType) + { + Context = context; + ReferenceType = referenceType; + } + } + } +} diff --git a/src/OpenTracing/Mock/MockSpanBuilder.cs b/src/OpenTracing/Mock/MockSpanBuilder.cs new file mode 100644 index 0000000..3e06973 --- /dev/null +++ b/src/OpenTracing/Mock/MockSpanBuilder.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace OpenTracing.Mock +{ + public sealed class MockSpanBuilder : ISpanBuilder + { + private readonly MockTracer _tracer; + private readonly string _operationName; + private DateTimeOffset _startTimestamp = DateTimeOffset.MinValue; + private readonly List _references = new List(); + private readonly Dictionary _initialTags = new Dictionary(); + private bool _ignoreActiveSpan; + + public MockSpanBuilder(MockTracer tracer, string operationName) + { + _tracer = tracer; + _operationName = operationName; + } + + public ISpanBuilder AsChildOf(ISpanContext parent) + { + if (parent == null) + return this; + + return AddReference(References.ChildOf, parent); + } + + public ISpanBuilder AsChildOf(ISpan parent) + { + if (parent == null) + return this; + + return AddReference(References.ChildOf, parent.Context); + } + + public ISpanBuilder AddReference(string referenceType, ISpanContext referencedContext) + { + if (referencedContext != null) + { + _references.Add(new MockSpan.Reference((MockSpanContext)referencedContext, referenceType)); + } + + return this; + } + + public ISpanBuilder IgnoreActiveSpan() + { + _ignoreActiveSpan = true; + return this; + } + + public ISpanBuilder WithTag(string key, bool value) + { + _initialTags[key] = value; + return this; + } + + public ISpanBuilder WithTag(string key, double value) + { + _initialTags[key] = value; + return this; + } + + public ISpanBuilder WithTag(string key, int value) + { + _initialTags[key] = value; + return this; + } + + public ISpanBuilder WithTag(string key, string value) + { + _initialTags[key] = value; + return this; + } + + public ISpanBuilder WithStartTimestamp(DateTimeOffset startTimestamp) + { + _startTimestamp = startTimestamp; + return this; + } + + public IScope StartActive(bool finishSpanOnDispose) + { + ISpan span = Start(); + return _tracer.ScopeManager.Activate(span, finishSpanOnDispose); + } + + public ISpan Start() + { + if (_startTimestamp == DateTimeOffset.MinValue) // value was not set by builder + { + _startTimestamp = DateTimeOffset.UtcNow; + } + + ISpanContext activeSpanContext = _tracer.ActiveSpan?.Context; + + if (!_references.Any() && !_ignoreActiveSpan && activeSpanContext != null) + { + _references.Add(new MockSpan.Reference((MockSpanContext)activeSpanContext, References.ChildOf)); + } + + return new MockSpan(_tracer, _operationName, _startTimestamp, _initialTags, _references); + } + } +} diff --git a/src/OpenTracing/Mock/MockSpanContext.cs b/src/OpenTracing/Mock/MockSpanContext.cs new file mode 100644 index 0000000..6921f03 --- /dev/null +++ b/src/OpenTracing/Mock/MockSpanContext.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using System.Linq; + +namespace OpenTracing.Mock +{ + /// + /// implements a Dapper-like with a trace-id and span-id. + /// + /// Note that parent ids are part of the , not the + /// (since they do not need to propagate between processes). + /// + public sealed class MockSpanContext : ISpanContext + { + private readonly IDictionary _baggage; + + public long TraceId { get; } + + public long SpanId { get; } + + /// + /// An internal constructor to create a new . + /// This should only be called by and/or . + /// + /// The id of the trace + /// The id of the span + /// The MockContext takes ownership of the baggage parameter. + /// + internal MockSpanContext(long traceId, long spanId, IDictionary baggage) + { + TraceId = traceId; + SpanId = spanId; + _baggage = baggage; + } + + public IEnumerable> GetBaggageItems() + { + return _baggage; + } + + public string GetBaggageItem(string key) + { + if (_baggage.ContainsKey(key)) + return _baggage[key]; + + return null; + } + + /// + /// Create and return a new (immutable) MockContext with the added baggage item. + /// + public MockSpanContext WithBaggageItem(string key, string val) + { + IDictionary newBaggage = _baggage + .ToDictionary(k => k.Key, v => v.Value); + + newBaggage[key] = val; + + return new MockSpanContext(TraceId, SpanId, newBaggage); + } + } +} diff --git a/src/OpenTracing/Mock/MockTracer.cs b/src/OpenTracing/Mock/MockTracer.cs new file mode 100644 index 0000000..ace40d5 --- /dev/null +++ b/src/OpenTracing/Mock/MockTracer.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using OpenTracing.Noop; +using OpenTracing.Propagation; +using OpenTracing.Util; + +namespace OpenTracing.Mock +{ + /// + /// makes it easy to test the semantics of OpenTracing instrumentation. + /// + /// By using a as an implementation for unittests, a developer can assert that Span + /// properties and relationships with other Spans are defined as expected by instrumentation code. + /// + /// The MockTracerTests class has simple usage examples. + /// + public class MockTracer : ITracer + { + private readonly object _lock = new object(); + + private readonly List _finishedSpans = new List(); + private readonly IPropagator _propagator; + private readonly IScopeManager _scopeManager; + + public IScopeManager ScopeManager => _scopeManager; + + public ISpan ActiveSpan => _scopeManager?.Active?.Span; + + public MockTracer() + : this(new AsyncLocalScopeManager(), Propagators.TextMap) + { + } + + public MockTracer(IScopeManager scopeManager) + : this(scopeManager, Propagators.TextMap) + { + } + + public MockTracer(IScopeManager scopeManager, IPropagator propagator) + { + _scopeManager = scopeManager; + _propagator = propagator; + } + + /// + /// Create a new that passes through any calls to Inject() and/or Extract(). + /// + public MockTracer(IPropagator propagator) + : this(NoopScopeManager.Instance, propagator) + { + } + + /// + /// Clear the queue. + /// + /// Note that this does *not* have any effect on Spans created by MockTracer that have not Finish()ed yet; those + /// will still be enqueued in when they Finish(). + /// + public void Reset() + { + lock (_lock) + { + _finishedSpans.Clear(); + } + } + + /// + /// Returns a copy of all Finish()ed MockSpans started by this MockTracer (since construction or the last call to + /// ). + /// + /// + public List FinishedSpans() + { + lock (_lock) + { + return new List(_finishedSpans); + } + } + + /// + /// Noop method called on . + /// + protected virtual void OnSpanFinished(MockSpan mockSpan) + { + } + + public ISpanBuilder BuildSpan(string operationName) + { + return new MockSpanBuilder(this, operationName); + } + + public void Inject(ISpanContext spanContext, IFormat format, TCarrier carrier) + { + _propagator.Inject((MockSpanContext)spanContext, format, carrier); + } + + public ISpanContext Extract(IFormat format, TCarrier carrier) + { + return _propagator.Extract(format, carrier); + } + + internal void AppendFinishedSpan(MockSpan mockSpan) + { + lock (_lock) + { + _finishedSpans.Add(mockSpan); + OnSpanFinished(mockSpan); + } + } + } +} diff --git a/src/OpenTracing/Mock/Propagators.cs b/src/OpenTracing/Mock/Propagators.cs new file mode 100644 index 0000000..f3b5df8 --- /dev/null +++ b/src/OpenTracing/Mock/Propagators.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using OpenTracing.Propagation; + +namespace OpenTracing.Mock +{ + public static class Propagators + { + public static readonly IPropagator Printer = new PrinterPropagator(); + + public static readonly IPropagator TextMap = new TextMapPropagator(); + } + + /// + /// Allows the developer to inject into the and calls. + /// + public interface IPropagator + { + void Inject(MockSpanContext context, IFormat format, TCarrier carrier); + + MockSpanContext Extract(IFormat format, TCarrier carrier); + } + + public sealed class PrinterPropagator : IPropagator + { + public void Inject(MockSpanContext context, IFormat format, TCarrier carrier) + { + Console.WriteLine($"Inject({context}, {format}, {carrier}"); + } + + public MockSpanContext Extract(IFormat format, TCarrier carrier) + { + Console.WriteLine($"Extract({format}, {carrier}"); + return null; + } + } + + /// + /// implementation that uses internally. + /// + public sealed class TextMapPropagator : IPropagator + { + public const string SpanIdKey = "spanid"; + public const string TraceIdKey = "traceid"; + public const string BaggageKeyPrefix = "baggage-"; + + public void Inject(MockSpanContext context, IFormat format, TCarrier carrier) + { + if (carrier is ITextMap text) + { + foreach (var entry in context.GetBaggageItems()) + { + text.Set(BaggageKeyPrefix + entry.Key, entry.Value); + } + + text.Set(SpanIdKey, context.SpanId.ToString()); + text.Set(TraceIdKey, context.TraceId.ToString()); + } + else + { + throw new UnsupportedFormatException($"unknown carrier [{carrier.GetType()}]"); + } + } + + public MockSpanContext Extract(IFormat format, TCarrier carrier) + { + long? traceId = null; + long? spanId = null; + Dictionary baggage = new Dictionary(); + + if (carrier is ITextMap text) + { + foreach (var entry in text) + { + if (TraceIdKey.Equals(entry.Key)) + { + traceId = Convert.ToInt64(entry.Value); + } + else if (SpanIdKey.Equals(entry.Key)) + { + spanId = Convert.ToInt64(entry.Value); + } + else if (entry.Key.StartsWith(BaggageKeyPrefix)) + { + var key = entry.Key.Substring(BaggageKeyPrefix.Length); + baggage[key] = entry.Value; + } + } + } + else + { + throw new UnsupportedFormatException($"unknown carrier [{carrier.GetType()}]"); + } + + if (traceId.HasValue && spanId.HasValue) + { + return new MockSpanContext(traceId.Value, spanId.Value, baggage); + } + + return null; + } + } +} diff --git a/test/OpenTracing.MockTracer.Tests/MockSpanSpecs.cs b/test/OpenTracing.MockTracer.Tests/MockSpanSpecs.cs deleted file mode 100644 index 022d4cf..0000000 --- a/test/OpenTracing.MockTracer.Tests/MockSpanSpecs.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Linq; -using FluentAssertions; -using Xunit; - -namespace OpenTracing.MockTracer.Tests -{ - public class MockSpanSpecs - { - [Fact] - public void SetOperationNameAfterFinishShouldThrow() - { - var tracer = new OpenTracing.MockTracer.MockTracer(); - var span = tracer.BuildSpan("foo").Start(); - span.Finish(); - - Action act = () => span.SetOperationName("bar"); - - act.ShouldThrow(); - - tracer.FinishedSpans.First().Errors.Count.Should().Be(1); - } - - [Fact] - public void SetTagAFterFinishShouldThrow() - { - var tracer = new MockTracer(); - var span = tracer.BuildSpan("foo").Start(); - span.Finish(); - - Action act = () => span.SetTag("foo", "bar"); - - act.ShouldThrow(); - - tracer.FinishedSpans.First().Errors.Count.Should().Be(1); - } - - [Fact] - public void AddLogAFterFinishShouldThrow() - { - var tracer = new MockTracer(); - var span = tracer.BuildSpan("foo").Start(); - span.Finish(); - - Action act = () => span.Log("test"); - - act.ShouldThrow(); - - tracer.FinishedSpans.First().Errors.Count.Should().Be(1); - } - - [Fact] - public void AddBaggageAFterFinishShouldThrow() - { - var tracer = new MockTracer(); - var span = tracer.BuildSpan("foo").Start(); - span.Finish(); - - Action act = () => span.SetBaggageItem("foo", "bar"); - - act.ShouldThrow(); - - tracer.FinishedSpans.First().Errors.Count.Should().Be(1); - } - } -} \ No newline at end of file diff --git a/test/OpenTracing.MockTracer.Tests/MockTracerSpecs.cs b/test/OpenTracing.MockTracer.Tests/MockTracerSpecs.cs deleted file mode 100644 index 9c370eb..0000000 --- a/test/OpenTracing.MockTracer.Tests/MockTracerSpecs.cs +++ /dev/null @@ -1,249 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using FluentAssertions; -using OpenTracing.Propagation; -using Xunit; - -namespace OpenTracing.MockTracer.Tests -{ - public class MockTracerSpecs - { - [Fact(DisplayName = "Root span's fields should all be set correctly")] - public void RootSpanShouldHavePropertiesSetCorrectly() - { - var tracer = new MockTracer(); - var span = tracer.BuildSpan("foo").WithTag("actorId", 1) - .WithTag("actorPath", "/user/foo/test") - .WithTag("singleton", false) - .WithStartTimestamp(DateTimeOffset.UtcNow.AddMilliseconds(-10)).Start(); - - var timeStamp = DateTimeOffset.UtcNow.AddMilliseconds(-8); - span.Log(timeStamp, "foo"); - span.Log(timeStamp.AddMilliseconds(1), new[] { new KeyValuePair("foo1", true) }); - span.SetBaggageItem("baggageIsDifferentThanTags", "apparently"); - - span.Finish(); - - var testSpan = (MockSpan)span; - - var recordedSpans = tracer.FinishedSpans; - recordedSpans.First().Should().Be(testSpan); - - testSpan.IsFinished.Should().BeTrue(); - testSpan.References.Should().BeEmpty("We did not set any reference"); - testSpan.Context.As().SpanId.Should() - .NotBe(testSpan.Context.As().TraceId, "traceId and spanId should be set independently"); - testSpan.Errors.Should().BeEmpty(); - testSpan.FinishTime.Should().BeAfter(testSpan.StartTime); - testSpan.Tags.Keys.Should().BeEquivalentTo("actorId", "actorPath", "singleton"); - testSpan.Tags.Values.Should().BeEquivalentTo(1, "/user/foo/test", false); - testSpan.Logs.Count.Should().Be(2); - - var log1 = testSpan.Logs[0]; - log1.Fields.Count.Should().Be(1); - log1.Fields["event"].Should().Be("foo"); - log1.TimeStamp.Should().Be(timeStamp); - - var log2 = testSpan.Logs[1]; - log2.Fields.Count.Should().Be(1); - log2.Fields["foo1"].Should().Be(true); - log2.TimeStamp.Should().Be(timeStamp.AddMilliseconds(1)); - - var baggage = testSpan.Context.GetBaggageItems().ToList(); - baggage.Count.Should().Be(1); - testSpan.GetBaggageItem("baggageIsDifferentThanTags").Should().Be("apparently"); - } - - [Fact(DisplayName = "Child span's fields should be set correctly")] - public void ChildSpanPropertiesShouldBeSetCorrectly() - { - // create and finish a root span - var tracer = new MockTracer(); - using (var parent = tracer.BuildSpan("parent").WithStartTimestamp(DateTimeOffset.UtcNow).Start()) - { - var child = tracer.BuildSpan("child").WithStartTimestamp(DateTimeOffset.UtcNow.AddMilliseconds(100)) - .AsChildOf(parent).Start(); - - child.Finish(DateTimeOffset.UtcNow.AddMilliseconds(900)); - parent.Finish(DateTimeOffset.UtcNow.AddMilliseconds(1000)); - } - - var finishedSpans = tracer.FinishedSpans.ToList(); - - - finishedSpans.Count.Should().Be(2); - var c = finishedSpans[0]; - var p = finishedSpans[1]; - c.OperationName.Should().Be("child"); - p.OperationName.Should().Be("parent"); - p.SpanId.Should().Be(c.ParentId); - p.TraceId.Should().Be(c.TraceId); - c.SpanId.Should().BeGreaterThan(p.SpanId); - } - - [Fact(DisplayName = "Spans should use explicit timestamps when specified")] - public void ExplicitTimeStampShouldBeUsed() - { - // create and finish a root span - var tracer = new MockTracer(); - var startTime = DateTimeOffset.UtcNow.AddMilliseconds(-100); - tracer.BuildSpan("foo").WithStartTimestamp(startTime).Start().Finish(); - - var finishedSpan = tracer.FinishedSpans.First(); - finishedSpan.StartTime.Equals(startTime); - } - - [Fact(DisplayName = "TextMapPropagator should work as expected with TextMap format")] - public void TextMapPropagatorShouldWorkWithTextMap() - { - var tracer = new MockTracer(MockTracer.TextMapPropagator); - var injectMap = new Dictionary {["foobag"] = "donttouch"}; - - var parentSpan = tracer.BuildSpan("foo").Start(); - parentSpan.SetBaggageItem("foobag", "fooitem"); - parentSpan.Finish(); - - tracer.Inject(parentSpan.Context, Formats.TextMap, new DictionaryCarrier(injectMap)); - - var extract = tracer.Extract(Formats.TextMap, new DictionaryCarrier(injectMap)); - - var childSpan = tracer.BuildSpan("bar") - .AsChildOf(extract).Start(); - childSpan.SetBaggageItem("barbag", "baritem"); - childSpan.Finish(); - - var finishedSpans = tracer.FinishedSpans.ToList(); - - finishedSpans.Count.Should().Be(2); - finishedSpans[0].TraceId.Should().Be(finishedSpans[1].TraceId); - finishedSpans[0].SpanId.Should().Be(finishedSpans[1].ParentId); - finishedSpans[0].SpanId.Should().NotBe(finishedSpans[1].SpanId); - finishedSpans[0].GetBaggageItem("foobag").Should().Be("fooitem"); - finishedSpans[0].GetBaggageItem("barbag").Should().BeNullOrEmpty(); - finishedSpans[1].GetBaggageItem("foobag").Should().Be("fooitem"); - finishedSpans[1].GetBaggageItem("barbag").Should().Be("baritem"); - injectMap["foobag"].Should().Be("donttouch"); - } - - [Fact(DisplayName = "TextMapPropagator should work as expected with HTTP Headers format")] - public void TextMapPropagatorShouldWorkWithHttpHeaders() - { - var tracer = new MockTracer(MockTracer.TextMapPropagator); - var injectMap = new Dictionary(); - - var parentSpan = tracer.BuildSpan("foo").Start(); - parentSpan.Finish(); - - tracer.Inject(parentSpan.Context, Formats.HttpHeaders, new DictionaryCarrier(injectMap)); - - var extract = tracer.Extract(Formats.HttpHeaders, new DictionaryCarrier(injectMap)); - - tracer.BuildSpan("bar") - .AsChildOf(extract).Start().Finish(); - - var finishedSpans = tracer.FinishedSpans.ToList(); - - finishedSpans.Count.Should().Be(2); - finishedSpans[0].TraceId.Should().Be(finishedSpans[1].TraceId); - finishedSpans[0].SpanId.Should().Be(finishedSpans[1].ParentId); - finishedSpans[0].SpanId.Should().NotBe(finishedSpans[1].SpanId); - } - - [Fact(DisplayName = "MockTracer.Reset should clear all finished spans from memory.")] - public void MockTracerResetShouldClearFinishedSpans() - { - var tracer = new MockTracer(); - - tracer.BuildSpan("foo").Start().Finish(); - - tracer.FinishedSpans.Count().Should().Be(1); - tracer.Reset(); - tracer.FinishedSpans.Count().Should().Be(0); - } - - [Fact(DisplayName = "FollowFrom references should be set correctly")] - public void FollowFromReferenceSpec() - { - var tracer = new MockTracer(MockTracer.TextMapPropagator); - - var precedent = (MockSpan)tracer.BuildSpan("precedent").Start(); - var following = (MockSpan)tracer.BuildSpan("follows") - .AddReference(References.FollowsFrom, precedent.Context) - .Start(); - - precedent.SpanId.Should().Be(following.ParentId); // should still be parent ID, since there's no explicit childof - following.References.Count.Should().Be(1); - - var followingRef = following.References[0]; - - new MockSpan.Reference((MockSpan.MockContext)precedent.Context, References.FollowsFrom).Should().Be(followingRef); - } - - [Fact(DisplayName = "Spans with multiple references should be set correctly")] - public void MultiReferenceSpec() - { - var tracer = new MockTracer(MockTracer.TextMapPropagator); - var parent = (MockSpan)tracer.BuildSpan("parent").Start(); - var precedent = (MockSpan)tracer.BuildSpan("precedent").Start(); - - var followingSpan = (MockSpan)tracer.BuildSpan("follows") - .AddReference(References.FollowsFrom, precedent.Context) - .AsChildOf(parent.Context) - .Start(); - - parent.SpanId.Should().Be(followingSpan.ParentId); - followingSpan.References.Count.Should().Be(2); - - var followsFromRef = followingSpan.References[0]; - var parentRef = followingSpan.References[1]; - - new MockSpan.Reference((MockSpan.MockContext)precedent.Context, References.FollowsFrom).Should() - .Be(followsFromRef); - new MockSpan.Reference((MockSpan.MockContext)parent.Context, References.ChildOf).Should() - .Be(parentRef); - } - - [Fact(DisplayName = "Spans with multiple references should merge baggage correctly")] - public void MultiReferencesBaggageSpec() - { - var tracer = new MockTracer(MockTracer.TextMapPropagator); - var parent = (MockSpan)tracer.BuildSpan("parent").Start(); - parent.SetBaggageItem("parent", "foo"); - var precedent = (MockSpan)tracer.BuildSpan("precedent").Start(); - precedent.SetBaggageItem("precedent", "bar"); - - var followingSpan = (MockSpan)tracer.BuildSpan("follows") - .AddReference(References.FollowsFrom, precedent.Context) - .AsChildOf(parent.Context) - .Start(); - - followingSpan.GetBaggageItem("parent").Should().Be("foo"); - followingSpan.GetBaggageItem("precedent").Should().Be("bar"); - } - - [Fact(DisplayName = "Spans with non-standard references should flow correctly")] - public void NonStandardReferenceSpec() - { - var tracer = new MockTracer(MockTracer.TextMapPropagator); - var parent = (MockSpan)tracer.BuildSpan("parent").Start(); - - var nextSpan = (MockSpan)tracer.BuildSpan("follows").AddReference("a_reference", parent.Context) - .Start(); - - parent.SpanId.Should().Be(nextSpan.ParentId); - nextSpan.References.Count.Should().Be(1); - nextSpan.References[0].Should().Be(new MockSpan.Reference((MockSpan.MockContext)parent.Context, "a_reference")); - } - - [Fact(DisplayName = "SpanBuilder.ChildOf with null parent should not throw")] - public void ChildOfWithNullParentShouldNotThrow() - { - var tracer = new MockTracer(); - ISpan parent = null; - - var span = tracer.BuildSpan("foo").AsChildOf(parent).Start(); - span.Finish(); - } - } -} diff --git a/test/OpenTracing.MockTracer.Tests/OpenTracing.MockTracer.Tests.csproj b/test/OpenTracing.MockTracer.Tests/OpenTracing.MockTracer.Tests.csproj deleted file mode 100644 index 39ec25d..0000000 --- a/test/OpenTracing.MockTracer.Tests/OpenTracing.MockTracer.Tests.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - netcoreapp1.1 - - - - - - - - - - - - - - diff --git a/test/OpenTracing.Tests/Mock/MockSpanTests.cs b/test/OpenTracing.Tests/Mock/MockSpanTests.cs new file mode 100644 index 0000000..c98350d --- /dev/null +++ b/test/OpenTracing.Tests/Mock/MockSpanTests.cs @@ -0,0 +1,54 @@ +using System; +using System.Linq; +using OpenTracing.Mock; +using Xunit; + +namespace OpenTracing.Tests.Mock +{ + public class MockSpanTests + { + [Fact] + public void SetOperationNameAfterFinishShouldThrow() + { + var tracer = new MockTracer(); + var span = tracer.BuildSpan("foo").Start(); + span.Finish(); + + Assert.Throws(() => span.SetOperationName("bar")); + Assert.Single(tracer.FinishedSpans()[0].GeneratedErrors); + } + + [Fact] + public void SetTagAFterFinishShouldThrow() + { + var tracer = new MockTracer(); + var span = tracer.BuildSpan("foo").Start(); + span.Finish(); + + Assert.Throws(() => span.SetTag("foo", "bar")); + Assert.Single(tracer.FinishedSpans()[0].GeneratedErrors); + } + + [Fact] + public void AddLogAFterFinishShouldThrow() + { + var tracer = new MockTracer(); + var span = tracer.BuildSpan("foo").Start(); + span.Finish(); + + Assert.Throws(() => span.Log("bar")); + Assert.Single(tracer.FinishedSpans()[0].GeneratedErrors); + } + + [Fact] + public void AddBaggageAFterFinishShouldThrow() + { + var tracer = new MockTracer(); + var span = tracer.BuildSpan("foo").Start(); + span.Finish(); + + Assert.Throws(() => span.SetBaggageItem("foo", "bar")); + Assert.Single(tracer.FinishedSpans()[0].GeneratedErrors); + } + } +} diff --git a/test/OpenTracing.Tests/Mock/MockTracerTests.cs b/test/OpenTracing.Tests/Mock/MockTracerTests.cs new file mode 100644 index 0000000..76c01c3 --- /dev/null +++ b/test/OpenTracing.Tests/Mock/MockTracerTests.cs @@ -0,0 +1,319 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using OpenTracing.Mock; +using OpenTracing.Propagation; +using Xunit; + +namespace OpenTracing.Tests.Mock +{ + public class MockTracerTests + { + private static readonly DateTimeOffset FixedStartTimestamp = GetTestTimestamp(0); + private static readonly DateTimeOffset FixedFinishTimestamp = GetTestTimestamp(999); + + private static DateTimeOffset GetTestTimestamp(int millisecond) + { + return new DateTimeOffset(2000, 1, 1, 12, 0, 0, millisecond, TimeSpan.Zero); + } + + [Fact] + public void TestRootSpan() + { + // Create and finish a root Span. + var tracer = new MockTracer(); + + var span = tracer.BuildSpan("tester") + .WithStartTimestamp(FixedStartTimestamp) + .Start(); + + span.SetTag("string", "foo"); + span.SetTag("int", 7); + span.Log("foo"); + var fields = new Dictionary { { "f1", 4 }, { "f2", "two" } }; + span.Log(GetTestTimestamp(2), fields); + span.Log(GetTestTimestamp(3), "event name"); + span.Finish(FixedFinishTimestamp); + + List finishedSpans = tracer.FinishedSpans(); + + // Check that the Span looks right. + + Assert.Single(finishedSpans); + MockSpan finishedSpan = finishedSpans[0]; + Assert.Equal("tester", finishedSpan.OperationName); + Assert.Equal(0, finishedSpan.ParentId); + Assert.NotEqual(0, finishedSpan.Context.TraceId); + Assert.NotEqual(0, finishedSpan.Context.SpanId); + Assert.Equal(FixedStartTimestamp, finishedSpan.StartTimestamp); + Assert.Equal(FixedFinishTimestamp, finishedSpan.FinishTimestamp); + + var tags = finishedSpan.Tags; + Assert.Equal(2, tags.Count); + Assert.Equal(7, tags["int"]); + Assert.Equal("foo", tags["string"]); + + var logs = finishedSpan.LogEntries; + Assert.Equal(3, logs.Count); + { + MockSpan.LogEntry log = logs[0]; + Assert.Single(log.Fields); + Assert.Equal("foo", log.Fields["event"]); + } + { + MockSpan.LogEntry log = logs[1]; + Assert.Equal(GetTestTimestamp(2), log.Timestamp); + Assert.Equal(4, log.Fields["f1"]); + Assert.Equal("two", log.Fields["f2"]); + } + { + MockSpan.LogEntry log = logs[2]; + Assert.Equal(GetTestTimestamp(3), log.Timestamp); + Assert.Equal("event name", log.Fields["event"]); + } + } + + [Fact] + public void TestChildSpan() + { + // Create and finish a root Span. + MockTracer tracer = new MockTracer(); + ISpan originalParent = tracer.BuildSpan("parent").WithStartTimestamp(GetTestTimestamp(100)).Start(); + ISpan originalChild = tracer.BuildSpan("child").WithStartTimestamp(GetTestTimestamp(200)).AsChildOf(originalParent).Start(); + originalChild.Finish(GetTestTimestamp(800)); + originalParent.Finish(GetTestTimestamp(900)); + + List finishedSpans = tracer.FinishedSpans(); + + // Check that the Spans look right. + Assert.Equal(2, finishedSpans.Count); + MockSpan child = finishedSpans[0]; + MockSpan parent = finishedSpans[1]; + Assert.Equal("child", child.OperationName); + Assert.Equal("parent", parent.OperationName); + Assert.Equal(parent.Context.SpanId, child.ParentId); + Assert.Equal(parent.Context.TraceId, child.Context.TraceId); + } + + [Fact] + public void TestStartTimestamp() + { + MockTracer tracer = new MockTracer(); + DateTimeOffset startTimestamp; + { + ISpanBuilder fooSpan = tracer.BuildSpan("foo"); + Thread.Sleep(20); + startTimestamp = DateTimeOffset.Now; + fooSpan.Start().Finish(); + } + List finishedSpans = tracer.FinishedSpans(); + + Assert.Single(finishedSpans); + MockSpan span = finishedSpans[0]; + Assert.True(startTimestamp <= span.StartTimestamp); + Assert.True(DateTimeOffset.Now >= span.FinishTimestamp); + } + + [Fact] + public void TestStartExplicitTimestamp() + { + MockTracer tracer = new MockTracer(); + DateTimeOffset startTimestamp = FixedStartTimestamp; + { + tracer.BuildSpan("foo") + .WithStartTimestamp(startTimestamp) + .Start() + .Finish(); + } + List finishedSpans = tracer.FinishedSpans(); + + Assert.Single(finishedSpans); + Assert.Equal(startTimestamp, finishedSpans[0].StartTimestamp); + } + + [Fact] + public void TestTextMapPropagatorTextMap() + { + MockTracer tracer = new MockTracer(Propagators.TextMap); + var injectMap = new Dictionary { { "foobag", "donttouch" } }; + { + ISpan parentSpan = tracer.BuildSpan("foo") + .Start(); + parentSpan.SetBaggageItem("foobag", "fooitem"); + parentSpan.Finish(); + + tracer.Inject(parentSpan.Context, BuiltinFormats.TextMap, + new TextMapInjectAdapter(injectMap)); + + ISpanContext extract = tracer.Extract(BuiltinFormats.TextMap, new TextMapExtractAdapter(injectMap)); + + ISpan childSpan = tracer.BuildSpan("bar") + .AsChildOf(extract) + .Start(); + childSpan.SetBaggageItem("barbag", "baritem"); + childSpan.Finish(); + } + List finishedSpans = tracer.FinishedSpans(); + + Assert.Equal(2, finishedSpans.Count); + Assert.Equal(finishedSpans[0].Context.TraceId, finishedSpans[1].Context.TraceId); + Assert.Equal(finishedSpans[0].Context.SpanId, finishedSpans[1].ParentId); + Assert.Equal("fooitem", finishedSpans[0].GetBaggageItem("foobag")); + Assert.Null(finishedSpans[0].GetBaggageItem("barbag")); + Assert.Equal("fooitem", finishedSpans[1].GetBaggageItem("foobag")); + Assert.Equal("baritem", finishedSpans[1].GetBaggageItem("barbag")); + Assert.Equal("donttouch", injectMap["foobag"]); + } + + [Fact] + public void TestTextMapPropagatorHttpHeaders() + { + MockTracer tracer = new MockTracer(Propagators.TextMap); + { + ISpan parentSpan = tracer.BuildSpan("foo") + .Start(); + parentSpan.Finish(); + + var injectMap = new Dictionary(); + tracer.Inject(parentSpan.Context, BuiltinFormats.HttpHeaders, + new TextMapInjectAdapter(injectMap)); + + ISpanContext extract = tracer.Extract(BuiltinFormats.HttpHeaders, new TextMapExtractAdapter(injectMap)); + + tracer.BuildSpan("bar") + .AsChildOf(extract) + .Start() + .Finish(); + } + List finishedSpans = tracer.FinishedSpans(); + + Assert.Equal(2, finishedSpans.Count); + Assert.Equal(finishedSpans[0].Context.TraceId, finishedSpans[1].Context.TraceId); + Assert.Equal(finishedSpans[0].Context.SpanId, finishedSpans[1].ParentId); + } + + [Fact] + public void TestActiveSpan() + { + MockTracer mockTracer = new MockTracer(); + Assert.Null(mockTracer.ActiveSpan); + + using (mockTracer.BuildSpan("foo").StartActive(finishSpanOnDispose: true)) + { + Assert.Equal(mockTracer.ScopeManager.Active.Span, mockTracer.ActiveSpan); + } + + Assert.Null(mockTracer.ActiveSpan); + } + + [Fact] + public void TestReset() + { + MockTracer mockTracer = new MockTracer(); + + mockTracer.BuildSpan("foo") + .Start() + .Finish(); + + Assert.Single(mockTracer.FinishedSpans()); + mockTracer.Reset(); + Assert.Empty(mockTracer.FinishedSpans()); + } + + [Fact] + public void TestFollowFromReference() + { + MockTracer tracer = new MockTracer(Propagators.TextMap); + MockSpan precedent = (MockSpan)tracer.BuildSpan("precedent").Start(); + + MockSpan followingSpan = (MockSpan)tracer.BuildSpan("follows") + .AddReference(References.FollowsFrom, precedent.Context) + .Start(); + + Assert.Equal(precedent.Context.SpanId, followingSpan.ParentId); + Assert.Single(followingSpan.References); + + MockSpan.Reference followsFromRef = followingSpan.References[0]; + + Assert.Equal(new MockSpan.Reference(precedent.Context, References.FollowsFrom), followsFromRef); + } + + [Fact] + public void TestMultiReferences() + { + MockTracer tracer = new MockTracer(Propagators.TextMap); + MockSpan parent = (MockSpan)tracer.BuildSpan("parent").Start(); + MockSpan precedent = (MockSpan)tracer.BuildSpan("precedent").Start(); + + MockSpan followingSpan = (MockSpan)tracer.BuildSpan("follows") + .AddReference(References.FollowsFrom, precedent.Context) + .AsChildOf(parent.Context) + .Start(); + + Assert.Equal(parent.Context.SpanId, followingSpan.ParentId); + Assert.Equal(2, followingSpan.References.Count); + + MockSpan.Reference followsFromRef = followingSpan.References[0]; + MockSpan.Reference parentRef = followingSpan.References[1]; + + Assert.Equal(new MockSpan.Reference(precedent.Context, References.FollowsFrom), followsFromRef); + Assert.Equal(new MockSpan.Reference(parent.Context, References.ChildOf), parentRef); + } + + [Fact] + public void TestMultiReferencesBaggage() + { + MockTracer tracer = new MockTracer(Propagators.TextMap); + MockSpan parent = (MockSpan)tracer.BuildSpan("parent").Start(); + parent.SetBaggageItem("parent", "foo"); + MockSpan precedent = (MockSpan)tracer.BuildSpan("precedent").Start(); + precedent.SetBaggageItem("precedent", "bar"); + + MockSpan followingSpan = (MockSpan)tracer.BuildSpan("follows") + .AddReference(References.FollowsFrom, precedent.Context) + .AsChildOf(parent.Context) + .Start(); + + Assert.Equal("foo", followingSpan.GetBaggageItem("parent")); + Assert.Equal("bar", followingSpan.GetBaggageItem("precedent")); + } + + [Fact] + public void TestNonStandardReference() + { + MockTracer tracer = new MockTracer(Propagators.TextMap); + MockSpan parent = (MockSpan)tracer.BuildSpan("parent").Start(); + + MockSpan nextSpan = (MockSpan)tracer.BuildSpan("follows") + .AddReference("a_reference", parent.Context) + .Start(); + + Assert.Equal(parent.Context.SpanId, nextSpan.ParentId); + Assert.Single(nextSpan.References); + Assert.Equal(nextSpan.References[0], + new MockSpan.Reference(parent.Context, "a_reference")); + } + + [Fact] + public void TestDefaultConstructor() + { + MockTracer mockTracer = new MockTracer(); + IScope scope = mockTracer.BuildSpan("foo").StartActive(true); + Assert.Equal(scope, mockTracer.ScopeManager.Active); + + var propag = new Dictionary(); + mockTracer.Inject(scope.Span.Context, BuiltinFormats.TextMap, new TextMapInjectAdapter(propag)); + Assert.True(propag.Any()); + } + + [Fact] + public void TestChildOfWithNullParentDoesNotThrowException() + { + MockTracer tracer = new MockTracer(); + ISpan parent = null; + ISpan span = tracer.BuildSpan("foo").AsChildOf(parent).Start(); + span.Finish(); + } + } +} From 5507bb4daa210e7ff49c0d86adb52cea76fa239f Mon Sep 17 00:00:00 2001 From: Christian Weiss Date: Tue, 13 Feb 2018 14:34:46 +0100 Subject: [PATCH 4/6] small fixes --- src/OpenTracing/Mock/MockSpan.cs | 4 ++-- src/OpenTracing/Mock/MockSpanContext.cs | 3 +-- src/OpenTracing/Mock/MockTracer.cs | 3 ++- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/OpenTracing/Mock/MockSpan.cs b/src/OpenTracing/Mock/MockSpan.cs index f695d08..00851ea 100644 --- a/src/OpenTracing/Mock/MockSpan.cs +++ b/src/OpenTracing/Mock/MockSpan.cs @@ -7,7 +7,7 @@ namespace OpenTracing.Mock { /// - /// MockSpans are created via MockTracer.BuildSpan(...), but they are also returned via calls to + /// MockSpans are created via , but they are also returned via calls to /// . They provide accessors to all Span state. /// /// @@ -108,7 +108,7 @@ public MockSpan( _tags = initialTags == null ? new Dictionary() - : initialTags.ToDictionary(k => k.Key, v => v.Value); + : new Dictionary(initialTags); _references = references == null ? new List() diff --git a/src/OpenTracing/Mock/MockSpanContext.cs b/src/OpenTracing/Mock/MockSpanContext.cs index 6921f03..4a127dc 100644 --- a/src/OpenTracing/Mock/MockSpanContext.cs +++ b/src/OpenTracing/Mock/MockSpanContext.cs @@ -50,8 +50,7 @@ public string GetBaggageItem(string key) /// public MockSpanContext WithBaggageItem(string key, string val) { - IDictionary newBaggage = _baggage - .ToDictionary(k => k.Key, v => v.Value); + var newBaggage = new Dictionary(_baggage); newBaggage[key] = val; diff --git a/src/OpenTracing/Mock/MockTracer.cs b/src/OpenTracing/Mock/MockTracer.cs index ace40d5..18ba579 100644 --- a/src/OpenTracing/Mock/MockTracer.cs +++ b/src/OpenTracing/Mock/MockTracer.cs @@ -43,7 +43,8 @@ public MockTracer(IScopeManager scopeManager, IPropagator propagator) } /// - /// Create a new that passes through any calls to Inject() and/or Extract(). + /// Create a new that passes through any calls + /// to and/or . /// public MockTracer(IPropagator propagator) : this(NoopScopeManager.Instance, propagator) From 1812e737b7c5a8e3aaddefc2322a114913106b1d Mon Sep 17 00:00:00 2001 From: Christian Weiss Date: Fri, 16 Feb 2018 09:55:11 +0100 Subject: [PATCH 5/6] Reference is now a class again and implements Equals (generated by VS) --- src/OpenTracing/Mock/MockSpan.cs | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/OpenTracing/Mock/MockSpan.cs b/src/OpenTracing/Mock/MockSpan.cs index 00851ea..897ed4e 100644 --- a/src/OpenTracing/Mock/MockSpan.cs +++ b/src/OpenTracing/Mock/MockSpan.cs @@ -287,7 +287,7 @@ public LogEntry(DateTimeOffset timestamp, IDictionary fields) } } - public struct Reference + public sealed class Reference : IEquatable { public MockSpanContext Context { get; } @@ -298,8 +298,28 @@ public struct Reference public Reference(MockSpanContext context, string referenceType) { - Context = context; - ReferenceType = referenceType; + Context = context ?? throw new ArgumentNullException(nameof(context)); + ReferenceType = referenceType ?? throw new ArgumentNullException(nameof(referenceType)); + } + + public override bool Equals(object obj) + { + return Equals(obj as Reference); + } + + public bool Equals(Reference other) + { + return other != null && + EqualityComparer.Default.Equals(Context, other.Context) && + ReferenceType == other.ReferenceType; + } + + public override int GetHashCode() + { + var hashCode = 2083322454; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Context); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ReferenceType); + return hashCode; } } } From d7b1309c8b2e8dcba499e9e19ab3bee780bd04ff Mon Sep 17 00:00:00 2001 From: Christian Weiss Date: Tue, 20 Feb 2018 09:34:55 +0100 Subject: [PATCH 6/6] PrinterPropagator -> ConsolePropagator --- src/OpenTracing/Mock/Propagators.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/OpenTracing/Mock/Propagators.cs b/src/OpenTracing/Mock/Propagators.cs index f3b5df8..4f9d151 100644 --- a/src/OpenTracing/Mock/Propagators.cs +++ b/src/OpenTracing/Mock/Propagators.cs @@ -6,7 +6,7 @@ namespace OpenTracing.Mock { public static class Propagators { - public static readonly IPropagator Printer = new PrinterPropagator(); + public static readonly IPropagator Console = new ConsolePropagator(); public static readonly IPropagator TextMap = new TextMapPropagator(); } @@ -21,7 +21,7 @@ public interface IPropagator MockSpanContext Extract(IFormat format, TCarrier carrier); } - public sealed class PrinterPropagator : IPropagator + public sealed class ConsolePropagator : IPropagator { public void Inject(MockSpanContext context, IFormat format, TCarrier carrier) { @@ -58,7 +58,7 @@ public void Inject(MockSpanContext context, IFormat format, } else { - throw new UnsupportedFormatException($"unknown carrier [{carrier.GetType()}]"); + throw new InvalidOperationException($"Unknown carrier [{carrier.GetType()}]"); } } @@ -89,7 +89,7 @@ public MockSpanContext Extract(IFormat format, TCarrier carr } else { - throw new UnsupportedFormatException($"unknown carrier [{carrier.GetType()}]"); + throw new InvalidOperationException($"Unknown carrier [{carrier.GetType()}]"); } if (traceId.HasValue && spanId.HasValue)