From 760f41c2aff5d4646096ccc677dcb0b8dc5fe3f0 Mon Sep 17 00:00:00 2001 From: Alex Hemsath <57361211+nr-ahemsath@users.noreply.github.com> Date: Fri, 27 Oct 2023 09:30:42 -0700 Subject: [PATCH] Add integration tests for MassTransit (#2014) * Initial exerciser implementation * Cleanup * Build works for all TFMS * Add legacy (7.x) exerciser Also cleanup, and rename start/stop methods * Unify v7/v8 exercisers Also pass in queue name as a parameter to StartBus to help with test assertions * Implement send * Adding alternate instrumentation point and first pass at tests (#2001) * Checkpoint trying to get hosted service model working for v7 * Message consume is working in v7! * Add instrumentation project readmes * Added regex scope, tests are passing * Test send as well as publish; cleanup * Expanded tests, updated instrumentation for v7 * Make regex metric name assertions more specific --------- Co-authored-by: Chris Hynes <111462425+chynesNR@users.noreply.github.com> --- .../Wrapper/MassTransit/Instrumentation.xml | 3 + .../Providers/Wrapper/MassTransit/README.md | 5 + .../MassTransit/TransportConfigWrapper.cs | 2 + .../MassTransitLegacy/Instrumentation.xml | 11 +- .../Wrapper/MassTransitLegacy/README.md | 5 + .../TransportConfigLegacyWrapper.cs | 2 + .../IntegrationTestHelpers/Assertions.cs | 10 +- .../MassTransit/MassTransitTests.cs | 161 ++++++++++++++++++ .../MultiFunctionApplicationHelpers.csproj | 29 ++++ .../MassTransit/MassTransitExerciser.cs | 123 +++++++++++++ .../MassTransit/Message.cs | 10 ++ .../MassTransit/MessageConsumer.cs | 17 ++ 12 files changed, 374 insertions(+), 4 deletions(-) create mode 100644 src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MassTransit/README.md create mode 100644 src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MassTransitLegacy/README.md create mode 100644 tests/Agent/IntegrationTests/IntegrationTests/MassTransit/MassTransitTests.cs create mode 100644 tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MassTransit/MassTransitExerciser.cs create mode 100644 tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MassTransit/Message.cs create mode 100644 tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MassTransit/MessageConsumer.cs diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MassTransit/Instrumentation.xml b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MassTransit/Instrumentation.xml index ff57986f1d..c4568da3ca 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MassTransit/Instrumentation.xml +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MassTransit/Instrumentation.xml @@ -12,6 +12,9 @@ SPDX-License-Identifier: Apache-2.0 + + + diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MassTransit/README.md b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MassTransit/README.md new file mode 100644 index 0000000000..ced3c1a943 --- /dev/null +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MassTransit/README.md @@ -0,0 +1,5 @@ +This is the wrapper and instrumentation project for version 8.0.0 and higher of [MassTransit](https://masstransit.io/). + +The code in this project is nearly identical to that in the `MassTransitLegacy` project, which is for MassTransit v7. However, different MassTransit types are referenced +in the two projects. The team decided at the time this instrumentation was introduced that eliminating the duplicate code would make things far less readable, +and that code duplication was the lesser of two evils. diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MassTransit/TransportConfigWrapper.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MassTransit/TransportConfigWrapper.cs index 34ffa7252b..3c65cffe08 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MassTransit/TransportConfigWrapper.cs +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MassTransit/TransportConfigWrapper.cs @@ -22,6 +22,8 @@ public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall ins { // This will be run for each bus. Each bus gets one transport. // We can support more than one transport with this setup. + // Note that there are two instrumentation points that can get us here, and the first parameter is different for both, + // but they both implement IBusFactoryConfigurator, which is what we need var configurator = instrumentedMethodCall.MethodCall.MethodArguments.ExtractNotNullAs(0); var spec = new NewRelicPipeSpecification(agent); diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MassTransitLegacy/Instrumentation.xml b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MassTransitLegacy/Instrumentation.xml index f3ebecb3ec..cf00b0a35c 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MassTransitLegacy/Instrumentation.xml +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MassTransitLegacy/Instrumentation.xml @@ -9,9 +9,18 @@ SPDX-License-Identifier: Apache-2.0 - + + + + + + + + + + diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MassTransitLegacy/README.md b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MassTransitLegacy/README.md new file mode 100644 index 0000000000..96a3b02c57 --- /dev/null +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MassTransitLegacy/README.md @@ -0,0 +1,5 @@ +This is the wrapper and instrumentation project for version 7.x of [MassTransit](https://masstransit.io/). + +The code in this project is nearly identical to that in the `MassTransit` project, which is for MassTransit v8. However, different MassTransit types are referenced +in the two projects. The team decided at the time this instrumentation was introduced that eliminating the duplicate code would make things far less readable, +and that code duplication was the lesser of two evils. diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MassTransitLegacy/TransportConfigLegacyWrapper.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MassTransitLegacy/TransportConfigLegacyWrapper.cs index df42f021ce..845d574fa3 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MassTransitLegacy/TransportConfigLegacyWrapper.cs +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/MassTransitLegacy/TransportConfigLegacyWrapper.cs @@ -22,6 +22,8 @@ public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall ins { // This will be run for each bus. Each bus gets one transport. // We can support more than one transport with this setup. + // Note that there are two instrumentation points that can get us here, and the first parameter is different for both, + // but they both implement IBusFactoryConfigurator, which is what we need var configurator = instrumentedMethodCall.MethodCall.MethodArguments.ExtractNotNullAs(0); var spec = new NewRelicPipeSpecification(agent); diff --git a/tests/Agent/IntegrationTests/IntegrationTestHelpers/Assertions.cs b/tests/Agent/IntegrationTests/IntegrationTestHelpers/Assertions.cs index fb8adc6963..0b3496cc2b 100644 --- a/tests/Agent/IntegrationTests/IntegrationTestHelpers/Assertions.cs +++ b/tests/Agent/IntegrationTests/IntegrationTestHelpers/Assertions.cs @@ -573,7 +573,9 @@ public static Metric TryFindMetric(ExpectedMetric expectedMetric, IEnumerable TryFindMetrics(ExpectedMetric expectedMetric, IEnumer var foundMetrics = actualMetrics .Where(actualMetric => (expectedMetric.IsRegexName && Regex.IsMatch(actualMetric.MetricSpec.Name, expectedMetric.metricName)) || (!expectedMetric.IsRegexName && expectedMetric.metricName == actualMetric.MetricSpec.Name)) - .Where(actualMetric => expectedMetric.metricScope == actualMetric.MetricSpec.Scope) + .Where(actualMetric => (expectedMetric.IsRegexScope && Regex.IsMatch(actualMetric.MetricSpec.Scope, expectedMetric.metricScope)) || + (!expectedMetric.IsRegexScope && expectedMetric.metricScope == actualMetric.MetricSpec.Scope)) .ToList(); return foundMetrics; @@ -1113,10 +1116,11 @@ public class ExpectedMetric public decimal? callCount = null; public decimal? CallCountAllHarvests = null; public bool IsRegexName = false; + public bool IsRegexScope = false; public override string ToString() { - return $"{{ metricName: {metricName} metricScope: {metricScope}, IsRegexName: {IsRegexName}, callCount: {callCount}, CallCountAllHarvests: {CallCountAllHarvests} }}"; + return $"{{ metricName: {metricName} metricScope: {metricScope}, IsRegexName: {IsRegexName}, IsRegexScope: {IsRegexScope}, callCount: {callCount}, CallCountAllHarvests: {CallCountAllHarvests} }}"; } } diff --git a/tests/Agent/IntegrationTests/IntegrationTests/MassTransit/MassTransitTests.cs b/tests/Agent/IntegrationTests/IntegrationTests/MassTransit/MassTransitTests.cs new file mode 100644 index 0000000000..1e1075b3b4 --- /dev/null +++ b/tests/Agent/IntegrationTests/IntegrationTests/MassTransit/MassTransitTests.cs @@ -0,0 +1,161 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System.Collections.Generic; +using System.Linq; +using NewRelic.Agent.IntegrationTestHelpers; +using Xunit; +using Xunit.Abstractions; +using System; +using NewRelic.Agent.IntegrationTestHelpers.RemoteServiceFixtures; + +namespace NewRelic.Agent.IntegrationTests.MassTransit +{ + public abstract class MassTransitTestsBase : NewRelicIntegrationTest + where TFixture : ConsoleDynamicMethodFixture + { + private readonly TFixture _fixture; + + public MassTransitTestsBase(TFixture fixture, ITestOutputHelper output, bool useStartBus) : base(fixture) + { + _fixture = fixture; + _fixture.SetTimeout(TimeSpan.FromMinutes(2)); + _fixture.TestLogger = output; + + if (useStartBus) + { + _fixture.AddCommand($"MassTransitExerciser StartBus"); + } + else + { + _fixture.AddCommand("MassTransitExerciser StartHost"); + } + _fixture.AddCommand("MassTransitExerciser Publish publishedMessageOne"); + _fixture.AddCommand("MassTransitExerciser Publish publishedMessageTwo"); + _fixture.AddCommand("MassTransitExerciser Send sentMessageOne"); + _fixture.AddCommand("MassTransitExerciser Send sentMessageTwo"); + + if (useStartBus) + { + _fixture.AddCommand($"MassTransitExerciser StopBus"); + } + else + { + _fixture.AddCommand("MassTransitExerciser StopHost"); + } + + _fixture.AddActions + ( + setupConfiguration: () => + { + var configModifier = new NewRelicConfigModifier(fixture.DestinationNewRelicConfigFilePath); + configModifier.ForceTransactionTraces(); + configModifier.SetLogLevel("finest"); + }, + exerciseApplication: () => + { + _fixture.AgentLog.WaitForLogLine(AgentLogBase.MetricDataLogLineRegex, TimeSpan.FromMinutes(2)); + } + ); + + _fixture.Initialize(); + } + + [Fact] + public void Test() + { + + var massTransitMetricNameRegexBase = @"MessageBroker\/MassTransit\/Queue\/"; + var queueNameRegex = @"Named\/(.{26})"; // The auto-generated in-memory queue names have 26 chars + var massTransitProduceMetricNameRegex = massTransitMetricNameRegexBase + @"Produce\/" + queueNameRegex; + var massTransitConsumeMetricNameRegex = massTransitMetricNameRegexBase + @"Consume\/" + queueNameRegex; + + var metrics = _fixture.AgentLog.GetMetrics().ToList(); + + var expectedMetrics = new List + { + new Assertions.ExpectedMetric { metricName = massTransitConsumeMetricNameRegex, callCount = 4, IsRegexName = true}, + new Assertions.ExpectedMetric { metricName = massTransitProduceMetricNameRegex, callCount = 4, IsRegexName = true}, + + new Assertions.ExpectedMetric { metricName = massTransitConsumeMetricNameRegex, callCount = 4, IsRegexName = true, metricScope = @"OtherTransaction\/Message\/MassTransit\/Queue\/" + queueNameRegex, IsRegexScope = true}, + new Assertions.ExpectedMetric { metricName = massTransitProduceMetricNameRegex, callCount = 2, IsRegexName = true, metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.MassTransitExerciser/Publish"}, + new Assertions.ExpectedMetric { metricName = massTransitProduceMetricNameRegex, callCount = 2, IsRegexName = true, metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.MassTransitExerciser/Send"}, + }; + + Assertions.MetricsExist(expectedMetrics, metrics); + + var transactionEvent = _fixture.AgentLog.TryGetTransactionEvent($"OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.MassTransitExerciser/Publish"); + + Assert.NotNull( transactionEvent ); + } + } + + // Tests using StartHost (hosted service configuration method) + [NetFrameworkTest] + public class MassTransitTests_StartHost_FW462 : MassTransitTestsBase + { + public MassTransitTests_StartHost_FW462(ConsoleDynamicMethodFixtureFW462 fixture, ITestOutputHelper output) + : base(fixture, output, false) + { + } + } + [NetFrameworkTest] + public class MassTransitTests_StartHost_FWLatest : MassTransitTestsBase + { + public MassTransitTests_StartHost_FWLatest(ConsoleDynamicMethodFixtureFWLatest fixture, ITestOutputHelper output) + : base(fixture, output, false) + { + } + } + [NetCoreTest] + public class MassTransitTests_StartHost_Core60 : MassTransitTestsBase + { + public MassTransitTests_StartHost_Core60(ConsoleDynamicMethodFixtureCore60 fixture, ITestOutputHelper output) + : base(fixture, output, false) + { + } + } + [NetCoreTest] + public class MassTransitTests_StartHost_CoreLatest : MassTransitTestsBase + { + public MassTransitTests_StartHost_CoreLatest(ConsoleDynamicMethodFixtureCoreLatest fixture, ITestOutputHelper output) + : base(fixture, output, false) + { + } + } + + // Tests using StartBus (bus factory configuration method) + [NetFrameworkTest] + public class MassTransitTests_StartBus_FW462 : MassTransitTestsBase + { + public MassTransitTests_StartBus_FW462(ConsoleDynamicMethodFixtureFW462 fixture, ITestOutputHelper output) + : base(fixture, output, true) + { + } + } + [NetFrameworkTest] + public class MassTransitTests_StartBus_FWLatest : MassTransitTestsBase + { + public MassTransitTests_StartBus_FWLatest(ConsoleDynamicMethodFixtureFWLatest fixture, ITestOutputHelper output) + : base(fixture, output, true) + { + } + } + [NetCoreTest] + public class MassTransitTests_StartBus_Core60 : MassTransitTestsBase + { + public MassTransitTests_StartBus_Core60(ConsoleDynamicMethodFixtureCore60 fixture, ITestOutputHelper output) + : base(fixture, output, true) + { + } + } + [NetCoreTest] + public class MassTransitTests_StartBus_CoreLatest : MassTransitTestsBase + { + public MassTransitTests_StartBus_CoreLatest(ConsoleDynamicMethodFixtureCoreLatest fixture, ITestOutputHelper output) + : base(fixture, output, true) + { + } + } + +} diff --git a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/MultiFunctionApplicationHelpers.csproj b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/MultiFunctionApplicationHelpers.csproj index f3564ea268..691dddac80 100644 --- a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/MultiFunctionApplicationHelpers.csproj +++ b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/MultiFunctionApplicationHelpers.csproj @@ -262,6 +262,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +