From e6f863b80779b3ba126b3222a58c74a30792546d Mon Sep 17 00:00:00 2001 From: Steven Bouwkamp Date: Fri, 20 Mar 2026 11:29:58 -0400 Subject: [PATCH 01/28] Dispose Testcontainers and update to V4 --- .../AerospikeTests.cs | 4 +- ....Trace.ClrProfiler.IntegrationTests.csproj | 2 +- .../Helpers/ContainersCollection.cs | 21 ++++++ .../Containers/AerospikeFixture.cs | 13 ++-- .../Containers/ContainerFixture.cs | 23 +++++- .../Containers/ContainersRegistry.cs | 72 ------------------- ...ace.TestHelpers.AutoInstrumentation.csproj | 3 +- 7 files changed, 53 insertions(+), 85 deletions(-) create mode 100644 tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs delete mode 100644 tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ContainersRegistry.cs diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AerospikeTests.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AerospikeTests.cs index 63cb6c7aa8bd..47ecbed11028 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AerospikeTests.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AerospikeTests.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Datadog.Trace.ClrProfiler.IntegrationTests.Helpers; using Datadog.Trace.Configuration; using Datadog.Trace.TestHelpers; using Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; @@ -21,7 +22,8 @@ namespace Datadog.Trace.ClrProfiler.IntegrationTests [Trait("RequiresDockerDependency", "true")] [Trait("DockerGroup", "2")] [UsesVerify] - public class AerospikeTests : TracingIntegrationTest, IClassFixture + [Collection(AerospikeCollection.Name)] + public class AerospikeTests : TracingIntegrationTest { public AerospikeTests(ITestOutputHelper output, AerospikeFixture aerospikeFixture) : base("Aerospike", output) diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Datadog.Trace.ClrProfiler.IntegrationTests.csproj b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Datadog.Trace.ClrProfiler.IntegrationTests.csproj index dd3a4309eab1..0e8c9543bb64 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Datadog.Trace.ClrProfiler.IntegrationTests.csproj +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Datadog.Trace.ClrProfiler.IntegrationTests.csproj @@ -32,7 +32,7 @@ - + diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs new file mode 100644 index 000000000000..c05c317b4d4f --- /dev/null +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs @@ -0,0 +1,21 @@ +// +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. +// +#pragma warning disable SA1649 // File name should match first type name (this will just store all the classes) +#pragma warning disable SA1402 // File may only contain a single type (this will just store all the classes) +using Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; +using Xunit; + +namespace Datadog.Trace.ClrProfiler.IntegrationTests.Helpers +{ + [CollectionDefinition(Name)] + + public class AerospikeCollection : ICollectionFixture + { + public const string Name = "Aerospike"; + } +} + +#pragma warning restore SA1649 // File name should match first type name +#pragma warning restore SA1402 // File may only contain a single type diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/AerospikeFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/AerospikeFixture.cs index 2bd2819cb097..303123d63116 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/AerospikeFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/AerospikeFixture.cs @@ -30,20 +30,19 @@ protected override async Task InitializeResources(Action registe // pinning to a known good version because the latest version // (6.3.0.5 at time of issue) causes 'Server memory error' and flake // Keep syncronized image version with docker-compose.yml - var container = new ContainerBuilder() - .WithImage("aerospike/aerospike-server:6.2.0.6") + var container = new ContainerBuilder("aerospike/aerospike-server:6.2.0.6") .WithPortBinding(AerospikePort, true) .WithCreateParameterModifier(p => - { - p.HostConfig ??= new HostConfig(); - p.HostConfig.Ulimits = new List + { + p.HostConfig ??= new HostConfig(); + p.HostConfig.Ulimits = new List { // Aerospike requires a minimum of 15000 file descriptors, otherwise it'll fail to start // Some versions of dockerengine set a lower limit (1024) new Ulimit { Name = "nofile", Soft = 15000, Hard = 15000 } }; - }) - .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(AerospikePort)) + }) + .WithWaitStrategy(Wait.ForUnixContainer().UntilInternalTcpPortIsAvailable(AerospikePort)) .Build(); await container.StartAsync(); diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ContainerFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ContainerFixture.cs index 649251d7d202..c0fa6d203fb2 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ContainerFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ContainerFixture.cs @@ -19,11 +19,28 @@ public abstract class ContainerFixture : IAsyncLifetime public async Task InitializeAsync() { - _resources = await ContainersRegistry.GetOrAdd(GetType(), InitializeResources); + _resources = await InitializeResources().ConfigureAwait(false); } - // Do not implement, the ContainersRegistry is responsible for disposing the containers - public Task DisposeAsync() => Task.CompletedTask; + public async Task DisposeAsync() + { + if (_resources is null) + { + return; + } + + foreach (var resource in _resources.Values) + { + if (resource is IAsyncDisposable asyncDisposable) + { + await asyncDisposable.DisposeAsync().ConfigureAwait(false); + } + else if (resource is IDisposable disposable) + { + disposable.Dispose(); + } + } + } public virtual IEnumerable> GetEnvironmentVariables() => Enumerable.Empty>(); diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ContainersRegistry.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ContainersRegistry.cs deleted file mode 100644 index 7c7d6f85e530..000000000000 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ContainersRegistry.cs +++ /dev/null @@ -1,72 +0,0 @@ -// -// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. -// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. -// - -#nullable enable - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; - -public static class ContainersRegistry -{ - private static readonly ConcurrentDictionary>> Resources = new(); - - public static async Task> GetOrAdd(Type type, Func>> createResources) - { - if (!Resources.TryGetValue(type, out var task)) - { - var tcs = new TaskCompletionSource>(TaskCreationOptions.RunContinuationsAsynchronously); - - task = Resources.GetOrAdd(type, tcs.Task); - - if (task == tcs.Task) - { - try - { - var resources = await createResources().ConfigureAwait(false); - tcs.SetResult(resources); - } - catch (Exception ex) - { - tcs.SetException(ex); - } - } - } - - return await task.ConfigureAwait(false); - } - - public static async Task DisposeAll() - { - foreach (var resourceGroup in Resources.Values) - { - try - { - var resources = await resourceGroup.ConfigureAwait(false); - - foreach (var resource in resources.Values) - { - if (resource is IAsyncDisposable asyncDisposable) - { - await asyncDisposable.DisposeAsync().ConfigureAwait(false); - } - else if (resource is IDisposable disposable) - { - disposable.Dispose(); - } - } - } - catch - { - // Exceptions are expected here, if the container failed to initialize - } - } - - Resources.Clear(); - } -} diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Datadog.Trace.TestHelpers.AutoInstrumentation.csproj b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Datadog.Trace.TestHelpers.AutoInstrumentation.csproj index 499642cefe0f..91191b37681d 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Datadog.Trace.TestHelpers.AutoInstrumentation.csproj +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Datadog.Trace.TestHelpers.AutoInstrumentation.csproj @@ -1,8 +1,9 @@ + - + From 36b956db39010e0bc4b2b8a4a4527ccaf1ed068b Mon Sep 17 00:00:00 2001 From: Steven Bouwkamp Date: Thu, 23 Apr 2026 15:57:55 -0400 Subject: [PATCH 02/28] Remove DockerTestFramework container pre-warming --- .../CustomTestFramework.cs | 2 +- .../DockerTestFramework.cs | 59 ------------------- 2 files changed, 1 insertion(+), 60 deletions(-) delete mode 100644 tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/DockerTestFramework.cs diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/CustomTestFramework.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/CustomTestFramework.cs index a81d67e89b12..8048439bd367 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/CustomTestFramework.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/CustomTestFramework.cs @@ -12,7 +12,7 @@ namespace Datadog.Trace.ClrProfiler.IntegrationTests { - public class CustomTestFramework : TestHelpers.AutoInstrumentation.DockerTestFramework + public class CustomTestFramework : TestHelpers.CustomTestFramework { public CustomTestFramework(IMessageSink messageSink) : base(messageSink, typeof(Instrumentation)) diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/DockerTestFramework.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/DockerTestFramework.cs deleted file mode 100644 index 1d998299f215..000000000000 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/DockerTestFramework.cs +++ /dev/null @@ -1,59 +0,0 @@ -// -// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. -// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. -// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; -using Xunit; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Datadog.Trace.TestHelpers.AutoInstrumentation; - -public class DockerTestFramework : CustomTestFramework -{ - public DockerTestFramework(IMessageSink messageSink) - : base(messageSink) - { - } - - public DockerTestFramework(IMessageSink messageSink, Type typeTestedAssembly) - : base(messageSink, typeTestedAssembly) - { - } - - protected override async Task RunTestCollectionsCallback(IMessageSink diagnosticsMessageSink, IEnumerable testCases) - { - var containerFixtures = testCases - .Select(t => t.Method.ToRuntimeMethod().DeclaringType) - .Where(t => t.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IClassFixture<>))) - .ToList(); - - foreach (var type in containerFixtures) - { - // Retrieve all the types of container fixtures - var fixtureTypes = type.GetInterfaces() - .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IClassFixture<>)) - .Select(i => i.GetGenericArguments()[0]) - .Where(t => typeof(ContainerFixture).IsAssignableFrom(t)) - .ToList(); - - foreach (var fixtureType in fixtureTypes) - { - try - { - var fixture = (ContainerFixture)Activator.CreateInstance(fixtureType); - await fixture!.InitializeAsync(); - } - catch (Exception ex) - { - DiagnosticMessageSink.OnMessage(new DiagnosticMessage($"ERROR: {fixtureType.Name} ({ex.Message})")); - } - } - } - } -} From b7e07418ba9336b64a48c52acf47ac0852b86b41 Mon Sep 17 00:00:00 2001 From: Steven Bouwkamp Date: Wed, 29 Jul 2026 10:23:32 -0400 Subject: [PATCH 03/28] Migrate ServiceStack.Redis to Testcontainers --- docker-compose.yml | 23 ++--------- .../Helpers/ContainersCollection.cs | 11 ++++- .../ServiceStackRedisTests.cs | 16 +++++--- .../Containers/ServiceStackRedisFixture.cs | 40 +++++++++++++++++++ 4 files changed, 64 insertions(+), 26 deletions(-) create mode 100644 tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ServiceStackRedisFixture.cs diff --git a/docker-compose.yml b/docker-compose.yml index bbd5fac6b772..c8e2b6af4220 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -53,9 +53,6 @@ services: servicestackredis_arm64: image: redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20 - command: redis-server --bind 0.0.0.0 - ports: - - "6379" sqledge_arm64: image: mcr.microsoft.com/azure-sql-edge:latest@sha256:902628a8be89e35dfb7895ca31d602974c7bafde4d583a0d0873844feb1c42cf @@ -114,10 +111,6 @@ services: servicestackredis: image: redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20 - profiles: ["group1"] - command: redis-server --bind 0.0.0.0 - ports: - - "127.0.0.1:6379:6379" stackexchangeredis: image: redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20 @@ -436,7 +429,6 @@ services: - DiffEngine_Disabled=true - IncludeAllTestFrameworks - MONGO_HOST=mongo - - SERVICESTACK_REDIS_HOST=servicestackredis:6379 - STACKEXCHANGE_REDIS_HOST=stackexchangeredis:6379,stackexchangeredis-replica:6379 - STACKEXCHANGE_REDIS_SINGLE_HOST=stackexchangeredis-single:6379 - ELASTICSEARCH7_HOST=elasticsearch7:9200 @@ -653,7 +645,6 @@ services: image: andrewlock/wait-for-dependencies:latest@sha256:5d87561de8c019c3954298f707c1f6d7087f620a1ce36c3ec38cdfbc1ec4066e profiles: ["group1"] depends_on: - - servicestackredis - stackexchangeredis - stackexchangeredis-replica - stackexchangeredis-single @@ -668,7 +659,7 @@ services: - test-agent environment: - TIMEOUT_LENGTH=120 - command: servicestackredis:6379 stackexchangeredis:6379 stackexchangeredis-replica:6379 stackexchangeredis-single:6379 sqlserver:1433 postgres:5432 mysql:3306 mysql57:3306 rabbitmq:5672 kafka-broker:9092 kafka-zookeeper:2181 couchbase:11210 test-agent:8126 test-agent:4317 test-agent:4318 + command: stackexchangeredis:6379 stackexchangeredis-replica:6379 stackexchangeredis-single:6379 sqlserver:1433 postgres:5432 mysql:3306 mysql57:3306 rabbitmq:5672 kafka-broker:9092 kafka-zookeeper:2181 couchbase:11210 test-agent:8126 test-agent:4317 test-agent:4318 StartDependencies.Group2: image: andrewlock/wait-for-dependencies:latest@sha256:5d87561de8c019c3954298f707c1f6d7087f620a1ce36c3ec38cdfbc1ec4066e @@ -699,6 +690,7 @@ services: command: dotnet /build/bin/Debug/_build.dll RunIntegrationTests volumes: - ./:/project + - /var/run/docker.sock:/var/run/docker.sock cap_add: - SYS_PTRACE environment: @@ -718,7 +710,6 @@ services: - DD_CLR_ENABLE_NGEN=${DD_CLR_ENABLE_NGEN:-1} - IncludeAllTestFrameworks - MONGO_HOST=mongo_arm64 - - SERVICESTACK_REDIS_HOST=servicestackredis_arm64:6379 - STACKEXCHANGE_REDIS_HOST=stackexchangeredis_arm64:6379,stackexchangeredis_arm64-replica:6379 - STACKEXCHANGE_REDIS_SINGLE_HOST=stackexchangeredis_arm64-single:6379 - ELASTICSEARCH7_HOST=elasticsearch7_arm64:9200 @@ -760,7 +751,6 @@ services: - RANDOM_SEED - TEST_AGENT_HOST=test-agent depends_on: - - servicestackredis_arm64 - stackexchangeredis_arm64 - stackexchangeredis_arm64-replica - stackexchangeredis_arm64-single @@ -777,7 +767,6 @@ services: StartDependencies.ARM64: image: andrewlock/wait-for-dependencies:latest@sha256:5d87561de8c019c3954298f707c1f6d7087f620a1ce36c3ec38cdfbc1ec4066e depends_on: - - servicestackredis_arm64 - stackexchangeredis_arm64 - stackexchangeredis_arm64-replica - stackexchangeredis_arm64-single @@ -792,7 +781,7 @@ services: - cosmosdb-emulator_arm64 environment: - TIMEOUT_LENGTH=120 - command: servicestackredis_arm64:6379 stackexchangeredis_arm64:6379 stackexchangeredis_arm64-replica:6379 stackexchangeredis_arm64-single:6379 elasticsearch7_arm64:9200 sqledge_arm64:1433 mongo_arm64:27017 postgres_arm64:5432 mysql_arm64:3306 rabbitmq_arm64:5672 localstack_arm64:4566 test-agent:8126 test-agent:4317 test-agent:4318 cosmosdb-emulator_arm64:8081 + command: stackexchangeredis_arm64:6379 stackexchangeredis_arm64-replica:6379 stackexchangeredis_arm64-single:6379 elasticsearch7_arm64:9200 sqledge_arm64:1433 mongo_arm64:27017 postgres_arm64:5432 mysql_arm64:3306 rabbitmq_arm64:5672 localstack_arm64:4566 test-agent:8126 test-agent:4317 test-agent:4318 cosmosdb-emulator_arm64:8081 IntegrationTests.ARM64.Debugger: build: @@ -865,7 +854,6 @@ services: StartDependencies.OSXARM64: image: andrewlock/wait-for-dependencies:latest@sha256:5d87561de8c019c3954298f707c1f6d7087f620a1ce36c3ec38cdfbc1ec4066e depends_on: - - servicestackredis_osx_arm64 - stackexchangeredis_osx_arm64 - stackexchangeredis_osx_arm64-replica - stackexchangeredis_osx_arm64-single @@ -878,7 +866,7 @@ services: - localstack_osx_arm64 environment: - TIMEOUT_LENGTH=120 - command: servicestackredis_osx_arm64:6379 stackexchangeredis_osx_arm64:6379 stackexchangeredis_osx_arm64-replica:6379 stackexchangeredis_osx_arm64-single:6379 elasticsearch7_osx_arm64:9200 sqledge_osx_arm64:1433 mongo_osx_arm64:27017 postgres_osx_arm64:5432 mysql_osx_arm64:3306 rabbitmq_osx_arm64:5672 localstack_osx_arm64:4566 + command: stackexchangeredis_osx_arm64:6379 stackexchangeredis_osx_arm64-replica:6379 stackexchangeredis_osx_arm64-single:6379 elasticsearch7_osx_arm64:9200 sqledge_osx_arm64:1433 mongo_osx_arm64:27017 postgres_osx_arm64:5432 mysql_osx_arm64:3306 rabbitmq_osx_arm64:5672 localstack_osx_arm64:4566 # OSX ARM64 dependencies @@ -937,9 +925,6 @@ services: servicestackredis_osx_arm64: image: redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20 - command: redis-server --bind 0.0.0.0 - ports: - - "6379:6379" stackexchangeredis_osx_arm64: image: redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20 diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs index c05c317b4d4f..d801bfb434bc 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs @@ -9,12 +9,19 @@ namespace Datadog.Trace.ClrProfiler.IntegrationTests.Helpers { - [CollectionDefinition(Name)] - + // Container-backed collections must set DisableParallelization = true. CustomTestFramework only auto-serializes + // collections whose display name contains the assembly namespace, which named [CollectionDefinition]s lose. + [CollectionDefinition(Name, DisableParallelization = true)] public class AerospikeCollection : ICollectionFixture { public const string Name = "Aerospike"; } + + [CollectionDefinition(Name, DisableParallelization = true)] + public class ServiceStackRedisCollection : ICollectionFixture + { + public const string Name = "ServiceStackRedis"; + } } #pragma warning restore SA1649 // File name should match first type name diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/ServiceStackRedisTests.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/ServiceStackRedisTests.cs index ae359488f067..96a6d98d95d2 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/ServiceStackRedisTests.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/ServiceStackRedisTests.cs @@ -3,13 +3,14 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. // -using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Datadog.Trace.ClrProfiler.IntegrationTests.Helpers; using Datadog.Trace.Configuration; using Datadog.Trace.ExtensionMethods; using Datadog.Trace.TestHelpers; +using Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; using FluentAssertions; using FluentAssertions.Execution; using VerifyXunit; @@ -21,12 +22,17 @@ namespace Datadog.Trace.ClrProfiler.IntegrationTests [Trait("RequiresDockerDependency", "true")] [Trait("DockerGroup", "1")] [UsesVerify] + [Collection(ServiceStackRedisCollection.Name)] public class ServiceStackRedisTests : TracingIntegrationTest { - public ServiceStackRedisTests(ITestOutputHelper output) + private readonly ServiceStackRedisFixture _redisFixture; + + public ServiceStackRedisTests(ITestOutputHelper output, ServiceStackRedisFixture redisFixture) : base("ServiceStack.Redis", output) { + _redisFixture = redisFixture; SetServiceVersion("1.0.0"); + ConfigureContainers(redisFixture); } public static IEnumerable GetEnabledConfig() @@ -64,9 +70,9 @@ public async Task SubmitsTraces(string packageVersion, string metadataSchemaVers spans.Count.Should().Be(expectedSpans); ValidateIntegrationSpans(spans, metadataSchemaVersion, expectedServiceName: clientSpanServiceName, isExternalSpan); - var host = Environment.GetEnvironmentVariable("SERVICESTACK_REDIS_HOST") ?? "localhost:6379"; - var port = host.Substring(host.IndexOf(':') + 1); - host = host.Substring(0, host.IndexOf(':')); + var redisHost = _redisFixture.GetEnvironmentVariables().First(kvp => kvp.Key == "SERVICESTACK_REDIS_HOST").Value; + var port = redisHost.Substring(redisHost.IndexOf(':') + 1); + var host = redisHost.Substring(0, redisHost.IndexOf(':')); var settings = VerifyHelper.GetSpanVerifierSettings(); settings.UseFileName($"{nameof(ServiceStackRedisTests)}.RunServiceStack" + $".Schema{metadataSchemaVersion.ToUpper()}"); diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ServiceStackRedisFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ServiceStackRedisFixture.cs new file mode 100644 index 000000000000..e0357c68097b --- /dev/null +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ServiceStackRedisFixture.cs @@ -0,0 +1,40 @@ +// +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. +// + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using DotNet.Testcontainers.Builders; +using DotNet.Testcontainers.Containers; + +namespace Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; + +public class ServiceStackRedisFixture : ContainerFixture +{ + private const int RedisPort = 6379; + + protected IContainer Container => GetResource("container"); + + public override IEnumerable> GetEnvironmentVariables() + { + yield return new("SERVICESTACK_REDIS_HOST", $"{Container.Hostname}:{Container.GetMappedPublicPort(RedisPort)}"); + } + + protected override async Task InitializeResources(Action registerResource) + { + // Keep synchronized with the image version in docker-compose.yml. + var container = new ContainerBuilder("redis:4-alpine") + .WithCommand("redis-server", "--bind", "0.0.0.0") + .WithPortBinding(RedisPort, true) + .WithWaitStrategy(Wait.ForUnixContainer().UntilInternalTcpPortIsAvailable(RedisPort)) + .Build(); + + await container.StartAsync().ConfigureAwait(false); + + registerResource("container", container); + } +} From 90645003c75040ecc301acb2631479eac14a1784 Mon Sep 17 00:00:00 2001 From: Steven Bouwkamp Date: Wed, 29 Jul 2026 10:27:38 -0400 Subject: [PATCH 04/28] Migrate StackExchange.Redis to Testcontainers --- docker-compose.yml | 61 +--------- ...sAssemblyConflictLegacyProjectSmokeTest.cs | 7 +- ...edisAssemblyConflictSdkProjectSmokeTest.cs | 14 +-- ...ngeRedisStackOverflowExceptionSmokeTest.cs | 14 +-- .../StackExchangeRedisTests.cs | 46 +++---- .../StackExchangeRedisTestCollection.cs | 3 +- .../Containers/StackExchangeRedisFixture.cs | 115 ++++++++++++++++++ 7 files changed, 165 insertions(+), 95 deletions(-) create mode 100644 tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/StackExchangeRedisFixture.cs diff --git a/docker-compose.yml b/docker-compose.yml index c8e2b6af4220..93b8c520ab2c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -68,24 +68,12 @@ services: stackexchangeredis_arm64: image: redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20 - hostname: stackexchangeredis_arm64 - command: redis-server --bind 0.0.0.0 - ports: - - "6379" stackexchangeredis_arm64-replica: image: redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20 - hostname: stackexchangeredis_arm64-replica - command: redis-server --bind 0.0.0.0 --slaveof stackexchangeredis_arm64 6379 - ports: - - "127.0.0.1:6390:6379" stackexchangeredis_arm64-single: image: redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20 - hostname: stackexchangeredis_arm64-single - command: redis-server --bind 0.0.0.0 - ports: - - "127.0.0.1:6391:6379" # Dependencies localstack: @@ -114,27 +102,12 @@ services: stackexchangeredis: image: redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20 - profiles: ["group1"] - hostname: stackexchangeredis - command: redis-server --bind 0.0.0.0 - ports: - - "127.0.0.1:6389:6379" stackexchangeredis-replica: image: redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20 - profiles: ["group1"] - hostname: stackexchangeredis-replica - command: redis-server --bind 0.0.0.0 --slaveof stackexchangeredis 6379 - ports: - - "127.0.0.1:6390:6379" stackexchangeredis-single: image: redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20 - profiles: ["group1"] - hostname: stackexchangeredis-single - command: redis-server --bind 0.0.0.0 - ports: - - "127.0.0.1:6391:6379" mongo: image: mongo:5.0.31@sha256:54bcd8da3ea5eec561b68c605046c55c6b304387dc4c2bf5b3a5f5064fbb7495 @@ -429,8 +402,6 @@ services: - DiffEngine_Disabled=true - IncludeAllTestFrameworks - MONGO_HOST=mongo - - STACKEXCHANGE_REDIS_HOST=stackexchangeredis:6379,stackexchangeredis-replica:6379 - - STACKEXCHANGE_REDIS_SINGLE_HOST=stackexchangeredis-single:6379 - ELASTICSEARCH7_HOST=elasticsearch7:9200 - ELASTICSEARCH6_HOST=elasticsearch6:9200 - ELASTICSEARCH5_HOST=elasticsearch5:9200 @@ -645,9 +616,6 @@ services: image: andrewlock/wait-for-dependencies:latest@sha256:5d87561de8c019c3954298f707c1f6d7087f620a1ce36c3ec38cdfbc1ec4066e profiles: ["group1"] depends_on: - - stackexchangeredis - - stackexchangeredis-replica - - stackexchangeredis-single - sqlserver - postgres - mysql @@ -659,7 +627,7 @@ services: - test-agent environment: - TIMEOUT_LENGTH=120 - command: stackexchangeredis:6379 stackexchangeredis-replica:6379 stackexchangeredis-single:6379 sqlserver:1433 postgres:5432 mysql:3306 mysql57:3306 rabbitmq:5672 kafka-broker:9092 kafka-zookeeper:2181 couchbase:11210 test-agent:8126 test-agent:4317 test-agent:4318 + command: sqlserver:1433 postgres:5432 mysql:3306 mysql57:3306 rabbitmq:5672 kafka-broker:9092 kafka-zookeeper:2181 couchbase:11210 test-agent:8126 test-agent:4317 test-agent:4318 StartDependencies.Group2: image: andrewlock/wait-for-dependencies:latest@sha256:5d87561de8c019c3954298f707c1f6d7087f620a1ce36c3ec38cdfbc1ec4066e @@ -710,8 +678,6 @@ services: - DD_CLR_ENABLE_NGEN=${DD_CLR_ENABLE_NGEN:-1} - IncludeAllTestFrameworks - MONGO_HOST=mongo_arm64 - - STACKEXCHANGE_REDIS_HOST=stackexchangeredis_arm64:6379,stackexchangeredis_arm64-replica:6379 - - STACKEXCHANGE_REDIS_SINGLE_HOST=stackexchangeredis_arm64-single:6379 - ELASTICSEARCH7_HOST=elasticsearch7_arm64:9200 - ELASTICSEARCH6_HOST=elasticsearch7_arm64:9200 - ELASTICSEARCH5_HOST=elasticsearch7_arm64:9200 @@ -751,9 +717,6 @@ services: - RANDOM_SEED - TEST_AGENT_HOST=test-agent depends_on: - - stackexchangeredis_arm64 - - stackexchangeredis_arm64-replica - - stackexchangeredis_arm64-single - elasticsearch7_arm64 - sqledge_arm64 - mongo_arm64 @@ -767,9 +730,6 @@ services: StartDependencies.ARM64: image: andrewlock/wait-for-dependencies:latest@sha256:5d87561de8c019c3954298f707c1f6d7087f620a1ce36c3ec38cdfbc1ec4066e depends_on: - - stackexchangeredis_arm64 - - stackexchangeredis_arm64-replica - - stackexchangeredis_arm64-single - elasticsearch7_arm64 - sqledge_arm64 - mongo_arm64 @@ -781,7 +741,7 @@ services: - cosmosdb-emulator_arm64 environment: - TIMEOUT_LENGTH=120 - command: stackexchangeredis_arm64:6379 stackexchangeredis_arm64-replica:6379 stackexchangeredis_arm64-single:6379 elasticsearch7_arm64:9200 sqledge_arm64:1433 mongo_arm64:27017 postgres_arm64:5432 mysql_arm64:3306 rabbitmq_arm64:5672 localstack_arm64:4566 test-agent:8126 test-agent:4317 test-agent:4318 cosmosdb-emulator_arm64:8081 + command: elasticsearch7_arm64:9200 sqledge_arm64:1433 mongo_arm64:27017 postgres_arm64:5432 mysql_arm64:3306 rabbitmq_arm64:5672 localstack_arm64:4566 test-agent:8126 test-agent:4317 test-agent:4318 cosmosdb-emulator_arm64:8081 IntegrationTests.ARM64.Debugger: build: @@ -854,9 +814,6 @@ services: StartDependencies.OSXARM64: image: andrewlock/wait-for-dependencies:latest@sha256:5d87561de8c019c3954298f707c1f6d7087f620a1ce36c3ec38cdfbc1ec4066e depends_on: - - stackexchangeredis_osx_arm64 - - stackexchangeredis_osx_arm64-replica - - stackexchangeredis_osx_arm64-single - elasticsearch7_osx_arm64 - sqledge_osx_arm64 - mongo_osx_arm64 @@ -866,7 +823,7 @@ services: - localstack_osx_arm64 environment: - TIMEOUT_LENGTH=120 - command: stackexchangeredis_osx_arm64:6379 stackexchangeredis_osx_arm64-replica:6379 stackexchangeredis_osx_arm64-single:6379 elasticsearch7_osx_arm64:9200 sqledge_osx_arm64:1433 mongo_osx_arm64:27017 postgres_osx_arm64:5432 mysql_osx_arm64:3306 rabbitmq_osx_arm64:5672 localstack_osx_arm64:4566 + command: elasticsearch7_osx_arm64:9200 sqledge_osx_arm64:1433 mongo_osx_arm64:27017 postgres_osx_arm64:5432 mysql_osx_arm64:3306 rabbitmq_osx_arm64:5672 localstack_osx_arm64:4566 # OSX ARM64 dependencies @@ -928,24 +885,12 @@ services: stackexchangeredis_osx_arm64: image: redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20 - hostname: stackexchangeredis_osx_arm64 - command: redis-server --bind 0.0.0.0 - ports: - - "6392:6379" stackexchangeredis_osx_arm64-replica: image: redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20 - hostname: stackexchangeredis_osx_arm64-replica - command: redis-server --bind 0.0.0.0 --slaveof stackexchangeredis_osx_arm64 6379 - ports: - - "6390:6379" stackexchangeredis_osx_arm64-single: image: redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20 - hostname: stackexchangeredis_osx_arm64-single - command: redis-server --bind 0.0.0.0 - ports: - - "6391:6379" sqledge_osx_arm64: image: mcr.microsoft.com/azure-sql-edge:latest@sha256:902628a8be89e35dfb7895ca31d602974c7bafde4d583a0d0873844feb1c42cf diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/SmokeTests/StackExchangeRedisAssemblyConflictLegacyProjectSmokeTest.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/SmokeTests/StackExchangeRedisAssemblyConflictLegacyProjectSmokeTest.cs index b53b927535ce..9a37f4c79b3f 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/SmokeTests/StackExchangeRedisAssemblyConflictLegacyProjectSmokeTest.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/SmokeTests/StackExchangeRedisAssemblyConflictLegacyProjectSmokeTest.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Datadog.Trace.ClrProfiler.IntegrationTests.TestCollections; +using Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; using Xunit; using Xunit.Abstractions; @@ -14,9 +15,13 @@ namespace Datadog.Trace.ClrProfiler.IntegrationTests.SmokeTests [Collection(nameof(StackExchangeRedisTestCollection))] public class StackExchangeRedisAssemblyConflictLegacyProjectSmokeTest : SmokeTestBase { - public StackExchangeRedisAssemblyConflictLegacyProjectSmokeTest(ITestOutputHelper output) + public StackExchangeRedisAssemblyConflictLegacyProjectSmokeTest(ITestOutputHelper output, StackExchangeRedisFixture redisFixture) : base(output, "StackExchange.Redis.AssemblyConflict.LegacyProject", maxTestRunSeconds: 30) { + foreach (var variable in redisFixture.GetEnvironmentVariables()) + { + SetEnvironmentVariable(variable.Key, variable.Value); + } } [Fact] diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/SmokeTests/StackExchangeRedisAssemblyConflictSdkProjectSmokeTest.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/SmokeTests/StackExchangeRedisAssemblyConflictSdkProjectSmokeTest.cs index 4f3b534c998e..70cd6eeb5b8c 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/SmokeTests/StackExchangeRedisAssemblyConflictSdkProjectSmokeTest.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/SmokeTests/StackExchangeRedisAssemblyConflictSdkProjectSmokeTest.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Datadog.Trace.ClrProfiler.IntegrationTests.TestCollections; using Datadog.Trace.TestHelpers; +using Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; using Xunit; using Xunit.Abstractions; @@ -16,21 +17,20 @@ namespace Datadog.Trace.ClrProfiler.IntegrationTests.SmokeTests [Collection(nameof(StackExchangeRedisTestCollection))] public class StackExchangeRedisAssemblyConflictSdkProjectSmokeTest : SmokeTestBase { - public StackExchangeRedisAssemblyConflictSdkProjectSmokeTest(ITestOutputHelper output) + public StackExchangeRedisAssemblyConflictSdkProjectSmokeTest(ITestOutputHelper output, StackExchangeRedisFixture redisFixture) : base(output, "StackExchange.Redis.AssemblyConflict.SdkProject", maxTestRunSeconds: 30) { + foreach (var variable in redisFixture.GetEnvironmentVariables()) + { + SetEnvironmentVariable(variable.Key, variable.Value); + } } [SkippableFact] [Trait("Category", "Smoke")] public async Task NoExceptions() { - if (EnvironmentTools.IsWindows()) - { - Output.WriteLine("Ignored for Windows"); - return; - } - + Skip.If(EnvironmentTools.IsWindows(), "Ignored for Windows"); await CheckForSmoke(shouldDeserializeTraces: false); } } diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/SmokeTests/StackExchangeRedisStackOverflowExceptionSmokeTest.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/SmokeTests/StackExchangeRedisStackOverflowExceptionSmokeTest.cs index e2eae88258b6..f3052893c123 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/SmokeTests/StackExchangeRedisStackOverflowExceptionSmokeTest.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/SmokeTests/StackExchangeRedisStackOverflowExceptionSmokeTest.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Datadog.Trace.ClrProfiler.IntegrationTests.TestCollections; using Datadog.Trace.TestHelpers; +using Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; using Xunit; using Xunit.Abstractions; @@ -16,21 +17,20 @@ namespace Datadog.Trace.ClrProfiler.IntegrationTests.SmokeTests [Collection(nameof(StackExchangeRedisTestCollection))] public class StackExchangeRedisStackOverflowExceptionSmokeTest : SmokeTestBase { - public StackExchangeRedisStackOverflowExceptionSmokeTest(ITestOutputHelper output) + public StackExchangeRedisStackOverflowExceptionSmokeTest(ITestOutputHelper output, StackExchangeRedisFixture redisFixture) : base(output, "StackExchange.Redis.StackOverflowException", maxTestRunSeconds: 30) { + foreach (var variable in redisFixture.GetEnvironmentVariables()) + { + SetEnvironmentVariable(variable.Key, variable.Value); + } } [SkippableFact] [Trait("Category", "Smoke")] public async Task NoExceptions() { - if (EnvironmentTools.IsWindows()) - { - Output.WriteLine("Ignored for Windows"); - return; - } - + Skip.If(EnvironmentTools.IsWindows(), "Ignored for Windows"); await CheckForSmoke(shouldDeserializeTraces: false); } } diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/StackExchangeRedisTests.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/StackExchangeRedisTests.cs index b459d5d50501..8d946b6ac032 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/StackExchangeRedisTests.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/StackExchangeRedisTests.cs @@ -6,10 +6,12 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Datadog.Trace.ClrProfiler.IntegrationTests.TestCollections; using Datadog.Trace.Configuration; using Datadog.Trace.TestHelpers; +using Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; using FluentAssertions; using FluentAssertions.Execution; using VerifyXunit; @@ -24,10 +26,14 @@ namespace Datadog.Trace.ClrProfiler.IntegrationTests [UsesVerify] public class StackExchangeRedisTests : TracingIntegrationTest { - public StackExchangeRedisTests(ITestOutputHelper output) + private readonly StackExchangeRedisFixture _redisFixture; + + public StackExchangeRedisTests(ITestOutputHelper output, StackExchangeRedisFixture redisFixture) : base("StackExchange.Redis", output) { + _redisFixture = redisFixture; SetServiceVersion("1.0.0"); + ConfigureContainers(redisFixture); } private enum PackageVersion @@ -79,30 +85,12 @@ public async Task SubmitsTraces(string packageVersion, string metadataSchemaVers var spans = await agent.WaitForSpansAsync(expectedCount); ValidateIntegrationSpans(spans, metadataSchemaVersion, expectedServiceName: clientSpanServiceName, isExternalSpan); - var host = Environment.GetEnvironmentVariable("STACKEXCHANGE_REDIS_HOST") ?? "localhost:6389"; - var port = host.Substring(host.IndexOf(':') + 1); - host = host.Substring(0, host.IndexOf(':')); - var settings = VerifyHelper.GetSpanVerifierSettings(); settings.UseFileName($"{nameof(StackExchangeRedisTests)}.{calculatedVersion}" + $".Schema{metadataSchemaVersion.ToUpper()}"); settings.DisableRequireUniquePrefix(); settings.AddSimpleScrubber($" {TestPrefix}StackExchange.Redis.", " StackExchange.Redis."); - if (EnvironmentTools.IsOsx()) - { - settings.AddSimpleScrubber("out.host: localhost", "out.host: stackexchangeredis"); - settings.AddSimpleScrubber("peer.service: localhost", "peer.service: stackexchangeredis"); - settings.AddSimpleScrubber("out.host: 127.0.0.1", "out.host: stackexchangeredis-replica"); - settings.AddSimpleScrubber("peer.service: 127.0.0.1", "peer.service: stackexchangeredis-replica"); - settings.AddSimpleScrubber("out.port: 6390", "out.port: 6379"); - settings.AddSimpleScrubber("out.port: 6391", "out.port: 6379"); - settings.AddSimpleScrubber("out.port: 6392", "out.port: 6379"); - } - else - { - settings.AddSimpleScrubber($"out.host: {host}", "out.host: stackexchangeredis"); - settings.AddSimpleScrubber($"peer.service: {host}", "peer.service: stackexchangeredis"); - settings.AddSimpleScrubber($"out.port: {port}", "out.port: 6379"); - } + AddEndpointScrubbers(settings, _redisFixture.PrimaryHost, _redisFixture.PrimaryPort, "stackexchangeredis"); + AddEndpointScrubbers(settings, _redisFixture.ReplicaHost, _redisFixture.ReplicaPort, "stackexchangeredis-replica"); await VerifyHelper.VerifySpans( spans, @@ -118,6 +106,22 @@ await VerifyHelper.VerifySpans( await telemetry.AssertIntegrationEnabledAsync(IntegrationId.StackExchangeRedis); } + private static void AddEndpointScrubbers(VerifyTests.VerifySettings settings, string host, ushort port, string normalizedHost) + { + var escapedHost = Regex.Escape(host); + var endpointWithPeerService = new Regex($@"out\.host: {escapedHost},\r?\n([ \t]+)out\.port: {port},\r?\n\1peer\.service: [^,\r\n]+,"); + var endpoint = new Regex($@"out\.host: {escapedHost},\r?\n([ \t]+)out\.port: {port},"); + settings.AddScrubber( + builder => + { + var scrubbed = endpointWithPeerService.Replace( + builder.ToString(), + $"out.host: {normalizedHost},\n out.port: 6379,\n peer.service: {normalizedHost},"); + scrubbed = endpoint.Replace(scrubbed, $"out.host: {normalizedHost},\n out.port: 6379,"); + builder.Clear().Append(scrubbed); + }); + } + private static PackageVersion GetPackageVersion(string packageVersionString) { if (string.IsNullOrEmpty(packageVersionString)) diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/TestCollections/StackExchangeRedisTestCollection.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/TestCollections/StackExchangeRedisTestCollection.cs index 44d85212ab55..2af93c3c933c 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/TestCollections/StackExchangeRedisTestCollection.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/TestCollections/StackExchangeRedisTestCollection.cs @@ -3,12 +3,13 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. // +using Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; using Xunit; namespace Datadog.Trace.ClrProfiler.IntegrationTests.TestCollections { [CollectionDefinition(nameof(StackExchangeRedisTestCollection), DisableParallelization = true)] - public class StackExchangeRedisTestCollection + public class StackExchangeRedisTestCollection : ICollectionFixture { } } diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/StackExchangeRedisFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/StackExchangeRedisFixture.cs new file mode 100644 index 000000000000..bed5715ae2e9 --- /dev/null +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/StackExchangeRedisFixture.cs @@ -0,0 +1,115 @@ +// +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. +// + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using DotNet.Testcontainers.Builders; +using DotNet.Testcontainers.Containers; +using DotNet.Testcontainers.Networks; + +namespace Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; + +public class StackExchangeRedisFixture : ContainerFixture +{ + private const int RedisPort = 6379; + private const string PrimaryAlias = "stackexchangeredis"; + + private Resources? _resources; + + public string PrimaryHost => _resources!.PrimaryContainer.Hostname; + + public ushort PrimaryPort => _resources!.PrimaryContainer.GetMappedPublicPort(RedisPort); + + public string ReplicaHost => _resources!.ReplicaContainer.Hostname; + + public ushort ReplicaPort => _resources!.ReplicaContainer.GetMappedPublicPort(RedisPort); + + public override IEnumerable> GetEnvironmentVariables() + { + if (_resources is null) + { + yield break; + } + + yield return new("STACKEXCHANGE_REDIS_HOST", $"{PrimaryHost}:{PrimaryPort},{ReplicaHost}:{ReplicaPort}"); + yield return new("STACKEXCHANGE_REDIS_SINGLE_HOST", $"{_resources.SingleContainer.Hostname}:{_resources.SingleContainer.GetMappedPublicPort(RedisPort)}"); + } + + protected override async Task InitializeResources(Action registerResource) + { + // Keep synchronized with the image version in docker-compose.yml. + const string image = "redis:4-alpine"; + + var network = new NetworkBuilder().Build(); + var primaryContainer = new ContainerBuilder(image) + .WithCommand("redis-server", "--bind", "0.0.0.0") + .WithNetwork(network) + .WithNetworkAliases(PrimaryAlias) + .WithPortBinding(RedisPort, true) + .WithWaitStrategy(Wait.ForUnixContainer().UntilInternalTcpPortIsAvailable(RedisPort)) + .Build(); + var replicaContainer = new ContainerBuilder(image) + .WithCommand("redis-server", "--bind", "0.0.0.0", "--slaveof", PrimaryAlias, "6379") + .WithNetwork(network) + .WithPortBinding(RedisPort, true) + .WithWaitStrategy( + Wait.ForUnixContainer() + .UntilInternalTcpPortIsAvailable(RedisPort) + .UntilCommandIsCompleted("sh", "-c", "redis-cli info replication | grep -q 'master_link_status:up'")) + .Build(); + var singleContainer = new ContainerBuilder(image) + .WithCommand("redis-server", "--bind", "0.0.0.0") + .WithNetwork(network) + .WithPortBinding(RedisPort, true) + .WithWaitStrategy(Wait.ForUnixContainer().UntilInternalTcpPortIsAvailable(RedisPort)) + .Build(); + var resources = new Resources(primaryContainer, replicaContainer, singleContainer, network); + + try + { + await network.CreateAsync().ConfigureAwait(false); + await primaryContainer.StartAsync().ConfigureAwait(false); + await Task.WhenAll(replicaContainer.StartAsync(), singleContainer.StartAsync()).ConfigureAwait(false); + } + catch + { + await resources.DisposeAsync().ConfigureAwait(false); + throw; + } + + _resources = resources; + registerResource("resources", resources); + } + + private sealed class Resources : IAsyncDisposable + { + public Resources(IContainer primaryContainer, IContainer replicaContainer, IContainer singleContainer, INetwork network) + { + PrimaryContainer = primaryContainer; + ReplicaContainer = replicaContainer; + SingleContainer = singleContainer; + Network = network; + } + + public IContainer PrimaryContainer { get; } + + public IContainer ReplicaContainer { get; } + + public IContainer SingleContainer { get; } + + private INetwork Network { get; } + + public async ValueTask DisposeAsync() + { + await SingleContainer.DisposeAsync().ConfigureAwait(false); + await ReplicaContainer.DisposeAsync().ConfigureAwait(false); + await PrimaryContainer.DisposeAsync().ConfigureAwait(false); + await Network.DisposeAsync().ConfigureAwait(false); + } + } +} From dbaa18152e2bb7c97c4d959ce672ba043510a4a9 Mon Sep 17 00:00:00 2001 From: Steven Bouwkamp Date: Wed, 29 Jul 2026 10:33:17 -0400 Subject: [PATCH 05/28] Migrate SQL Server to Testcontainers --- docker-compose.yml | 34 +---------- tracer/build/_build/Build.Steps.cs | 3 + .../DotNetSettingsExtensions.cs | 1 - .../AdoNet/MicrosoftDataSqlClientTests.cs | 5 +- .../AdoNet/SystemDataSqlClientTests.cs | 14 ++++- .../Helpers/ContainersCollection.cs | 6 ++ .../ServiceBusMinimalMassTransitTest.cs | 10 +++- .../ServiceBusMinimalNServiceBusTest.cs | 9 ++- .../SmokeTests/ServiceBusMinimalRebusTest.cs | 9 ++- .../Containers/SqlServerFixture.cs | 58 +++++++++++++++++++ 10 files changed, 112 insertions(+), 37 deletions(-) create mode 100644 tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/SqlServerFixture.cs diff --git a/docker-compose.yml b/docker-compose.yml index 93b8c520ab2c..3348b55a6c64 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -54,14 +54,6 @@ services: servicestackredis_arm64: image: redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20 - sqledge_arm64: - image: mcr.microsoft.com/azure-sql-edge:latest@sha256:902628a8be89e35dfb7895ca31d602974c7bafde4d583a0d0873844feb1c42cf - ports: - - "1433" - environment: - - ACCEPT_EULA=Y - - SA_PASSWORD=Strong!Passw0rd - cosmosdb-emulator_arm64: image: mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:vnext-preview@sha256:54d7bc334494c50cea867c270880671a7db080626a9732832b34c0d69342f9b0 command: ["--protocol", "https"] @@ -187,12 +179,6 @@ services: sqlserver: image: mcr.microsoft.com/mssql/server:latest@sha256:2cd0aec4a3bfc3cf9205bed3f7922f4c6208f7c767dc62edcee308d0fd7d56d0 - profiles: ["group1"] - ports: - - "127.0.0.1:1433:1433" - environment: - - ACCEPT_EULA=Y - - SA_PASSWORD=Strong!Passw0rd sqledge: image: mcr.microsoft.com/azure-sql-edge:latest@sha256:902628a8be89e35dfb7895ca31d602974c7bafde4d583a0d0873844feb1c42cf @@ -405,7 +391,6 @@ services: - ELASTICSEARCH7_HOST=elasticsearch7:9200 - ELASTICSEARCH6_HOST=elasticsearch6:9200 - ELASTICSEARCH5_HOST=elasticsearch5:9200 - - SQLSERVER_CONNECTION_STRING=Server=sqlserver;User=sa;Password=Strong!Passw0rd;TrustServerCertificate=true - POSTGRES_HOST=postgres - MYSQL_HOST=mysql - MYSQL_PORT=3306 @@ -616,7 +601,6 @@ services: image: andrewlock/wait-for-dependencies:latest@sha256:5d87561de8c019c3954298f707c1f6d7087f620a1ce36c3ec38cdfbc1ec4066e profiles: ["group1"] depends_on: - - sqlserver - postgres - mysql - mysql57 @@ -627,7 +611,7 @@ services: - test-agent environment: - TIMEOUT_LENGTH=120 - command: sqlserver:1433 postgres:5432 mysql:3306 mysql57:3306 rabbitmq:5672 kafka-broker:9092 kafka-zookeeper:2181 couchbase:11210 test-agent:8126 test-agent:4317 test-agent:4318 + command: postgres:5432 mysql:3306 mysql57:3306 rabbitmq:5672 kafka-broker:9092 kafka-zookeeper:2181 couchbase:11210 test-agent:8126 test-agent:4317 test-agent:4318 StartDependencies.Group2: image: andrewlock/wait-for-dependencies:latest@sha256:5d87561de8c019c3954298f707c1f6d7087f620a1ce36c3ec38cdfbc1ec4066e @@ -681,7 +665,6 @@ services: - ELASTICSEARCH7_HOST=elasticsearch7_arm64:9200 - ELASTICSEARCH6_HOST=elasticsearch7_arm64:9200 - ELASTICSEARCH5_HOST=elasticsearch7_arm64:9200 - - SQLSERVER_CONNECTION_STRING=Server=sqledge_arm64;User=sa;Password=Strong!Passw0rd;TrustServerCertificate=true - POSTGRES_HOST=postgres_arm64 - MYSQL_HOST=mysql_arm64 - MYSQL_PORT=3306 @@ -718,7 +701,6 @@ services: - TEST_AGENT_HOST=test-agent depends_on: - elasticsearch7_arm64 - - sqledge_arm64 - mongo_arm64 - postgres_arm64 - mysql_arm64 @@ -731,7 +713,6 @@ services: image: andrewlock/wait-for-dependencies:latest@sha256:5d87561de8c019c3954298f707c1f6d7087f620a1ce36c3ec38cdfbc1ec4066e depends_on: - elasticsearch7_arm64 - - sqledge_arm64 - mongo_arm64 - postgres_arm64 - mysql_arm64 @@ -741,7 +722,7 @@ services: - cosmosdb-emulator_arm64 environment: - TIMEOUT_LENGTH=120 - command: elasticsearch7_arm64:9200 sqledge_arm64:1433 mongo_arm64:27017 postgres_arm64:5432 mysql_arm64:3306 rabbitmq_arm64:5672 localstack_arm64:4566 test-agent:8126 test-agent:4317 test-agent:4318 cosmosdb-emulator_arm64:8081 + command: elasticsearch7_arm64:9200 mongo_arm64:27017 postgres_arm64:5432 mysql_arm64:3306 rabbitmq_arm64:5672 localstack_arm64:4566 test-agent:8126 test-agent:4317 test-agent:4318 cosmosdb-emulator_arm64:8081 IntegrationTests.ARM64.Debugger: build: @@ -815,7 +796,6 @@ services: image: andrewlock/wait-for-dependencies:latest@sha256:5d87561de8c019c3954298f707c1f6d7087f620a1ce36c3ec38cdfbc1ec4066e depends_on: - elasticsearch7_osx_arm64 - - sqledge_osx_arm64 - mongo_osx_arm64 - postgres_osx_arm64 - mysql_osx_arm64 @@ -823,7 +803,7 @@ services: - localstack_osx_arm64 environment: - TIMEOUT_LENGTH=120 - command: elasticsearch7_osx_arm64:9200 sqledge_osx_arm64:1433 mongo_osx_arm64:27017 postgres_osx_arm64:5432 mysql_osx_arm64:3306 rabbitmq_osx_arm64:5672 localstack_osx_arm64:4566 + command: elasticsearch7_osx_arm64:9200 mongo_osx_arm64:27017 postgres_osx_arm64:5432 mysql_osx_arm64:3306 rabbitmq_osx_arm64:5672 localstack_osx_arm64:4566 # OSX ARM64 dependencies @@ -892,14 +872,6 @@ services: stackexchangeredis_osx_arm64-single: image: redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20 - sqledge_osx_arm64: - image: mcr.microsoft.com/azure-sql-edge:latest@sha256:902628a8be89e35dfb7895ca31d602974c7bafde4d583a0d0873844feb1c42cf - ports: - - "1433:1433" - environment: - - ACCEPT_EULA=Y - - SA_PASSWORD=Strong!Passw0rd - # keep syncronized image version with tracer\test\Datadog.Trace.TestHelpers.AutoInstrumentation\Containers\AerospikeFixture.cs aerospike: image: aerospike/aerospike-server:6.2.0.6@sha256:ec8959a17598dd1e2a254489c127f9cba2172a51272f0ba4cb26194028e28324 diff --git a/tracer/build/_build/Build.Steps.cs b/tracer/build/_build/Build.Steps.cs index 32deb4539513..0dfdb38ae643 100644 --- a/tracer/build/_build/Build.Steps.cs +++ b/tracer/build/_build/Build.Steps.cs @@ -1867,6 +1867,7 @@ void CompileSamplesThatDependOnDatadogTrace() // Don't apply a custom filter to these tests, they should all be able to be run .When(!string.IsNullOrWhiteSpace(AddAreaFilter(Filter)), c => c.SetFilter(AddAreaFilter(Filter))) .When(TestAllPackageVersions, o => o.SetProcessEnvironmentVariable("TestAllPackageVersions", "true")) + .When(IsWin, o => o.SetProcessEnvironmentVariable("SQLSERVER_CONNECTION_STRING", @"Server=(localdb)\MSSQLLocalDB;Integrated Security=true;Connection Timeout=60")) .When(CodeCoverageEnabled, ConfigureCodeCoverage) .CombineWith(parallelJobs, (s, project) => s .EnableTrxLogOutput(GetResultsDirectory(project)) @@ -1889,6 +1890,7 @@ void CompileSamplesThatDependOnDatadogTrace() .SetLogsDirectory(TestLogsDirectory) .When(!string.IsNullOrWhiteSpace(filter), c => c.SetFilter(filter)) .When(TestAllPackageVersions, o => o.SetProcessEnvironmentVariable("TestAllPackageVersions", "true")) + .When(IsWin, o => o.SetProcessEnvironmentVariable("SQLSERVER_CONNECTION_STRING", @"Server=(localdb)\MSSQLLocalDB;Integrated Security=true;Connection Timeout=60")) .When(CodeCoverageEnabled, ConfigureCodeCoverage) .CombineWith(ClrProfilerIntegrationTests, (s, project) => s .EnableTrxLogOutput(GetResultsDirectory(project)) @@ -2037,6 +2039,7 @@ private string AddAreaFilter(string filter) .SetIsDebugRun(isDebugRun) .SetProcessEnvironmentVariable("MonitoringHomeDirectory", MonitoringHomeDirectory) .SetLogsDirectory(TestLogsDirectory) + .SetProcessEnvironmentVariable("SQLSERVER_CONNECTION_STRING", @"Server=(localdb)\MSSQLLocalDB;Integrated Security=true;Connection Timeout=60") .When(CodeCoverageEnabled, ConfigureCodeCoverage) .CombineWith(ClrProfilerIntegrationTests, (s, project) => s .EnableTrxLogOutput(GetResultsDirectory(project)) diff --git a/tracer/build/_build/NukeExtensions/DotNetSettingsExtensions.cs b/tracer/build/_build/NukeExtensions/DotNetSettingsExtensions.cs index d685b370dc3c..053d409488bc 100644 --- a/tracer/build/_build/NukeExtensions/DotNetSettingsExtensions.cs +++ b/tracer/build/_build/NukeExtensions/DotNetSettingsExtensions.cs @@ -275,7 +275,6 @@ public static T SetLocalOsxEnvironmentVariables(this T toolSettings) .SetProcessEnvironmentVariable("ELASTICSEARCH7_HOST", "localhost:9200") .SetProcessEnvironmentVariable("ELASTICSEARCH6_HOST", "localhost:9200") .SetProcessEnvironmentVariable("ELASTICSEARCH5_HOST", "localhost:9200") - .SetProcessEnvironmentVariable("SQLSERVER_CONNECTION_STRING", "Server=localhost;User=sa;Password=Strong!Passw0rd") .SetProcessEnvironmentVariable("POSTGRES_HOST", "localhost") .SetProcessEnvironmentVariable("MYSQL_HOST", "localhost") .SetProcessEnvironmentVariable("MYSQL_PORT", "3306") diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/MicrosoftDataSqlClientTests.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/MicrosoftDataSqlClientTests.cs index 829f9cfd4e5a..5af05af0504c 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/MicrosoftDataSqlClientTests.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/MicrosoftDataSqlClientTests.cs @@ -10,6 +10,7 @@ using Datadog.Trace.ClrProfiler.IntegrationTests.Helpers; using Datadog.Trace.Configuration; using Datadog.Trace.TestHelpers; +using Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; using FluentAssertions; using Xunit; using Xunit.Abstractions; @@ -18,12 +19,14 @@ namespace Datadog.Trace.ClrProfiler.IntegrationTests.AdoNet { [Trait("RequiresDockerDependency", "true")] [Trait("DockerGroup", "1")] + [Collection(SqlServerCollection.Name)] public class MicrosoftDataSqlClientTests : TracingIntegrationTest { - public MicrosoftDataSqlClientTests(ITestOutputHelper output) + public MicrosoftDataSqlClientTests(ITestOutputHelper output, SqlServerFixture sqlServerFixture) : base("Microsoft.Data.SqlClient", output) { SetServiceVersion("1.0.0"); + ConfigureContainers(sqlServerFixture); } public override Result ValidateIntegrationSpan(MockSpan span, string metadataSchemaVersion) => span.IsSqlClient(metadataSchemaVersion); diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/SystemDataSqlClientTests.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/SystemDataSqlClientTests.cs index 2cddaa7f3a29..1282ef06b7a9 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/SystemDataSqlClientTests.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/SystemDataSqlClientTests.cs @@ -12,6 +12,7 @@ using Datadog.Trace.Configuration; using Datadog.Trace.ExtensionMethods; using Datadog.Trace.TestHelpers; +using Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; using FluentAssertions; using VerifyXunit; using Xunit; @@ -22,12 +23,17 @@ namespace Datadog.Trace.ClrProfiler.IntegrationTests.AdoNet [Trait("RequiresDockerDependency", "true")] [Trait("DockerGroup", "1")] [UsesVerify] + [Collection(SqlServerCollection.Name)] public class SystemDataSqlClientTests : TracingIntegrationTest { - public SystemDataSqlClientTests(ITestOutputHelper output) + private readonly SqlServerFixture _sqlServerFixture; + + public SystemDataSqlClientTests(ITestOutputHelper output, SqlServerFixture sqlServerFixture) : base("SqlServer", output) { + _sqlServerFixture = sqlServerFixture; SetServiceVersion("1.0.0"); + ConfigureContainers(sqlServerFixture); } public override Result ValidateIntegrationSpan(MockSpan span, string metadataSchemaVersion) => span.IsSqlClient(metadataSchemaVersion); @@ -97,6 +103,12 @@ public async Task SubmitsTraces( builder.Append(filtered); }); + if (_sqlServerFixture.HostAndPort is { } hostAndPort) + { + settings.AddSimpleScrubber($"out.host: {hostAndPort}", "out.host: sqlserver"); + settings.AddSimpleScrubber($"peer.service: {hostAndPort}", "peer.service: sqlserver"); + } + settings.AddSimpleScrubber("out.host: localhost", "out.host: sqlserver"); settings.AddSimpleScrubber("out.host: (localdb)\\MSSQLLocalDB", "out.host: sqlserver"); settings.AddSimpleScrubber("out.host: sqledge_arm64", "out.host: sqlserver"); diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs index d801bfb434bc..c5da3fc60f9c 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs @@ -22,6 +22,12 @@ public class ServiceStackRedisCollection : ICollectionFixture + { + public const string Name = "SqlServer"; + } } #pragma warning restore SA1649 // File name should match first type name diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/SmokeTests/ServiceBusMinimalMassTransitTest.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/SmokeTests/ServiceBusMinimalMassTransitTest.cs index f387d92dd64c..0b08d02f8c97 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/SmokeTests/ServiceBusMinimalMassTransitTest.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/SmokeTests/ServiceBusMinimalMassTransitTest.cs @@ -4,6 +4,8 @@ // using System.Threading.Tasks; +using Datadog.Trace.ClrProfiler.IntegrationTests.Helpers; +using Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; using Xunit; using Xunit.Abstractions; @@ -11,11 +13,17 @@ namespace Datadog.Trace.ClrProfiler.IntegrationTests.SmokeTests { [Trait("RequiresDockerDependency", "true")] [Trait("DockerGroup", "2")] + [Collection(SqlServerCollection.Name)] public class ServiceBusMinimalMassTransitTest : SmokeTestBase { - public ServiceBusMinimalMassTransitTest(ITestOutputHelper output) + public ServiceBusMinimalMassTransitTest(ITestOutputHelper output, SqlServerFixture sqlServerFixture) : base(output, "ServiceBus.Minimal.MassTransit", maxTestRunSeconds: 60) { + foreach (var variable in sqlServerFixture.GetEnvironmentVariables()) + { + SetEnvironmentVariable(variable.Key, variable.Value); + } + AssumeSuccessOnTimeout = true; } diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/SmokeTests/ServiceBusMinimalNServiceBusTest.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/SmokeTests/ServiceBusMinimalNServiceBusTest.cs index b408411db20a..b3d2f8bf5aab 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/SmokeTests/ServiceBusMinimalNServiceBusTest.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/SmokeTests/ServiceBusMinimalNServiceBusTest.cs @@ -4,6 +4,8 @@ // using System.Threading.Tasks; +using Datadog.Trace.ClrProfiler.IntegrationTests.Helpers; +using Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; using Xunit; using Xunit.Abstractions; @@ -11,11 +13,16 @@ namespace Datadog.Trace.ClrProfiler.IntegrationTests.SmokeTests { [Trait("RequiresDockerDependency", "true")] [Trait("DockerGroup", "2")] + [Collection(SqlServerCollection.Name)] public class ServiceBusMinimalNServiceBusTest : SmokeTestBase { - public ServiceBusMinimalNServiceBusTest(ITestOutputHelper output) + public ServiceBusMinimalNServiceBusTest(ITestOutputHelper output, SqlServerFixture sqlServerFixture) : base(output, "ServiceBus.Minimal.NServiceBus", maxTestRunSeconds: 90) { + foreach (var variable in sqlServerFixture.GetEnvironmentVariables()) + { + SetEnvironmentVariable(variable.Key, variable.Value); + } } [SkippableFact] diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/SmokeTests/ServiceBusMinimalRebusTest.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/SmokeTests/ServiceBusMinimalRebusTest.cs index 78162ec857b1..4a583134ff37 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/SmokeTests/ServiceBusMinimalRebusTest.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/SmokeTests/ServiceBusMinimalRebusTest.cs @@ -4,6 +4,8 @@ // using System.Threading.Tasks; +using Datadog.Trace.ClrProfiler.IntegrationTests.Helpers; +using Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; using Xunit; using Xunit.Abstractions; @@ -11,11 +13,16 @@ namespace Datadog.Trace.ClrProfiler.IntegrationTests.SmokeTests { [Trait("RequiresDockerDependency", "true")] [Trait("DockerGroup", "2")] + [Collection(SqlServerCollection.Name)] public class ServiceBusMinimalRebusTest : SmokeTestBase { - public ServiceBusMinimalRebusTest(ITestOutputHelper output) + public ServiceBusMinimalRebusTest(ITestOutputHelper output, SqlServerFixture sqlServerFixture) : base(output, "ServiceBus.Minimal.Rebus", maxTestRunSeconds: 90) { + foreach (var variable in sqlServerFixture.GetEnvironmentVariables()) + { + SetEnvironmentVariable(variable.Key, variable.Value); + } } [SkippableFact] diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/SqlServerFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/SqlServerFixture.cs new file mode 100644 index 000000000000..9e4910129fdc --- /dev/null +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/SqlServerFixture.cs @@ -0,0 +1,58 @@ +// +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. +// + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using DotNet.Testcontainers.Builders; +using DotNet.Testcontainers.Containers; + +namespace Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; + +public class SqlServerFixture : ContainerFixture +{ + private const int SqlServerPort = 1433; + private const string Password = "Strong!Passw0rd"; + private const string SqlServerImage = "mcr.microsoft.com/mssql/server:latest@sha256:2cd0aec4a3bfc3cf9205bed3f7922f4c6208f7c767dc62edcee308d0fd7d56d0"; + private const string AzureSqlEdgeImage = "mcr.microsoft.com/azure-sql-edge:latest@sha256:902628a8be89e35dfb7895ca31d602974c7bafde4d583a0d0873844feb1c42cf"; + + private IContainer? _container; + + public string? HostAndPort => _container is null ? null : $"{_container.Hostname},{_container.GetMappedPublicPort(SqlServerPort)}"; + + public override IEnumerable> GetEnvironmentVariables() + { + if (HostAndPort is { } hostAndPort) + { + yield return new("SQLSERVER_CONNECTION_STRING", $"Server={hostAndPort};User=sa;Password={Password};TrustServerCertificate=True"); + } + } + + protected override async Task InitializeResources(Action registerResource) + { + // Windows CI provides LocalDB, and callers can explicitly select another existing SQL Server. + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("SQLSERVER_CONNECTION_STRING"))) + { + return; + } + + // mssql/server has no native arm64 image, so use Azure SQL Edge on arm64. + var image = RuntimeInformation.ProcessArchitecture == Architecture.Arm64 ? AzureSqlEdgeImage : SqlServerImage; + var container = new ContainerBuilder(image) + .WithPortBinding(SqlServerPort, true) + .WithEnvironment("ACCEPT_EULA", "Y") + .WithEnvironment("MSSQL_SA_PASSWORD", Password) + .WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged("SQL Server is now ready for client connections")) + .Build(); + + await container.StartAsync().ConfigureAwait(false); + + _container = container; + registerResource("container", container); + } +} From 7bbeeaedbdd3d58e9c14248907d2b8b6ec95a9ae Mon Sep 17 00:00:00 2001 From: Steven Bouwkamp Date: Wed, 29 Jul 2026 10:35:03 -0400 Subject: [PATCH 06/28] Migrate PostgreSQL to Testcontainers --- docker-compose.yml | 31 ++---------- .../DotNetSettingsExtensions.cs | 1 - .../AdoNet/DapperTests.cs | 6 ++- .../AdoNet/NpgsqlCommandTests.cs | 9 +++- .../Helpers/ContainersCollection.cs | 6 +++ .../Containers/PostgresFixture.cs | 49 +++++++++++++++++++ 6 files changed, 71 insertions(+), 31 deletions(-) create mode 100644 tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/PostgresFixture.cs diff --git a/docker-compose.yml b/docker-compose.yml index 3348b55a6c64..eec31941099a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -37,12 +37,6 @@ services: postgres_arm64: image: postgres:10.5-alpine@sha256:295a08ddd9efa1612c46033f0b96c3976f80f49c7ce29e05916b0af557806117 - environment: - - POSTGRES_PASSWORD=postgres - - POSTGRES_USER=postgres - - POSTGRES_DB=postgres - ports: - - "5432" rabbitmq_arm64: image: rabbitmq:3-management@sha256:e582c0bc7766f3342496d8485efb5a1df782b5ce3886ad017e2eaae442311f69 @@ -147,13 +141,6 @@ services: postgres: image: postgres:10.5-alpine@sha256:295a08ddd9efa1612c46033f0b96c3976f80f49c7ce29e05916b0af557806117 - profiles: ["group1"] - environment: - - POSTGRES_PASSWORD=postgres - - POSTGRES_USER=postgres - - POSTGRES_DB=postgres - ports: - - "127.0.0.1:5432:5432" mysql: image: mysql/mysql-server:8.0@sha256:d6c8301b7834c5b9c2b733b10b7e630f441af7bc917c74dba379f24eeeb6a313 @@ -391,7 +378,6 @@ services: - ELASTICSEARCH7_HOST=elasticsearch7:9200 - ELASTICSEARCH6_HOST=elasticsearch6:9200 - ELASTICSEARCH5_HOST=elasticsearch5:9200 - - POSTGRES_HOST=postgres - MYSQL_HOST=mysql - MYSQL_PORT=3306 - MYSQL57_HOST=mysql57 @@ -601,7 +587,6 @@ services: image: andrewlock/wait-for-dependencies:latest@sha256:5d87561de8c019c3954298f707c1f6d7087f620a1ce36c3ec38cdfbc1ec4066e profiles: ["group1"] depends_on: - - postgres - mysql - mysql57 - rabbitmq @@ -611,7 +596,7 @@ services: - test-agent environment: - TIMEOUT_LENGTH=120 - command: postgres:5432 mysql:3306 mysql57:3306 rabbitmq:5672 kafka-broker:9092 kafka-zookeeper:2181 couchbase:11210 test-agent:8126 test-agent:4317 test-agent:4318 + command: mysql:3306 mysql57:3306 rabbitmq:5672 kafka-broker:9092 kafka-zookeeper:2181 couchbase:11210 test-agent:8126 test-agent:4317 test-agent:4318 StartDependencies.Group2: image: andrewlock/wait-for-dependencies:latest@sha256:5d87561de8c019c3954298f707c1f6d7087f620a1ce36c3ec38cdfbc1ec4066e @@ -665,7 +650,6 @@ services: - ELASTICSEARCH7_HOST=elasticsearch7_arm64:9200 - ELASTICSEARCH6_HOST=elasticsearch7_arm64:9200 - ELASTICSEARCH5_HOST=elasticsearch7_arm64:9200 - - POSTGRES_HOST=postgres_arm64 - MYSQL_HOST=mysql_arm64 - MYSQL_PORT=3306 - RABBITMQ_HOST=rabbitmq_arm64 @@ -702,7 +686,6 @@ services: depends_on: - elasticsearch7_arm64 - mongo_arm64 - - postgres_arm64 - mysql_arm64 - rabbitmq_arm64 - localstack_arm64 @@ -714,7 +697,6 @@ services: depends_on: - elasticsearch7_arm64 - mongo_arm64 - - postgres_arm64 - mysql_arm64 - rabbitmq_arm64 - localstack_arm64 @@ -722,7 +704,7 @@ services: - cosmosdb-emulator_arm64 environment: - TIMEOUT_LENGTH=120 - command: elasticsearch7_arm64:9200 mongo_arm64:27017 postgres_arm64:5432 mysql_arm64:3306 rabbitmq_arm64:5672 localstack_arm64:4566 test-agent:8126 test-agent:4317 test-agent:4318 cosmosdb-emulator_arm64:8081 + command: elasticsearch7_arm64:9200 mongo_arm64:27017 mysql_arm64:3306 rabbitmq_arm64:5672 localstack_arm64:4566 test-agent:8126 test-agent:4317 test-agent:4318 cosmosdb-emulator_arm64:8081 IntegrationTests.ARM64.Debugger: build: @@ -797,13 +779,12 @@ services: depends_on: - elasticsearch7_osx_arm64 - mongo_osx_arm64 - - postgres_osx_arm64 - mysql_osx_arm64 - rabbitmq_osx_arm64 - localstack_osx_arm64 environment: - TIMEOUT_LENGTH=120 - command: elasticsearch7_osx_arm64:9200 mongo_osx_arm64:27017 postgres_osx_arm64:5432 mysql_osx_arm64:3306 rabbitmq_osx_arm64:5672 localstack_osx_arm64:4566 + command: elasticsearch7_osx_arm64:9200 mongo_osx_arm64:27017 mysql_osx_arm64:3306 rabbitmq_osx_arm64:5672 localstack_osx_arm64:4566 # OSX ARM64 dependencies @@ -846,12 +827,6 @@ services: postgres_osx_arm64: image: postgres:10.5-alpine@sha256:295a08ddd9efa1612c46033f0b96c3976f80f49c7ce29e05916b0af557806117 - environment: - - POSTGRES_PASSWORD=postgres - - POSTGRES_USER=postgres - - POSTGRES_DB=postgres - ports: - - "5432:5432" rabbitmq_osx_arm64: image: rabbitmq:3-management@sha256:e582c0bc7766f3342496d8485efb5a1df782b5ce3886ad017e2eaae442311f69 diff --git a/tracer/build/_build/NukeExtensions/DotNetSettingsExtensions.cs b/tracer/build/_build/NukeExtensions/DotNetSettingsExtensions.cs index 053d409488bc..aeafe0f5664c 100644 --- a/tracer/build/_build/NukeExtensions/DotNetSettingsExtensions.cs +++ b/tracer/build/_build/NukeExtensions/DotNetSettingsExtensions.cs @@ -275,7 +275,6 @@ public static T SetLocalOsxEnvironmentVariables(this T toolSettings) .SetProcessEnvironmentVariable("ELASTICSEARCH7_HOST", "localhost:9200") .SetProcessEnvironmentVariable("ELASTICSEARCH6_HOST", "localhost:9200") .SetProcessEnvironmentVariable("ELASTICSEARCH5_HOST", "localhost:9200") - .SetProcessEnvironmentVariable("POSTGRES_HOST", "localhost") .SetProcessEnvironmentVariable("MYSQL_HOST", "localhost") .SetProcessEnvironmentVariable("MYSQL_PORT", "3306") .SetProcessEnvironmentVariable("RABBITMQ_HOST", "localhost") diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/DapperTests.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/DapperTests.cs index 630e7175ee4c..c9e2d9b618e4 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/DapperTests.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/DapperTests.cs @@ -4,7 +4,9 @@ // using System.Threading.Tasks; +using Datadog.Trace.ClrProfiler.IntegrationTests.Helpers; using Datadog.Trace.TestHelpers; +using Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; using Xunit; using Xunit.Abstractions; @@ -12,12 +14,14 @@ namespace Datadog.Trace.ClrProfiler.IntegrationTests.AdoNet { [Trait("RequiresDockerDependency", "true")] [Trait("DockerGroup", "1")] + [Collection(PostgresCollection.Name)] public class DapperTests : TracingIntegrationTest { - public DapperTests(ITestOutputHelper output) + public DapperTests(ITestOutputHelper output, PostgresFixture postgresFixture) : base("Dapper", output) { SetServiceVersion("1.0.0"); + ConfigureContainers(postgresFixture); } // Assert Npgsql because the Dapper application uses Postgres for the actual client diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/NpgsqlCommandTests.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/NpgsqlCommandTests.cs index 41685a8ccb67..52e2eaca9bc3 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/NpgsqlCommandTests.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/NpgsqlCommandTests.cs @@ -11,6 +11,7 @@ using Datadog.Trace.ClrProfiler.IntegrationTests.Helpers; using Datadog.Trace.Configuration; using Datadog.Trace.TestHelpers; +using Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; using FluentAssertions; using VerifyXunit; using Xunit; @@ -21,12 +22,17 @@ namespace Datadog.Trace.ClrProfiler.IntegrationTests.AdoNet [Trait("RequiresDockerDependency", "true")] [Trait("DockerGroup", "1")] [UsesVerify] + [Collection(PostgresCollection.Name)] public class NpgsqlCommandTests : TracingIntegrationTest { - public NpgsqlCommandTests(ITestOutputHelper output) + private readonly PostgresFixture _postgresFixture; + + public NpgsqlCommandTests(ITestOutputHelper output, PostgresFixture postgresFixture) : base("Npgsql", output) { + _postgresFixture = postgresFixture; SetServiceVersion("1.0.0"); + ConfigureContainers(postgresFixture); } public override Result ValidateIntegrationSpan(MockSpan span, string metadataSchemaVersion) => span.IsNpgsql(metadataSchemaVersion); @@ -77,6 +83,7 @@ public async Task SubmitsTraces( var settings = VerifyHelper.GetSpanVerifierSettings(); settings.AddRegexScrubber(new Regex("Npgsql-Test-[a-zA-Z0-9]{32}"), "Npgsql-Test-GUID"); + settings.AddSimpleScrubber($"out.host: {_postgresFixture.Host}", "out.host: postgres"); settings.AddSimpleScrubber("out.host: localhost", "out.host: postgres"); settings.AddSimpleScrubber("out.host: postgres_arm64", "out.host: postgres"); diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs index c5da3fc60f9c..bc9e4a4a5f9b 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs @@ -28,6 +28,12 @@ public class SqlServerCollection : ICollectionFixture { public const string Name = "SqlServer"; } + + [CollectionDefinition(Name, DisableParallelization = true)] + public class PostgresCollection : ICollectionFixture + { + public const string Name = "Postgres"; + } } #pragma warning restore SA1649 // File name should match first type name diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/PostgresFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/PostgresFixture.cs new file mode 100644 index 000000000000..6692bef9fc1d --- /dev/null +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/PostgresFixture.cs @@ -0,0 +1,49 @@ +// +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. +// + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using DotNet.Testcontainers.Builders; +using DotNet.Testcontainers.Containers; + +namespace Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; + +public class PostgresFixture : ContainerFixture +{ + private const int PostgresPort = 5432; + private const string Username = "postgres"; + private const string Password = "postgres"; + private const string Database = "postgres"; + private const string Image = "postgres:10.5-alpine@sha256:295a08ddd9efa1612c46033f0b96c3976f80f49c7ce29e05916b0af557806117"; + + public string Host => Container.Hostname; + + public ushort Port => Container.GetMappedPublicPort(PostgresPort); + + private IContainer Container => GetResource("container"); + + public override IEnumerable> GetEnvironmentVariables() + { + yield return new("POSTGRES_CONNECTION_STRING", $"Host={Host};Port={Port};Username={Username};Password={Password};Database={Database}"); + } + + protected override async Task InitializeResources(Action registerResource) + { + var container = new ContainerBuilder(Image) + .WithPortBinding(PostgresPort, true) + .WithEnvironment("POSTGRES_USER", Username) + .WithEnvironment("POSTGRES_PASSWORD", Password) + .WithEnvironment("POSTGRES_DB", Database) + .WithWaitStrategy(Wait.ForUnixContainer().UntilCommandIsCompleted("pg_isready", "-U", Username)) + .Build(); + + await container.StartAsync().ConfigureAwait(false); + + registerResource("container", container); + } +} From 3a983f6c7018678f0e66f36fb29a25b32c184b0d Mon Sep 17 00:00:00 2001 From: Steven Bouwkamp Date: Wed, 29 Jul 2026 10:36:34 -0400 Subject: [PATCH 07/28] Migrate MySQL 8 to Testcontainers --- docker-compose.yml | 36 ++----------- .../DotNetSettingsExtensions.cs | 2 - .../AdoNet/MySqlCommandTests.cs | 9 +++- .../AdoNet/MySqlConnectorTests.cs | 5 +- .../Helpers/ContainersCollection.cs | 6 +++ .../Containers/MySql8Fixture.cs | 51 +++++++++++++++++++ 6 files changed, 72 insertions(+), 37 deletions(-) create mode 100644 tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql8Fixture.cs diff --git a/docker-compose.yml b/docker-compose.yml index eec31941099a..1f6fc2bc2648 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,13 +27,6 @@ services: mysql_arm64: image: mysql/mysql-server:8.0@sha256:d6c8301b7834c5b9c2b733b10b7e630f441af7bc917c74dba379f24eeeb6a313 - environment: - - MYSQL_DATABASE=world - - MYSQL_ROOT_PASSWORD=mysqldb - - MYSQL_USER=mysqldb - - MYSQL_PASSWORD=mysqldb - ports: - - "3306" postgres_arm64: image: postgres:10.5-alpine@sha256:295a08ddd9efa1612c46033f0b96c3976f80f49c7ce29e05916b0af557806117 @@ -144,14 +137,6 @@ services: mysql: image: mysql/mysql-server:8.0@sha256:d6c8301b7834c5b9c2b733b10b7e630f441af7bc917c74dba379f24eeeb6a313 - profiles: ["group1"] - environment: - - MYSQL_DATABASE=world - - MYSQL_ROOT_PASSWORD=mysqldb - - MYSQL_USER=mysqldb - - MYSQL_PASSWORD=mysqldb - ports: - - "127.0.0.1:3307:3306" mysql57: image: mysql/mysql-server:5.7@sha256:1178cdd375f758968cd834ac4057bae41307e64b7c69a9e145896e7b11f48064 @@ -378,8 +363,6 @@ services: - ELASTICSEARCH7_HOST=elasticsearch7:9200 - ELASTICSEARCH6_HOST=elasticsearch6:9200 - ELASTICSEARCH5_HOST=elasticsearch5:9200 - - MYSQL_HOST=mysql - - MYSQL_PORT=3306 - MYSQL57_HOST=mysql57 - MYSQL57_PORT=3306 - RABBITMQ_HOST=rabbitmq @@ -587,7 +570,6 @@ services: image: andrewlock/wait-for-dependencies:latest@sha256:5d87561de8c019c3954298f707c1f6d7087f620a1ce36c3ec38cdfbc1ec4066e profiles: ["group1"] depends_on: - - mysql - mysql57 - rabbitmq - kafka-broker @@ -596,7 +578,7 @@ services: - test-agent environment: - TIMEOUT_LENGTH=120 - command: mysql:3306 mysql57:3306 rabbitmq:5672 kafka-broker:9092 kafka-zookeeper:2181 couchbase:11210 test-agent:8126 test-agent:4317 test-agent:4318 + command: mysql57:3306 rabbitmq:5672 kafka-broker:9092 kafka-zookeeper:2181 couchbase:11210 test-agent:8126 test-agent:4317 test-agent:4318 StartDependencies.Group2: image: andrewlock/wait-for-dependencies:latest@sha256:5d87561de8c019c3954298f707c1f6d7087f620a1ce36c3ec38cdfbc1ec4066e @@ -650,8 +632,6 @@ services: - ELASTICSEARCH7_HOST=elasticsearch7_arm64:9200 - ELASTICSEARCH6_HOST=elasticsearch7_arm64:9200 - ELASTICSEARCH5_HOST=elasticsearch7_arm64:9200 - - MYSQL_HOST=mysql_arm64 - - MYSQL_PORT=3306 - RABBITMQ_HOST=rabbitmq_arm64 - AWS_SDK_HOST=localstack_arm64:4566 - COSMOSDB_ENDPOINT=https://cosmosdb-emulator_arm64:8081 @@ -686,7 +666,6 @@ services: depends_on: - elasticsearch7_arm64 - mongo_arm64 - - mysql_arm64 - rabbitmq_arm64 - localstack_arm64 - test-agent @@ -697,14 +676,13 @@ services: depends_on: - elasticsearch7_arm64 - mongo_arm64 - - mysql_arm64 - rabbitmq_arm64 - localstack_arm64 - test-agent - cosmosdb-emulator_arm64 environment: - TIMEOUT_LENGTH=120 - command: elasticsearch7_arm64:9200 mongo_arm64:27017 mysql_arm64:3306 rabbitmq_arm64:5672 localstack_arm64:4566 test-agent:8126 test-agent:4317 test-agent:4318 cosmosdb-emulator_arm64:8081 + command: elasticsearch7_arm64:9200 mongo_arm64:27017 rabbitmq_arm64:5672 localstack_arm64:4566 test-agent:8126 test-agent:4317 test-agent:4318 cosmosdb-emulator_arm64:8081 IntegrationTests.ARM64.Debugger: build: @@ -779,12 +757,11 @@ services: depends_on: - elasticsearch7_osx_arm64 - mongo_osx_arm64 - - mysql_osx_arm64 - rabbitmq_osx_arm64 - localstack_osx_arm64 environment: - TIMEOUT_LENGTH=120 - command: elasticsearch7_osx_arm64:9200 mongo_osx_arm64:27017 mysql_osx_arm64:3306 rabbitmq_osx_arm64:5672 localstack_osx_arm64:4566 + command: elasticsearch7_osx_arm64:9200 mongo_osx_arm64:27017 rabbitmq_osx_arm64:5672 localstack_osx_arm64:4566 # OSX ARM64 dependencies @@ -817,13 +794,6 @@ services: mysql_osx_arm64: image: mysql/mysql-server:8.0@sha256:d6c8301b7834c5b9c2b733b10b7e630f441af7bc917c74dba379f24eeeb6a313 - environment: - - MYSQL_DATABASE=world - - MYSQL_ROOT_PASSWORD=mysqldb - - MYSQL_USER=mysqldb - - MYSQL_PASSWORD=mysqldb - ports: - - "3306:3306" postgres_osx_arm64: image: postgres:10.5-alpine@sha256:295a08ddd9efa1612c46033f0b96c3976f80f49c7ce29e05916b0af557806117 diff --git a/tracer/build/_build/NukeExtensions/DotNetSettingsExtensions.cs b/tracer/build/_build/NukeExtensions/DotNetSettingsExtensions.cs index aeafe0f5664c..55257c6faa6e 100644 --- a/tracer/build/_build/NukeExtensions/DotNetSettingsExtensions.cs +++ b/tracer/build/_build/NukeExtensions/DotNetSettingsExtensions.cs @@ -275,8 +275,6 @@ public static T SetLocalOsxEnvironmentVariables(this T toolSettings) .SetProcessEnvironmentVariable("ELASTICSEARCH7_HOST", "localhost:9200") .SetProcessEnvironmentVariable("ELASTICSEARCH6_HOST", "localhost:9200") .SetProcessEnvironmentVariable("ELASTICSEARCH5_HOST", "localhost:9200") - .SetProcessEnvironmentVariable("MYSQL_HOST", "localhost") - .SetProcessEnvironmentVariable("MYSQL_PORT", "3306") .SetProcessEnvironmentVariable("RABBITMQ_HOST", "localhost") .SetProcessEnvironmentVariable("AWS_SDK_HOST", "localhost:4566"); } diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/MySqlCommandTests.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/MySqlCommandTests.cs index d41bbef6350c..69a0fecf3557 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/MySqlCommandTests.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/MySqlCommandTests.cs @@ -12,6 +12,7 @@ using Datadog.Trace.ClrProfiler.IntegrationTests.Helpers; using Datadog.Trace.Configuration; using Datadog.Trace.TestHelpers; +using Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; using FluentAssertions; using VerifyXunit; using Xunit; @@ -22,12 +23,17 @@ namespace Datadog.Trace.ClrProfiler.IntegrationTests.AdoNet [Trait("RequiresDockerDependency", "true")] [Trait("DockerGroup", "1")] [UsesVerify] + [Collection(MySqlCollection.Name)] public class MySqlCommandTests : TracingIntegrationTest { - public MySqlCommandTests(ITestOutputHelper output) + private readonly MySql8Fixture _mySqlFixture; + + public MySqlCommandTests(ITestOutputHelper output, MySql8Fixture mySqlFixture) : base("MySql", output) { + _mySqlFixture = mySqlFixture; SetServiceVersion("1.0.0"); + ConfigureContainers(mySqlFixture); } public override Result ValidateIntegrationSpan(MockSpan span, string metadataSchemaVersion) => span.IsMySql(metadataSchemaVersion); @@ -121,6 +127,7 @@ private async Task SubmitsTraces(string packageVersion, string metadataSchemaVer var settings = VerifyHelper.GetSpanVerifierSettings(); settings.AddRegexScrubber(new Regex("MySql-Test-[a-zA-Z0-9]{32}"), "MySql-Test-GUID"); + settings.AddSimpleScrubber($"out.host: {_mySqlFixture.Host}", "out.host: mysql"); settings.AddSimpleScrubber("out.host: localhost", "out.host: mysql"); settings.AddSimpleScrubber("out.host: mysql57", "out.host: mysql"); settings.AddSimpleScrubber("out.host: mysql_arm64", "out.host: mysql"); diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/MySqlConnectorTests.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/MySqlConnectorTests.cs index d6a65e4419a4..88c42c07328c 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/MySqlConnectorTests.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/MySqlConnectorTests.cs @@ -10,6 +10,7 @@ using Datadog.Trace.ClrProfiler.IntegrationTests.Helpers; using Datadog.Trace.Configuration; using Datadog.Trace.TestHelpers; +using Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; using FluentAssertions; using Xunit; using Xunit.Abstractions; @@ -18,12 +19,14 @@ namespace Datadog.Trace.ClrProfiler.IntegrationTests.AdoNet { [Trait("RequiresDockerDependency", "true")] [Trait("DockerGroup", "1")] + [Collection(MySqlCollection.Name)] public class MySqlConnectorTests : TracingIntegrationTest { - public MySqlConnectorTests(ITestOutputHelper output) + public MySqlConnectorTests(ITestOutputHelper output, MySql8Fixture mySqlFixture) : base("MySqlConnector", output) { SetServiceVersion("1.0.0"); + ConfigureContainers(mySqlFixture); } public override Result ValidateIntegrationSpan(MockSpan span, string metadataSchemaVersion) => span.IsMySql(metadataSchemaVersion); diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs index bc9e4a4a5f9b..fc9432048712 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs @@ -34,6 +34,12 @@ public class PostgresCollection : ICollectionFixture { public const string Name = "Postgres"; } + + [CollectionDefinition(Name, DisableParallelization = true)] + public class MySqlCollection : ICollectionFixture + { + public const string Name = "MySql"; + } } #pragma warning restore SA1649 // File name should match first type name diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql8Fixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql8Fixture.cs new file mode 100644 index 000000000000..961e2ce05d30 --- /dev/null +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql8Fixture.cs @@ -0,0 +1,51 @@ +// +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. +// + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using DotNet.Testcontainers.Builders; +using DotNet.Testcontainers.Containers; + +namespace Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; + +public class MySql8Fixture : ContainerFixture +{ + private const int MySqlPort = 3306; + private const string Username = "mysqldb"; + private const string Password = "mysqldb"; + private const string Database = "world"; + private const string Image = "mysql/mysql-server:8.0@sha256:d6c8301b7834c5b9c2b733b10b7e630f441af7bc917c74dba379f24eeeb6a313"; + + public string Host => Container.Hostname; + + public ushort Port => Container.GetMappedPublicPort(MySqlPort); + + private IContainer Container => GetResource("container"); + + public override IEnumerable> GetEnvironmentVariables() + { + yield return new("MYSQL_HOST", Host); + yield return new("MYSQL_PORT", Port.ToString()); + } + + protected override async Task InitializeResources(Action registerResource) + { + var container = new ContainerBuilder(Image) + .WithPortBinding(MySqlPort, true) + .WithEnvironment("MYSQL_DATABASE", Database) + .WithEnvironment("MYSQL_ROOT_PASSWORD", Password) + .WithEnvironment("MYSQL_USER", Username) + .WithEnvironment("MYSQL_PASSWORD", Password) + .WithWaitStrategy(Wait.ForUnixContainer().UntilCommandIsCompleted("mysqladmin", "ping", "--silent", "-h", "localhost", "-u", "root", $"-p{Password}")) + .Build(); + + await container.StartAsync().ConfigureAwait(false); + + registerResource("container", container); + } +} From 9caacdbfd6805501487b1cd19552b653759d26c0 Mon Sep 17 00:00:00 2001 From: Steven Bouwkamp Date: Wed, 29 Jul 2026 10:38:04 -0400 Subject: [PATCH 08/28] Migrate MySQL 5.7 to Testcontainers --- docker-compose.yml | 13 +--- .../AdoNet/MySqlCommandTests.cs | 17 +++-- .../AdoNet/MySqlConnectorTests.cs | 2 +- .../Helpers/ContainersCollection.cs | 8 ++- .../Containers/MySql57Fixture.cs | 62 +++++++++++++++++++ 5 files changed, 83 insertions(+), 19 deletions(-) create mode 100644 tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql57Fixture.cs diff --git a/docker-compose.yml b/docker-compose.yml index 1f6fc2bc2648..bb6059608ae0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -140,14 +140,6 @@ services: mysql57: image: mysql/mysql-server:5.7@sha256:1178cdd375f758968cd834ac4057bae41307e64b7c69a9e145896e7b11f48064 - profiles: ["group1"] - environment: - - MYSQL_DATABASE=world - - MYSQL_ROOT_PASSWORD=mysqldb - - MYSQL_USER=mysqldb - - MYSQL_PASSWORD=mysqldb - ports: - - "127.0.0.1:3407:3306" sqlserver: image: mcr.microsoft.com/mssql/server:latest@sha256:2cd0aec4a3bfc3cf9205bed3f7922f4c6208f7c767dc62edcee308d0fd7d56d0 @@ -363,8 +355,6 @@ services: - ELASTICSEARCH7_HOST=elasticsearch7:9200 - ELASTICSEARCH6_HOST=elasticsearch6:9200 - ELASTICSEARCH5_HOST=elasticsearch5:9200 - - MYSQL57_HOST=mysql57 - - MYSQL57_PORT=3306 - RABBITMQ_HOST=rabbitmq - KAFKA_BROKER_HOST=kafka-broker:29092 - AWS_SDK_HOST=localstack:4566 @@ -570,7 +560,6 @@ services: image: andrewlock/wait-for-dependencies:latest@sha256:5d87561de8c019c3954298f707c1f6d7087f620a1ce36c3ec38cdfbc1ec4066e profiles: ["group1"] depends_on: - - mysql57 - rabbitmq - kafka-broker - kafka-zookeeper @@ -578,7 +567,7 @@ services: - test-agent environment: - TIMEOUT_LENGTH=120 - command: mysql57:3306 rabbitmq:5672 kafka-broker:9092 kafka-zookeeper:2181 couchbase:11210 test-agent:8126 test-agent:4317 test-agent:4318 + command: rabbitmq:5672 kafka-broker:9092 kafka-zookeeper:2181 couchbase:11210 test-agent:8126 test-agent:4317 test-agent:4318 StartDependencies.Group2: image: andrewlock/wait-for-dependencies:latest@sha256:5d87561de8c019c3954298f707c1f6d7087f620a1ce36c3ec38cdfbc1ec4066e diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/MySqlCommandTests.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/MySqlCommandTests.cs index 69a0fecf3557..05b7b8b2dd31 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/MySqlCommandTests.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/MySqlCommandTests.cs @@ -26,14 +26,16 @@ namespace Datadog.Trace.ClrProfiler.IntegrationTests.AdoNet [Collection(MySqlCollection.Name)] public class MySqlCommandTests : TracingIntegrationTest { - private readonly MySql8Fixture _mySqlFixture; + private readonly MySql8Fixture _mySql8Fixture; + private readonly MySql57Fixture _mySql57Fixture; - public MySqlCommandTests(ITestOutputHelper output, MySql8Fixture mySqlFixture) + public MySqlCommandTests(ITestOutputHelper output, MySql8Fixture mySql8Fixture, MySql57Fixture mySql57Fixture) : base("MySql", output) { - _mySqlFixture = mySqlFixture; + _mySql8Fixture = mySql8Fixture; + _mySql57Fixture = mySql57Fixture; SetServiceVersion("1.0.0"); - ConfigureContainers(mySqlFixture); + ConfigureContainers(mySql8Fixture, mySql57Fixture); } public override Result ValidateIntegrationSpan(MockSpan span, string metadataSchemaVersion) => span.IsMySql(metadataSchemaVersion); @@ -127,7 +129,12 @@ private async Task SubmitsTraces(string packageVersion, string metadataSchemaVer var settings = VerifyHelper.GetSpanVerifierSettings(); settings.AddRegexScrubber(new Regex("MySql-Test-[a-zA-Z0-9]{32}"), "MySql-Test-GUID"); - settings.AddSimpleScrubber($"out.host: {_mySqlFixture.Host}", "out.host: mysql"); + settings.AddSimpleScrubber($"out.host: {_mySql8Fixture.Host}", "out.host: mysql"); + if (_mySql57Fixture.Host is { } mySql57Host) + { + settings.AddSimpleScrubber($"out.host: {mySql57Host}", "out.host: mysql"); + } + settings.AddSimpleScrubber("out.host: localhost", "out.host: mysql"); settings.AddSimpleScrubber("out.host: mysql57", "out.host: mysql"); settings.AddSimpleScrubber("out.host: mysql_arm64", "out.host: mysql"); diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/MySqlConnectorTests.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/MySqlConnectorTests.cs index 88c42c07328c..1eab27fa048d 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/MySqlConnectorTests.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/MySqlConnectorTests.cs @@ -19,7 +19,7 @@ namespace Datadog.Trace.ClrProfiler.IntegrationTests.AdoNet { [Trait("RequiresDockerDependency", "true")] [Trait("DockerGroup", "1")] - [Collection(MySqlCollection.Name)] + [Collection(MySqlConnectorCollection.Name)] public class MySqlConnectorTests : TracingIntegrationTest { public MySqlConnectorTests(ITestOutputHelper output, MySql8Fixture mySqlFixture) diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs index fc9432048712..3785cc5614d9 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs @@ -36,10 +36,16 @@ public class PostgresCollection : ICollectionFixture } [CollectionDefinition(Name, DisableParallelization = true)] - public class MySqlCollection : ICollectionFixture + public class MySqlCollection : ICollectionFixture, ICollectionFixture { public const string Name = "MySql"; } + + [CollectionDefinition(Name, DisableParallelization = true)] + public class MySqlConnectorCollection : ICollectionFixture + { + public const string Name = "MySqlConnector"; + } } #pragma warning restore SA1649 // File name should match first type name diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql57Fixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql57Fixture.cs new file mode 100644 index 000000000000..d38278d48f8f --- /dev/null +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql57Fixture.cs @@ -0,0 +1,62 @@ +// +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. +// + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using DotNet.Testcontainers.Builders; +using DotNet.Testcontainers.Containers; + +namespace Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; + +public class MySql57Fixture : ContainerFixture +{ + private const int MySqlPort = 3306; + private const string Username = "mysqldb"; + private const string Password = "mysqldb"; + private const string Database = "world"; + private const string Image = "mysql/mysql-server:5.7@sha256:1178cdd375f758968cd834ac4057bae41307e64b7c69a9e145896e7b11f48064"; + + private IContainer? _container; + + public string? Host => _container?.Hostname; + + public ushort? Port => _container?.GetMappedPublicPort(MySqlPort); + + public override IEnumerable> GetEnvironmentVariables() + { + if (Host is { } host && Port is { } port) + { + yield return new("MYSQL57_HOST", host); + yield return new("MYSQL57_PORT", port.ToString()); + } + } + + protected override async Task InitializeResources(Action registerResource) + { + // The old MySql.Data tests are marked ArmUnsupported, but their collection also contains MySQL 8 tests. + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + return; + } + + var container = new ContainerBuilder(Image) + .WithPortBinding(MySqlPort, true) + .WithEnvironment("MYSQL_DATABASE", Database) + .WithEnvironment("MYSQL_ROOT_PASSWORD", Password) + .WithEnvironment("MYSQL_USER", Username) + .WithEnvironment("MYSQL_PASSWORD", Password) + .WithWaitStrategy(Wait.ForUnixContainer().UntilCommandIsCompleted("mysqladmin", "ping", "--silent", "-h", "localhost", "-u", "root", $"-p{Password}")) + .Build(); + + await container.StartAsync().ConfigureAwait(false); + + _container = container; + registerResource("container", container); + } +} From f2d1d397ca0d3bce490e6946f54657018c145ba2 Mon Sep 17 00:00:00 2001 From: Steven Bouwkamp Date: Wed, 29 Jul 2026 10:40:46 -0400 Subject: [PATCH 09/28] Migrate RabbitMQ to Testcontainers --- docker-compose.yml | 25 ++--------- .../DotNetSettingsExtensions.cs | 1 - .../DataStreamsMonitoringRabbitMQTests.cs | 6 ++- .../Helpers/ContainersCollection.cs | 6 +++ .../RabbitMQTests.cs | 11 ++++- .../Containers/RabbitMqFixture.cs | 44 +++++++++++++++++++ .../Samples.DataStreams.RabbitMQ/Program.cs | 3 +- .../integrations/Samples.RabbitMQ/Program.cs | 9 +++- 8 files changed, 77 insertions(+), 28 deletions(-) create mode 100644 tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/RabbitMqFixture.cs diff --git a/docker-compose.yml b/docker-compose.yml index bb6059608ae0..8ce31c2e5882 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -33,10 +33,6 @@ services: rabbitmq_arm64: image: rabbitmq:3-management@sha256:e582c0bc7766f3342496d8485efb5a1df782b5ce3886ad017e2eaae442311f69 - command: rabbitmq-server - ports: - - "5672" - - "15672" servicestackredis_arm64: image: redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20 @@ -70,11 +66,6 @@ services: rabbitmq: image: rabbitmq:3-management@sha256:e582c0bc7766f3342496d8485efb5a1df782b5ce3886ad017e2eaae442311f69 - profiles: ["group1"] - command: rabbitmq-server - ports: - - "127.0.0.1:5672:5672" - - "127.0.0.1:15672:15672" servicestackredis: image: redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20 @@ -355,7 +346,6 @@ services: - ELASTICSEARCH7_HOST=elasticsearch7:9200 - ELASTICSEARCH6_HOST=elasticsearch6:9200 - ELASTICSEARCH5_HOST=elasticsearch5:9200 - - RABBITMQ_HOST=rabbitmq - KAFKA_BROKER_HOST=kafka-broker:29092 - AWS_SDK_HOST=localstack:4566 - ORACLE_HOST=oracle @@ -560,14 +550,13 @@ services: image: andrewlock/wait-for-dependencies:latest@sha256:5d87561de8c019c3954298f707c1f6d7087f620a1ce36c3ec38cdfbc1ec4066e profiles: ["group1"] depends_on: - - rabbitmq - kafka-broker - kafka-zookeeper - couchbase - test-agent environment: - TIMEOUT_LENGTH=120 - command: rabbitmq:5672 kafka-broker:9092 kafka-zookeeper:2181 couchbase:11210 test-agent:8126 test-agent:4317 test-agent:4318 + command: kafka-broker:9092 kafka-zookeeper:2181 couchbase:11210 test-agent:8126 test-agent:4317 test-agent:4318 StartDependencies.Group2: image: andrewlock/wait-for-dependencies:latest@sha256:5d87561de8c019c3954298f707c1f6d7087f620a1ce36c3ec38cdfbc1ec4066e @@ -621,7 +610,6 @@ services: - ELASTICSEARCH7_HOST=elasticsearch7_arm64:9200 - ELASTICSEARCH6_HOST=elasticsearch7_arm64:9200 - ELASTICSEARCH5_HOST=elasticsearch7_arm64:9200 - - RABBITMQ_HOST=rabbitmq_arm64 - AWS_SDK_HOST=localstack_arm64:4566 - COSMOSDB_ENDPOINT=https://cosmosdb-emulator_arm64:8081 - DD_LOGGER_DD_API_KEY @@ -655,7 +643,6 @@ services: depends_on: - elasticsearch7_arm64 - mongo_arm64 - - rabbitmq_arm64 - localstack_arm64 - test-agent - cosmosdb-emulator_arm64 @@ -665,13 +652,12 @@ services: depends_on: - elasticsearch7_arm64 - mongo_arm64 - - rabbitmq_arm64 - localstack_arm64 - test-agent - cosmosdb-emulator_arm64 environment: - TIMEOUT_LENGTH=120 - command: elasticsearch7_arm64:9200 mongo_arm64:27017 rabbitmq_arm64:5672 localstack_arm64:4566 test-agent:8126 test-agent:4317 test-agent:4318 cosmosdb-emulator_arm64:8081 + command: elasticsearch7_arm64:9200 mongo_arm64:27017 localstack_arm64:4566 test-agent:8126 test-agent:4317 test-agent:4318 cosmosdb-emulator_arm64:8081 IntegrationTests.ARM64.Debugger: build: @@ -746,11 +732,10 @@ services: depends_on: - elasticsearch7_osx_arm64 - mongo_osx_arm64 - - rabbitmq_osx_arm64 - localstack_osx_arm64 environment: - TIMEOUT_LENGTH=120 - command: elasticsearch7_osx_arm64:9200 mongo_osx_arm64:27017 rabbitmq_osx_arm64:5672 localstack_osx_arm64:4566 + command: elasticsearch7_osx_arm64:9200 mongo_osx_arm64:27017 localstack_osx_arm64:4566 # OSX ARM64 dependencies @@ -789,10 +774,6 @@ services: rabbitmq_osx_arm64: image: rabbitmq:3-management@sha256:e582c0bc7766f3342496d8485efb5a1df782b5ce3886ad017e2eaae442311f69 - command: rabbitmq-server - ports: - - "5672:5672" - - "15672:15672" servicestackredis_osx_arm64: image: redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20 diff --git a/tracer/build/_build/NukeExtensions/DotNetSettingsExtensions.cs b/tracer/build/_build/NukeExtensions/DotNetSettingsExtensions.cs index 55257c6faa6e..07f896e1d1db 100644 --- a/tracer/build/_build/NukeExtensions/DotNetSettingsExtensions.cs +++ b/tracer/build/_build/NukeExtensions/DotNetSettingsExtensions.cs @@ -275,7 +275,6 @@ public static T SetLocalOsxEnvironmentVariables(this T toolSettings) .SetProcessEnvironmentVariable("ELASTICSEARCH7_HOST", "localhost:9200") .SetProcessEnvironmentVariable("ELASTICSEARCH6_HOST", "localhost:9200") .SetProcessEnvironmentVariable("ELASTICSEARCH5_HOST", "localhost:9200") - .SetProcessEnvironmentVariable("RABBITMQ_HOST", "localhost") .SetProcessEnvironmentVariable("AWS_SDK_HOST", "localhost:4566"); } diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/DataStreamsMonitoringRabbitMQTests.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/DataStreamsMonitoringRabbitMQTests.cs index c30d7d85c92e..a7f3163578bc 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/DataStreamsMonitoringRabbitMQTests.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/DataStreamsMonitoringRabbitMQTests.cs @@ -7,8 +7,10 @@ using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; +using Datadog.Trace.ClrProfiler.IntegrationTests.Helpers; using Datadog.Trace.Configuration; using Datadog.Trace.TestHelpers; +using Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; using Datadog.Trace.TestHelpers.DataStreamsMonitoring; using FluentAssertions; using FluentAssertions.Execution; @@ -22,12 +24,14 @@ namespace Datadog.Trace.ClrProfiler.IntegrationTests; [UsesVerify] [Trait("RequiresDockerDependency", "true")] [Trait("DockerGroup", "1")] +[Collection(RabbitMqCollection.Name)] public class DataStreamsMonitoringRabbitMQTests : TestHelper { - public DataStreamsMonitoringRabbitMQTests(ITestOutputHelper output) + public DataStreamsMonitoringRabbitMQTests(ITestOutputHelper output, RabbitMqFixture rabbitMqFixture) : base("DataStreams.RabbitMQ", output) { SetServiceVersion("1.0.0"); + ConfigureContainers(rabbitMqFixture); } [SkippableTheory] diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs index 3785cc5614d9..7fece20f86b4 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs @@ -46,6 +46,12 @@ public class MySqlConnectorCollection : ICollectionFixture { public const string Name = "MySqlConnector"; } + + [CollectionDefinition(Name, DisableParallelization = true)] + public class RabbitMqCollection : ICollectionFixture + { + public const string Name = "RabbitMq"; + } } #pragma warning restore SA1649 // File name should match first type name diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/RabbitMQTests.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/RabbitMQTests.cs index af004a62650c..342dedc41d51 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/RabbitMQTests.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/RabbitMQTests.cs @@ -9,9 +9,11 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Datadog.Trace.ClrProfiler.IntegrationTests.Helpers; using Datadog.Trace.Configuration; using Datadog.Trace.ExtensionMethods; using Datadog.Trace.TestHelpers; +using Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; using FluentAssertions.Execution; using VerifyXunit; using Xunit; @@ -22,12 +24,17 @@ namespace Datadog.Trace.ClrProfiler.IntegrationTests [UsesVerify] [Trait("RequiresDockerDependency", "true")] [Trait("DockerGroup", "1")] + [Collection(RabbitMqCollection.Name)] public class RabbitMQTests : TracingIntegrationTest { - public RabbitMQTests(ITestOutputHelper output) + private readonly RabbitMqFixture _rabbitMqFixture; + + public RabbitMQTests(ITestOutputHelper output, RabbitMqFixture rabbitMqFixture) : base("RabbitMQ", output) { + _rabbitMqFixture = rabbitMqFixture; SetServiceVersion("1.0.0"); + ConfigureContainers(rabbitMqFixture); } public static IEnumerable GetEnabledConfig() @@ -78,6 +85,8 @@ public async Task SubmitTraces(string packageVersion, string metadataSchemaVersi // We generate a new queue name for the "default" queue with each run settings.AddScrubber(QueueScrubber.ReplaceRabbitMqQueues); + settings.AddSimpleScrubber($"out.host: {_rabbitMqFixture.Host}", "out.host: rabbitmq"); + settings.AddSimpleScrubber($"peer.service: {_rabbitMqFixture.Host}", "peer.service: rabbitmq"); settings.AddSimpleScrubber("out.host: localhost", "out.host: rabbitmq"); settings.AddSimpleScrubber("out.host: rabbitmq_arm64", "out.host: rabbitmq"); settings.AddSimpleScrubber("peer.service: localhost", "peer.service: rabbitmq"); diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/RabbitMqFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/RabbitMqFixture.cs new file mode 100644 index 000000000000..6c55a4cf81ee --- /dev/null +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/RabbitMqFixture.cs @@ -0,0 +1,44 @@ +// +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. +// + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using DotNet.Testcontainers.Builders; +using DotNet.Testcontainers.Containers; + +namespace Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; + +public class RabbitMqFixture : ContainerFixture +{ + private const int RabbitMqPort = 5672; + private const string Image = "rabbitmq:3-management@sha256:e582c0bc7766f3342496d8485efb5a1df782b5ce3886ad017e2eaae442311f69"; + + public string Host => Container.Hostname; + + public ushort Port => Container.GetMappedPublicPort(RabbitMqPort); + + private IContainer Container => GetResource("container"); + + public override IEnumerable> GetEnvironmentVariables() + { + yield return new("RABBITMQ_HOST", Host); + yield return new("RABBITMQ_PORT", Port.ToString()); + } + + protected override async Task InitializeResources(Action registerResource) + { + var container = new ContainerBuilder(Image) + .WithPortBinding(RabbitMqPort, true) + .WithWaitStrategy(Wait.ForUnixContainer().UntilCommandIsCompleted("rabbitmq-diagnostics", "-q", "ping")) + .Build(); + + await container.StartAsync().ConfigureAwait(false); + + registerResource("container", container); + } +} diff --git a/tracer/test/test-applications/integrations/Samples.DataStreams.RabbitMQ/Program.cs b/tracer/test/test-applications/integrations/Samples.DataStreams.RabbitMQ/Program.cs index 4ce8b5bf9e02..24131af4adbb 100644 --- a/tracer/test/test-applications/integrations/Samples.DataStreams.RabbitMQ/Program.cs +++ b/tracer/test/test-applications/integrations/Samples.DataStreams.RabbitMQ/Program.cs @@ -22,12 +22,13 @@ public static class Program private static readonly string DirectRoutingKey = nameof(DirectRoutingKey); private static readonly string Message = nameof(Message); private static readonly string Host = Environment.GetEnvironmentVariable("RABBITMQ_HOST") ?? "localhost"; + private static readonly int Port = int.TryParse(Environment.GetEnvironmentVariable("RABBITMQ_PORT"), out var port) ? port : 5672; public static async Task Main(string[] args) { await SampleHelpers.WaitForDiscoveryService(); - var factory = new ConnectionFactory() { HostName = Host }; + var factory = new ConnectionFactory() { HostName = Host, Port = Port }; #if RABBITMQ_7_0 using var connection = await factory.CreateConnectionAsync(); using var model = await connection.CreateChannelAsync(); diff --git a/tracer/test/test-applications/integrations/Samples.RabbitMQ/Program.cs b/tracer/test/test-applications/integrations/Samples.RabbitMQ/Program.cs index 987cca5c349d..cd242d026ec0 100644 --- a/tracer/test/test-applications/integrations/Samples.RabbitMQ/Program.cs +++ b/tracer/test/test-applications/integrations/Samples.RabbitMQ/Program.cs @@ -35,10 +35,15 @@ private static string Host() return Environment.GetEnvironmentVariable("RABBITMQ_HOST") ?? "localhost"; } + private static int Port() + { + return int.TryParse(Environment.GetEnvironmentVariable("RABBITMQ_PORT"), out var port) ? port : 5672; + } + public static async Task Main(string[] args) { // connecting takes 2 to 3 seconds, we re-use the connection to save time - var factory = new ConnectionFactory() { HostName = Host() }; + var factory = new ConnectionFactory() { HostName = Host(), Port = Port() }; #if RABBITMQ_5_0 && !RABBITMQ_7_0 factory.DispatchConsumersAsync = true; #endif @@ -51,7 +56,7 @@ public static async Task Main(string[] args) await RunProducersAndConsumers(asyncConnection, useQueue: true, ConsumerType.ExternalExplicit, isAsyncConsumer: true); } - factory = new ConnectionFactory() { HostName = Host() }; + factory = new ConnectionFactory() { HostName = Host(), Port = Port() }; using (var syncConnection = await Helper.CreateConnectionAsync(factory)) { // Test a derived type for the sync consumer from the library From 18c1342bdb95b42635f8f4890761f8f3e0c171a9 Mon Sep 17 00:00:00 2001 From: Steven Bouwkamp Date: Wed, 29 Jul 2026 10:47:00 -0400 Subject: [PATCH 10/28] Migrate Kafka to Testcontainers --- docker-compose.yml | 43 +-------- .../DataStreamsMonitoringKafkaTests.cs | 7 +- .../Helpers/ContainersCollection.cs | 6 ++ .../KafkaTests.cs | 12 ++- .../Containers/KafkaFixture.cs | 87 +++++++++++++++++++ ...ace.TestHelpers.AutoInstrumentation.csproj | 1 + 6 files changed, 105 insertions(+), 51 deletions(-) create mode 100644 tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/KafkaFixture.cs diff --git a/docker-compose.yml b/docker-compose.yml index 8ce31c2e5882..5176bd20ed22 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -198,50 +198,11 @@ services: ports: - "127.0.0.1:8585:8585" - # See https://github.com/confluentinc/cp-all-in-one/blob/6.1.1-post/cp-all-in-one/docker-compose.yml - # For original definitions kafka-zookeeper: image: confluentinc/cp-zookeeper:6.1.1@sha256:a7c0a20dce46a705300cd464e511e9c70ac55ec7e62c024867470a19ce210563 - profiles: ["group1"] - hostname: kafka-zookeeper - container_name: kafka-zookeeper - ports: - - "2181:2181" - restart: unless-stopped - environment: - ZOOKEEPER_CLIENT_PORT: 2181 - ZOOKEEPER_TICK_TIME: 2000 kafka-broker: image: confluentinc/cp-server:6.1.1@sha256:4a1ff92bd03e361759ba339c97b4c4b7dbb52d7cea478dda22d034261a8991e4 - profiles: ["group1"] - hostname: kafka-broker - container_name: kafka-broker - depends_on: - - kafka-zookeeper - ports: - - "9092:9092" - - "9101:9101" - restart: unless-stopped - environment: - KAFKA_BROKER_ID: 1 - KAFKA_ZOOKEEPER_CONNECT: 'kafka-zookeeper:2181' - KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT - KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false" - KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka-broker:29092,PLAINTEXT_HOST://localhost:9092 - KAFKA_METRIC_REPORTERS: io.confluent.metrics.reporter.ConfluentMetricsReporter - KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 - KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0 - KAFKA_CONFLUENT_LICENSE_TOPIC_REPLICATION_FACTOR: 1 - KAFKA_CONFLUENT_BALANCER_TOPIC_REPLICATION_FACTOR: 1 - KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 - KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 - KAFKA_JMX_PORT: 9101 - KAFKA_JMX_HOSTNAME: localhost - CONFLUENT_METRICS_REPORTER_BOOTSTRAP_SERVERS: kafka-broker:29092 - CONFLUENT_METRICS_REPORTER_TOPIC_REPLICAS: 1 - CONFLUENT_METRICS_ENABLE: 'true' - CONFLUENT_SUPPORT_CUSTOMER_ID: 'anonymous' openldap: image: osixia/openldap:latest@sha256:3f68751292b43564a2586fc29fb7337573e2dad692b92d4e78e49ad5c22e567b @@ -550,13 +511,11 @@ services: image: andrewlock/wait-for-dependencies:latest@sha256:5d87561de8c019c3954298f707c1f6d7087f620a1ce36c3ec38cdfbc1ec4066e profiles: ["group1"] depends_on: - - kafka-broker - - kafka-zookeeper - couchbase - test-agent environment: - TIMEOUT_LENGTH=120 - command: kafka-broker:9092 kafka-zookeeper:2181 couchbase:11210 test-agent:8126 test-agent:4317 test-agent:4318 + command: couchbase:11210 test-agent:8126 test-agent:4317 test-agent:4318 StartDependencies.Group2: image: andrewlock/wait-for-dependencies:latest@sha256:5d87561de8c019c3954298f707c1f6d7087f620a1ce36c3ec38cdfbc1ec4066e diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/DataStreamsMonitoringKafkaTests.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/DataStreamsMonitoringKafkaTests.cs index 65204d18f733..cc9adfe20da6 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/DataStreamsMonitoringKafkaTests.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/DataStreamsMonitoringKafkaTests.cs @@ -7,8 +7,10 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Datadog.Trace.ClrProfiler.IntegrationTests.Helpers; using Datadog.Trace.Configuration; using Datadog.Trace.TestHelpers; +using Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; using Datadog.Trace.TestHelpers.DataStreamsMonitoring; using FluentAssertions; using FluentAssertions.Execution; @@ -19,15 +21,16 @@ namespace Datadog.Trace.ClrProfiler.IntegrationTests; [UsesVerify] -[Collection(nameof(KafkaTests.KafkaTestsCollection))] +[Collection(KafkaCollection.Name)] [Trait("RequiresDockerDependency", "true")] [Trait("DockerGroup", "1")] public class DataStreamsMonitoringKafkaTests : TestHelper { - public DataStreamsMonitoringKafkaTests(ITestOutputHelper output) + public DataStreamsMonitoringKafkaTests(ITestOutputHelper output, KafkaFixture kafkaFixture) : base("DataStreams.Kafka", output) { SetServiceVersion("1.0.0"); + ConfigureContainers(kafkaFixture); } public static IEnumerable GetKafkaTestData() diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs index 7fece20f86b4..6ba6927a2d1d 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs @@ -52,6 +52,12 @@ public class RabbitMqCollection : ICollectionFixture { public const string Name = "RabbitMq"; } + + [CollectionDefinition(Name, DisableParallelization = true)] + public class KafkaCollection : ICollectionFixture + { + public const string Name = "Kafka"; + } } #pragma warning restore SA1649 // File name should match first type name diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/KafkaTests.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/KafkaTests.cs index e6747c218a0f..9de624282466 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/KafkaTests.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/KafkaTests.cs @@ -9,8 +9,10 @@ using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Datadog.Trace.ClrProfiler.IntegrationTests.Helpers; using Datadog.Trace.Configuration; using Datadog.Trace.TestHelpers; +using Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; using FluentAssertions; using FluentAssertions.Execution; using Xunit; @@ -18,7 +20,7 @@ namespace Datadog.Trace.ClrProfiler.IntegrationTests { - [Collection(nameof(KafkaTestsCollection))] + [Collection(KafkaCollection.Name)] [Trait("RequiresDockerDependency", "true")] [Trait("DockerGroup", "1")] public class KafkaTests : TracingIntegrationTest @@ -38,10 +40,11 @@ public class KafkaTests : TracingIntegrationTest private const string ErrorProducerResourceName = "Produce Topic INVALID-TOPIC"; - public KafkaTests(ITestOutputHelper output) + public KafkaTests(ITestOutputHelper output, KafkaFixture kafkaFixture) : base("Kafka", output) { SetServiceVersion("1.0.0"); + ConfigureContainers(kafkaFixture); } public static IEnumerable GetEnabledConfig() @@ -209,10 +212,5 @@ private string GetSuccessfulResourceName(string type, string topic) { return $"{type} Topic {topic}"; } - - [CollectionDefinition(nameof(KafkaTestsCollection), DisableParallelization = true)] - public class KafkaTestsCollection - { - } } } diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/KafkaFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/KafkaFixture.cs new file mode 100644 index 000000000000..908bb714fbe1 --- /dev/null +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/KafkaFixture.cs @@ -0,0 +1,87 @@ +// +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. +// + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using DotNet.Testcontainers.Builders; +using DotNet.Testcontainers.Containers; +using DotNet.Testcontainers.Networks; +using Testcontainers.Kafka; + +namespace Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; + +public class KafkaFixture : ContainerFixture +{ + private const int ZooKeeperPort = 2181; + private const string ZooKeeperAlias = "kafka-zookeeper"; + private const string ZooKeeperImage = "confluentinc/cp-zookeeper:6.1.1@sha256:a7c0a20dce46a705300cd464e511e9c70ac55ec7e62c024867470a19ce210563"; + private const string KafkaImage = "confluentinc/cp-server:6.1.1@sha256:4a1ff92bd03e361759ba339c97b4c4b7dbb52d7cea478dda22d034261a8991e4"; + + private KafkaContainer KafkaContainer => GetResource("resources").KafkaContainer; + + public override IEnumerable> GetEnvironmentVariables() + { + yield return new("KAFKA_BROKER_HOST", KafkaContainer.GetBootstrapAddress()); + } + + protected override async Task InitializeResources(Action registerResource) + { + var network = new NetworkBuilder().Build(); + var zooKeeperContainer = new ContainerBuilder(ZooKeeperImage) + .WithNetwork(network) + .WithNetworkAliases(ZooKeeperAlias) + .WithEnvironment("ZOOKEEPER_CLIENT_PORT", ZooKeeperPort.ToString()) + .WithEnvironment("ZOOKEEPER_TICK_TIME", "2000") + .WithWaitStrategy(Wait.ForUnixContainer().UntilInternalTcpPortIsAvailable(ZooKeeperPort)) + .Build(); + var kafkaContainer = new KafkaBuilder(KafkaImage) + .WithNetwork(network) + .WithZooKeeper($"{ZooKeeperAlias}:{ZooKeeperPort}") + .WithPortBinding(KafkaBuilder.ZooKeeperPort, true) + .WithEnvironment("KAFKA_AUTO_CREATE_TOPICS_ENABLE", "false") + .Build(); + var resources = new Resources(kafkaContainer, zooKeeperContainer, network); + + try + { + await network.CreateAsync().ConfigureAwait(false); + await zooKeeperContainer.StartAsync().ConfigureAwait(false); + await kafkaContainer.StartAsync().ConfigureAwait(false); + } + catch + { + await resources.DisposeAsync().ConfigureAwait(false); + throw; + } + + registerResource("resources", resources); + } + + private sealed class Resources : IAsyncDisposable + { + public Resources(KafkaContainer kafkaContainer, IContainer zooKeeperContainer, INetwork network) + { + KafkaContainer = kafkaContainer; + ZooKeeperContainer = zooKeeperContainer; + Network = network; + } + + public KafkaContainer KafkaContainer { get; } + + private IContainer ZooKeeperContainer { get; } + + private INetwork Network { get; } + + public async ValueTask DisposeAsync() + { + await KafkaContainer.DisposeAsync().ConfigureAwait(false); + await ZooKeeperContainer.DisposeAsync().ConfigureAwait(false); + await Network.DisposeAsync().ConfigureAwait(false); + } + } +} diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Datadog.Trace.TestHelpers.AutoInstrumentation.csproj b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Datadog.Trace.TestHelpers.AutoInstrumentation.csproj index 91191b37681d..72e9b4c51e06 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Datadog.Trace.TestHelpers.AutoInstrumentation.csproj +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Datadog.Trace.TestHelpers.AutoInstrumentation.csproj @@ -4,6 +4,7 @@ + From df950971f6a1a94ff948ff734de9461cb369d8b6 Mon Sep 17 00:00:00 2001 From: Steven Bouwkamp Date: Wed, 29 Jul 2026 11:02:32 -0400 Subject: [PATCH 11/28] Migrate Couchbase to Testcontainers --- docker-compose.yml | 9 +- .../Couchbase3Tests.cs | 13 +- .../CouchbaseTests.cs | 6 +- .../Helpers/ContainersCollection.cs | 6 + .../Containers/CouchbaseFixture.cs | 191 ++++++++++++++++++ .../integrations/Samples.Couchbase/Program.cs | 10 +- .../Samples.Couchbase3/Program.cs | 26 ++- 7 files changed, 236 insertions(+), 25 deletions(-) create mode 100644 tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/CouchbaseFixture.cs diff --git a/docker-compose.yml b/docker-compose.yml index 5176bd20ed22..dd798097748d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -87,11 +87,7 @@ services: command: mongod couchbase: - image: bentonam/couchbase-docker:community-5.0.1@sha256:847440848c80b95d82b12c8834856a14b1bfde854bd03b6acd0f9d0ac3484c63 - profiles: ["group1"] - ports: - - "8091-8094:8091-8094" - - "11210:11210" + image: couchbase:community-6.6.0@sha256:43103efdd4b562366c7a48afa977c4ad148e09c877da28a83a86b2c5ee2daa97 elasticsearch7: image: docker.elastic.co/elasticsearch/elasticsearch:7.14.1@sha256:2dcd2f31e246a8b13995ba24922da2edc3d88e65532ff301d0b92cb1be358af5 @@ -511,11 +507,10 @@ services: image: andrewlock/wait-for-dependencies:latest@sha256:5d87561de8c019c3954298f707c1f6d7087f620a1ce36c3ec38cdfbc1ec4066e profiles: ["group1"] depends_on: - - couchbase - test-agent environment: - TIMEOUT_LENGTH=120 - command: couchbase:11210 test-agent:8126 test-agent:4317 test-agent:4318 + command: test-agent:8126 test-agent:4317 test-agent:4318 StartDependencies.Group2: image: andrewlock/wait-for-dependencies:latest@sha256:5d87561de8c019c3954298f707c1f6d7087f620a1ce36c3ec38cdfbc1ec4066e diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Couchbase3Tests.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Couchbase3Tests.cs index 3834d75b17cc..5d8082424d56 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Couchbase3Tests.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Couchbase3Tests.cs @@ -8,8 +8,10 @@ using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Datadog.Trace.ClrProfiler.IntegrationTests.Helpers; using Datadog.Trace.Configuration; using Datadog.Trace.TestHelpers; +using Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; using FluentAssertions.Execution; using VerifyXunit; using Xunit; @@ -20,12 +22,17 @@ namespace Datadog.Trace.ClrProfiler.IntegrationTests [UsesVerify] [Trait("RequiresDockerDependency", "true")] [Trait("DockerGroup", "1")] + [Collection(CouchbaseCollection.Name)] public class Couchbase3Tests : TracingIntegrationTest { - public Couchbase3Tests(ITestOutputHelper output) + private readonly CouchbaseFixture _couchbaseFixture; + + public Couchbase3Tests(ITestOutputHelper output, CouchbaseFixture couchbaseFixture) : base("Couchbase3", output) { + _couchbaseFixture = couchbaseFixture; SetServiceVersion("1.0.0"); + ConfigureContainers(couchbaseFixture); } public static IEnumerable GetEnabledConfig() @@ -74,6 +81,10 @@ public async Task SubmitTraces(string packageVersion, string metadataSchemaVersi settings.AddSimpleScrubber("db.couchbase.seed.nodes: localhost", "db.couchbase.seed.nodes: couchbase"); settings.AddSimpleScrubber("out.host: localhost", "out.host: couchbase"); settings.AddSimpleScrubber("peer.service: localhost", "peer.service: couchbase"); + settings.AddRegexScrubber(new Regex($@"db.couchbase.seed.nodes: {Regex.Escape(_couchbaseFixture.Host)}(?::[0-9]+)?"), "db.couchbase.seed.nodes: couchbase"); + settings.AddSimpleScrubber($"out.host: {_couchbaseFixture.Host}", "out.host: couchbase"); + settings.AddSimpleScrubber($"peer.service: {_couchbaseFixture.Host}", "peer.service: couchbase"); + settings.AddSimpleScrubber($"couchbase.operation.bucket: {_couchbaseFixture.BucketName}", "couchbase.operation.bucket: default"); // theres' a fair amount less in 3.0.7 - fewer spans, different terminology etc diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/CouchbaseTests.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/CouchbaseTests.cs index 6d4644e9699c..a35bc54853b1 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/CouchbaseTests.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/CouchbaseTests.cs @@ -6,9 +6,11 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Datadog.Trace.ClrProfiler.IntegrationTests.Helpers; using Datadog.Trace.Configuration; using Datadog.Trace.ExtensionMethods; using Datadog.Trace.TestHelpers; +using Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; using Xunit; using Xunit.Abstractions; @@ -16,12 +18,14 @@ namespace Datadog.Trace.ClrProfiler.IntegrationTests { [Trait("RequiresDockerDependency", "true")] [Trait("DockerGroup", "1")] + [Collection(CouchbaseCollection.Name)] public class CouchbaseTests : TracingIntegrationTest { - public CouchbaseTests(ITestOutputHelper output) + public CouchbaseTests(ITestOutputHelper output, CouchbaseFixture couchbaseFixture) : base("Couchbase", output) { SetServiceVersion("1.0.0"); + ConfigureContainers(couchbaseFixture); } public static IEnumerable GetEnabledConfig() diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs index 6ba6927a2d1d..8439c89b0c65 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Helpers/ContainersCollection.cs @@ -58,6 +58,12 @@ public class KafkaCollection : ICollectionFixture { public const string Name = "Kafka"; } + + [CollectionDefinition(Name, DisableParallelization = true)] + public class CouchbaseCollection : ICollectionFixture + { + public const string Name = "Couchbase"; + } } #pragma warning restore SA1649 // File name should match first type name diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/CouchbaseFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/CouchbaseFixture.cs new file mode 100644 index 000000000000..a1725164ee8e --- /dev/null +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/CouchbaseFixture.cs @@ -0,0 +1,191 @@ +// +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. +// + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using DotNet.Testcontainers.Builders; +using DotNet.Testcontainers.Containers; + +namespace Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; + +public class CouchbaseFixture : ContainerFixture +{ + private const int ManagementPort = 8091; + private const int ManagementSslPort = 18091; + private const int ViewPort = 8092; + private const int ViewSslPort = 18092; + private const int QueryPort = 8093; + private const int QuerySslPort = 18093; + private const int KeyValuePort = 11210; + private const int KeyValueSslPort = 11207; + private const string Image = "couchbase:community-6.6.0@sha256:43103efdd4b562366c7a48afa977c4ad148e09c877da28a83a86b2c5ee2daa97"; + private const string AdministratorUsername = "Administrator"; + private const string Username = "default"; + private const string Password = "password"; + + public string Host => Container.Hostname; + + public string BucketName => "default"; + + private IContainer Container => GetResource("container"); + + public override IEnumerable> GetEnvironmentVariables() + { + yield return new("COUCHBASE_HOST", Host); + yield return new("COUCHBASE_PORT", Container.GetMappedPublicPort(ManagementPort).ToString()); + yield return new("COUCHBASE_CONNECTION_STRING", $"{Host}:{Container.GetMappedPublicPort(KeyValuePort)}"); + yield return new("COUCHBASE_USERNAME", Username); + yield return new("COUCHBASE_PASSWORD", Password); + yield return new("COUCHBASE_BUCKET", BucketName); + } + + protected override async Task InitializeResources(Action registerResource) + { + var container = new ContainerBuilder(Image) + // SDK 2.4 predates Couchbase's external-network selection, so it requires the advertised standard ports. + .WithPortBinding(ManagementPort, false) + .WithPortBinding(ManagementSslPort, false) + .WithPortBinding(ViewPort, false) + .WithPortBinding(ViewSslPort, false) + .WithPortBinding(QueryPort, false) + .WithPortBinding(QuerySslPort, false) + .WithPortBinding(KeyValuePort, false) + .WithPortBinding(KeyValueSslPort, false) + .WithWaitStrategy(Wait.ForUnixContainer().UntilInternalTcpPortIsAvailable(ManagementPort)) + .Build(); + + try + { + await container.StartAsync().ConfigureAwait(false); + await ConfigureCouchbaseAsync(container).ConfigureAwait(false); + } + catch + { + await container.DisposeAsync().ConfigureAwait(false); + throw; + } + + registerResource("container", container); + } + + private static async Task ConfigureCouchbaseAsync(IContainer container) + { + using var client = new HttpClient + { + BaseAddress = new UriBuilder(Uri.UriSchemeHttp, container.Hostname, container.GetMappedPublicPort(ManagementPort)).Uri, + Timeout = TimeSpan.FromSeconds(5), + }; + + await WaitForSuccessAsync(client, "/pools").ConfigureAwait(false); + await PostFormAsync(client, "/node/controller/rename", new() { ["hostname"] = container.Hostname }).ConfigureAwait(false); + await PostFormAsync(client, "/node/controller/setupServices", new() { ["services"] = "kv,index,n1ql" }).ConfigureAwait(false); + await PostFormAsync(client, "/pools/default", new() { ["memoryQuota"] = "256", ["indexMemoryQuota"] = "256" }).ConfigureAwait(false); + await PutFormAsync( + client, + "/node/controller/setupAlternateAddresses/external", + new() + { + ["hostname"] = container.Hostname, + ["mgmt"] = container.GetMappedPublicPort(ManagementPort).ToString(), + ["mgmtSSL"] = container.GetMappedPublicPort(ManagementSslPort).ToString(), + ["kv"] = container.GetMappedPublicPort(KeyValuePort).ToString(), + ["kvSSL"] = container.GetMappedPublicPort(KeyValueSslPort).ToString(), + ["capi"] = container.GetMappedPublicPort(ViewPort).ToString(), + ["capiSSL"] = container.GetMappedPublicPort(ViewSslPort).ToString(), + ["n1ql"] = container.GetMappedPublicPort(QueryPort).ToString(), + ["n1qlSSL"] = container.GetMappedPublicPort(QuerySslPort).ToString(), + }).ConfigureAwait(false); + await PostFormAsync( + client, + "/pools/default/buckets", + new() { ["name"] = "default", ["ramQuotaMB"] = "100", ["replicaNumber"] = "0" }).ConfigureAwait(false); + await PostFormAsync( + client, + "/settings/web", + new() { ["username"] = AdministratorUsername, ["password"] = Password, ["port"] = "SAME" }).ConfigureAwait(false); + + var credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{AdministratorUsername}:{Password}")); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials); + await PutFormAsync( + client, + $"/settings/rbac/users/local/{Username}", + new() { ["name"] = Username, ["password"] = Password, ["roles"] = "admin" }).ConfigureAwait(false); + await WaitForSuccessAsync( + client, + "/pools/default/buckets/default", + content => content.Contains(@"""status"":""healthy""", StringComparison.OrdinalIgnoreCase)).ConfigureAwait(false); + + using var queryClient = new HttpClient + { + BaseAddress = new UriBuilder(Uri.UriSchemeHttp, container.Hostname, container.GetMappedPublicPort(QueryPort)).Uri, + Timeout = TimeSpan.FromSeconds(5), + }; + queryClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials); + await WaitForSuccessAsync(queryClient, "/admin/ping").ConfigureAwait(false); + await WaitForSuccessAsync( + client, + "/pools/default/nodeServices", + content => content.Contains(@"""n1ql"":8093", StringComparison.OrdinalIgnoreCase)).ConfigureAwait(false); + } + + private static Task PostFormAsync(HttpClient client, string path, Dictionary form) + => SendFormAsync(client, HttpMethod.Post, path, form); + + private static Task PutFormAsync(HttpClient client, string path, Dictionary form) + => SendFormAsync(client, HttpMethod.Put, path, form); + + private static async Task SendFormAsync(HttpClient client, HttpMethod method, string path, Dictionary form) + { + using var request = new HttpRequestMessage(method, path) + { + Content = new FormUrlEncodedContent(form.Select(static pair => new KeyValuePair(pair.Key, pair.Value))), + }; + using var response = await client.SendAsync(request).ConfigureAwait(false); + + if (!response.IsSuccessStatusCode) + { + var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new InvalidOperationException($"Couchbase request '{path}' failed with status {(int)response.StatusCode}: {content}"); + } + } + + private static async Task WaitForSuccessAsync(HttpClient client, string path, Func? responsePredicate = null) + { + var timeout = DateTime.UtcNow.AddMinutes(2); + + while (DateTime.UtcNow < timeout) + { + try + { + using var response = await client.GetAsync(path).ConfigureAwait(false); + if (response.IsSuccessStatusCode) + { + var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + if (responsePredicate is null || responsePredicate(content)) + { + return; + } + } + } + catch (HttpRequestException) + { + } + catch (TaskCanceledException) + { + } + + await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); + } + + throw new TimeoutException($"Couchbase endpoint '{path}' did not become ready."); + } +} diff --git a/tracer/test/test-applications/integrations/Samples.Couchbase/Program.cs b/tracer/test/test-applications/integrations/Samples.Couchbase/Program.cs index 2a66ab70f392..fba43617650d 100644 --- a/tracer/test/test-applications/integrations/Samples.Couchbase/Program.cs +++ b/tracer/test/test-applications/integrations/Samples.Couchbase/Program.cs @@ -50,8 +50,8 @@ private async Task RunAllExamples() { var config = GetConnectionConfig(); _cluster = new Cluster(config); - _cluster.Authenticate("default", "password"); - _bucket = _cluster.OpenBucket("default"); + _cluster.Authenticate(Username(), Password()); + _bucket = _cluster.OpenBucket(BucketName()); RetrieveAndUpdate(); await RetrieveAndUpdateAsync(); } @@ -91,6 +91,12 @@ private static string Host() return $"{host}:{port}"; } + private static string Username() => Environment.GetEnvironmentVariable("COUCHBASE_USERNAME") ?? "default"; + + private static string Password() => Environment.GetEnvironmentVariable("COUCHBASE_PASSWORD") ?? "password"; + + private static string BucketName() => Environment.GetEnvironmentVariable("COUCHBASE_BUCKET") ?? "default"; + public void RetrieveAndUpdate() { var key = "SampleApp-" + DateTime.Now.Ticks; diff --git a/tracer/test/test-applications/integrations/Samples.Couchbase3/Program.cs b/tracer/test/test-applications/integrations/Samples.Couchbase3/Program.cs index c4dea968532e..e2b6bf03a04a 100644 --- a/tracer/test/test-applications/integrations/Samples.Couchbase3/Program.cs +++ b/tracer/test/test-applications/integrations/Samples.Couchbase3/Program.cs @@ -23,9 +23,9 @@ internal class Program private static async Task Main() { var options = new ClusterOptions() - .WithConnectionString("couchbase://" + Host()) - .WithCredentials(username: "default", password: "password") - .WithBuckets("default"); + .WithConnectionString("couchbase://" + ConnectionString()) + .WithCredentials(username: Username(), password: Password()) + .WithBuckets(BucketName()); ICluster cluster = null; @@ -43,15 +43,10 @@ private static async Task Main() } // get a bucket reference - var bucket = await cluster.BucketAsync("default"); + var bucket = await cluster.BucketAsync(BucketName()); - // get a user-defined collection reference -#if COUCHBASE_3_0 + // get the default collection reference var collection = bucket.DefaultCollection(); -#else - var scope = await bucket.ScopeAsync("tenant_agent_00"); - var collection = await scope.CollectionAsync("users"); -#endif // Upsert Document var upsertResult = await collection.UpsertAsync("my-document-key", new { Name = "Ted", Age = 31 }); @@ -83,9 +78,12 @@ private static async Task Main() return 0; } - private static string Host() - { - return Environment.GetEnvironmentVariable("COUCHBASE_HOST") ?? "localhost"; - } + private static string ConnectionString() => Environment.GetEnvironmentVariable("COUCHBASE_CONNECTION_STRING") ?? "localhost"; + + private static string Username() => Environment.GetEnvironmentVariable("COUCHBASE_USERNAME") ?? "default"; + + private static string Password() => Environment.GetEnvironmentVariable("COUCHBASE_PASSWORD") ?? "password"; + + private static string BucketName() => Environment.GetEnvironmentVariable("COUCHBASE_BUCKET") ?? "default"; } } From 78e434c39b5502e2687b99b3eb317e3f852b4b1a Mon Sep 17 00:00:00 2001 From: Steven Bouwkamp Date: Wed, 29 Jul 2026 11:03:50 -0400 Subject: [PATCH 12/28] Remove obsolete Group 1 Compose endpoints --- docker-compose.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index dd798097748d..a3260c7faa9e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -303,11 +303,8 @@ services: - ELASTICSEARCH7_HOST=elasticsearch7:9200 - ELASTICSEARCH6_HOST=elasticsearch6:9200 - ELASTICSEARCH5_HOST=elasticsearch5:9200 - - KAFKA_BROKER_HOST=kafka-broker:29092 - AWS_SDK_HOST=localstack:4566 - ORACLE_HOST=oracle - - COUCHBASE_HOST=couchbase - - COUCHBASE_PORT=8091 - ASB_CONNECTION_STRING=Endpoint=sb://azureservicebus-emulator:5672;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true; - EVENTHUBS_CONNECTION_STRING=Endpoint=sb://azure-eventhubs-emulator:5672;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true; - COSMOSDB_ENDPOINT=https://cosmosdb-emulator:8081 From dc1c0039f7c07f2443d978fef8ee331aec10189a Mon Sep 17 00:00:00 2001 From: Steven Bouwkamp Date: Thu, 30 Jul 2026 08:55:25 -0400 Subject: [PATCH 13/28] Avoid Redis Testcontainers on Windows --- .../Containers/StackExchangeRedisFixture.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/StackExchangeRedisFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/StackExchangeRedisFixture.cs index bed5715ae2e9..4b4fc603ecca 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/StackExchangeRedisFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/StackExchangeRedisFixture.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; +using System.Runtime.InteropServices; using System.Threading.Tasks; using DotNet.Testcontainers.Builders; using DotNet.Testcontainers.Containers; @@ -42,6 +43,13 @@ public override IEnumerable> GetEnvironmentVariable protected override async Task InitializeResources(Action registerResource) { + // The Redis smoke tests in this collection are skipped on Windows, where CI does not provide Docker. + // Collection fixtures are initialized before test-level skips are evaluated, so avoid starting containers here. + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return; + } + // Keep synchronized with the image version in docker-compose.yml. const string image = "redis:4-alpine"; From 8c9258431863f36d8c5bf0ee7cf0d39b49d6dde5 Mon Sep 17 00:00:00 2001 From: Steven Bouwkamp Date: Thu, 30 Jul 2026 09:46:25 -0400 Subject: [PATCH 14/28] Avoid RabbitMQ readiness cookie race --- .../Containers/RabbitMqFixture.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/RabbitMqFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/RabbitMqFixture.cs index 6c55a4cf81ee..e29bfc5b6d42 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/RabbitMqFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/RabbitMqFixture.cs @@ -32,9 +32,10 @@ public override IEnumerable> GetEnvironmentVariable protected override async Task InitializeResources(Action registerResource) { + // rabbitmq-diagnostics can race startup and create a root-owned Erlang cookie. var container = new ContainerBuilder(Image) .WithPortBinding(RabbitMqPort, true) - .WithWaitStrategy(Wait.ForUnixContainer().UntilCommandIsCompleted("rabbitmq-diagnostics", "-q", "ping")) + .WithWaitStrategy(Wait.ForUnixContainer().UntilInternalTcpPortIsAvailable(RabbitMqPort)) .Build(); await container.StartAsync().ConfigureAwait(false); From caef5b090898af1d477e8799b8e6ff1990e6f3d9 Mon Sep 17 00:00:00 2001 From: Steven Bouwkamp Date: Thu, 30 Jul 2026 10:10:50 -0400 Subject: [PATCH 15/28] Support net48 in Couchbase fixture --- .../Containers/CouchbaseFixture.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/CouchbaseFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/CouchbaseFixture.cs index a1725164ee8e..8056c80f0be8 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/CouchbaseFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/CouchbaseFixture.cs @@ -122,7 +122,7 @@ await PutFormAsync( await WaitForSuccessAsync( client, "/pools/default/buckets/default", - content => content.Contains(@"""status"":""healthy""", StringComparison.OrdinalIgnoreCase)).ConfigureAwait(false); + content => content.IndexOf(@"""status"":""healthy""", StringComparison.OrdinalIgnoreCase) >= 0).ConfigureAwait(false); using var queryClient = new HttpClient { @@ -134,7 +134,7 @@ await WaitForSuccessAsync( await WaitForSuccessAsync( client, "/pools/default/nodeServices", - content => content.Contains(@"""n1ql"":8093", StringComparison.OrdinalIgnoreCase)).ConfigureAwait(false); + content => content.IndexOf(@"""n1ql"":8093", StringComparison.OrdinalIgnoreCase) >= 0).ConfigureAwait(false); } private static Task PostFormAsync(HttpClient client, string path, Dictionary form) From 8634923be4a3dcf797eabcb973e079a4a8ab689d Mon Sep 17 00:00:00 2001 From: Steven Bouwkamp Date: Thu, 30 Jul 2026 10:55:19 -0400 Subject: [PATCH 16/28] Preserve Windows integration test endpoints --- tracer/build/_build/Build.Steps.cs | 6 +- .../Containers/SqlServerFixture.cs | 38 +++++-- .../Containers/StackExchangeRedisFixture.cs | 100 ++++++++++++++++-- 3 files changed, 123 insertions(+), 21 deletions(-) diff --git a/tracer/build/_build/Build.Steps.cs b/tracer/build/_build/Build.Steps.cs index 0dfdb38ae643..7e69a341e168 100644 --- a/tracer/build/_build/Build.Steps.cs +++ b/tracer/build/_build/Build.Steps.cs @@ -1867,7 +1867,7 @@ void CompileSamplesThatDependOnDatadogTrace() // Don't apply a custom filter to these tests, they should all be able to be run .When(!string.IsNullOrWhiteSpace(AddAreaFilter(Filter)), c => c.SetFilter(AddAreaFilter(Filter))) .When(TestAllPackageVersions, o => o.SetProcessEnvironmentVariable("TestAllPackageVersions", "true")) - .When(IsWin, o => o.SetProcessEnvironmentVariable("SQLSERVER_CONNECTION_STRING", @"Server=(localdb)\MSSQLLocalDB;Integrated Security=true;Connection Timeout=60")) + .When(IsWin && string.IsNullOrEmpty(Environment.GetEnvironmentVariable("SQLSERVER_CONNECTION_STRING")), o => o.SetProcessEnvironmentVariable("SQLSERVER_CONNECTION_STRING", @"Server=(localdb)\MSSQLLocalDB;Integrated Security=true;Connection Timeout=60")) .When(CodeCoverageEnabled, ConfigureCodeCoverage) .CombineWith(parallelJobs, (s, project) => s .EnableTrxLogOutput(GetResultsDirectory(project)) @@ -1890,7 +1890,7 @@ void CompileSamplesThatDependOnDatadogTrace() .SetLogsDirectory(TestLogsDirectory) .When(!string.IsNullOrWhiteSpace(filter), c => c.SetFilter(filter)) .When(TestAllPackageVersions, o => o.SetProcessEnvironmentVariable("TestAllPackageVersions", "true")) - .When(IsWin, o => o.SetProcessEnvironmentVariable("SQLSERVER_CONNECTION_STRING", @"Server=(localdb)\MSSQLLocalDB;Integrated Security=true;Connection Timeout=60")) + .When(IsWin && string.IsNullOrEmpty(Environment.GetEnvironmentVariable("SQLSERVER_CONNECTION_STRING")), o => o.SetProcessEnvironmentVariable("SQLSERVER_CONNECTION_STRING", @"Server=(localdb)\MSSQLLocalDB;Integrated Security=true;Connection Timeout=60")) .When(CodeCoverageEnabled, ConfigureCodeCoverage) .CombineWith(ClrProfilerIntegrationTests, (s, project) => s .EnableTrxLogOutput(GetResultsDirectory(project)) @@ -2039,7 +2039,7 @@ private string AddAreaFilter(string filter) .SetIsDebugRun(isDebugRun) .SetProcessEnvironmentVariable("MonitoringHomeDirectory", MonitoringHomeDirectory) .SetLogsDirectory(TestLogsDirectory) - .SetProcessEnvironmentVariable("SQLSERVER_CONNECTION_STRING", @"Server=(localdb)\MSSQLLocalDB;Integrated Security=true;Connection Timeout=60") + .When(IsWin && string.IsNullOrEmpty(Environment.GetEnvironmentVariable("SQLSERVER_CONNECTION_STRING")), o => o.SetProcessEnvironmentVariable("SQLSERVER_CONNECTION_STRING", @"Server=(localdb)\MSSQLLocalDB;Integrated Security=true;Connection Timeout=60")) .When(CodeCoverageEnabled, ConfigureCodeCoverage) .CombineWith(ClrProfilerIntegrationTests, (s, project) => s .EnableTrxLogOutput(GetResultsDirectory(project)) diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/SqlServerFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/SqlServerFixture.cs index 9e4910129fdc..208fe581b1b2 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/SqlServerFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/SqlServerFixture.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; +using System.Data.Common; using System.Runtime.InteropServices; using System.Threading.Tasks; using DotNet.Testcontainers.Builders; @@ -21,22 +22,30 @@ public class SqlServerFixture : ContainerFixture private const string SqlServerImage = "mcr.microsoft.com/mssql/server:latest@sha256:2cd0aec4a3bfc3cf9205bed3f7922f4c6208f7c767dc62edcee308d0fd7d56d0"; private const string AzureSqlEdgeImage = "mcr.microsoft.com/azure-sql-edge:latest@sha256:902628a8be89e35dfb7895ca31d602974c7bafde4d583a0d0873844feb1c42cf"; - private IContainer? _container; + private string? _connectionString; - public string? HostAndPort => _container is null ? null : $"{_container.Hostname},{_container.GetMappedPublicPort(SqlServerPort)}"; + public string? HostAndPort { get; private set; } public override IEnumerable> GetEnvironmentVariables() { - if (HostAndPort is { } hostAndPort) + if (_connectionString is not null) { - yield return new("SQLSERVER_CONNECTION_STRING", $"Server={hostAndPort};User=sa;Password={Password};TrustServerCertificate=True"); + yield return new("SQLSERVER_CONNECTION_STRING", _connectionString); } } protected override async Task InitializeResources(Action registerResource) { - // Windows CI provides LocalDB, and callers can explicitly select another existing SQL Server. - if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("SQLSERVER_CONNECTION_STRING"))) + var connectionString = Environment.GetEnvironmentVariable("SQLSERVER_CONNECTION_STRING"); + if (!string.IsNullOrEmpty(connectionString)) + { + _connectionString = connectionString; + HostAndPort = GetHost(connectionString); + return; + } + + // Windows provides LocalDB. + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return; } @@ -52,7 +61,22 @@ protected override async Task InitializeResources(Action registe await container.StartAsync().ConfigureAwait(false); - _container = container; + HostAndPort = $"{container.Hostname},{container.GetMappedPublicPort(SqlServerPort)}"; + _connectionString = $"Server={HostAndPort};User=sa;Password={Password};TrustServerCertificate=True"; registerResource("container", container); } + + private static string? GetHost(string connectionString) + { + var builder = new DbConnectionStringBuilder { ConnectionString = connectionString }; + foreach (var name in new[] { "Server", "Data Source", "DataSource", "Network Address", "NetworkAddress", "Address", "Addr", "Host", "Hostname", "Host Name" }) + { + if (builder.TryGetValue(name, out var value) && value is string host) + { + return host; + } + } + + return null; + } } diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/StackExchangeRedisFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/StackExchangeRedisFixture.cs index 4b4fc603ecca..7a16dad2baa2 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/StackExchangeRedisFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/StackExchangeRedisFixture.cs @@ -19,34 +19,44 @@ public class StackExchangeRedisFixture : ContainerFixture { private const int RedisPort = 6379; private const string PrimaryAlias = "stackexchangeredis"; + private const string DefaultHostConfiguration = "localhost:6379,localhost:6380"; - private Resources? _resources; + private Endpoint? _primaryEndpoint; + private Endpoint? _replicaEndpoint; + private string? _hostConfiguration; + private string? _singleHostConfiguration; - public string PrimaryHost => _resources!.PrimaryContainer.Hostname; + public string PrimaryHost => _primaryEndpoint!.Host; - public ushort PrimaryPort => _resources!.PrimaryContainer.GetMappedPublicPort(RedisPort); + public ushort PrimaryPort => _primaryEndpoint!.Port; - public string ReplicaHost => _resources!.ReplicaContainer.Hostname; + public string ReplicaHost => _replicaEndpoint!.Host; - public ushort ReplicaPort => _resources!.ReplicaContainer.GetMappedPublicPort(RedisPort); + public ushort ReplicaPort => _replicaEndpoint!.Port; public override IEnumerable> GetEnvironmentVariables() { - if (_resources is null) + if (_hostConfiguration is null || _singleHostConfiguration is null) { yield break; } - yield return new("STACKEXCHANGE_REDIS_HOST", $"{PrimaryHost}:{PrimaryPort},{ReplicaHost}:{ReplicaPort}"); - yield return new("STACKEXCHANGE_REDIS_SINGLE_HOST", $"{_resources.SingleContainer.Hostname}:{_resources.SingleContainer.GetMappedPublicPort(RedisPort)}"); + yield return new("STACKEXCHANGE_REDIS_HOST", _hostConfiguration); + yield return new("STACKEXCHANGE_REDIS_SINGLE_HOST", _singleHostConfiguration); } protected override async Task InitializeResources(Action registerResource) { - // The Redis smoke tests in this collection are skipped on Windows, where CI does not provide Docker. - // Collection fixtures are initialized before test-level skips are evaluated, so avoid starting containers here. + // Windows tests use an existing Redis instance because CI does not provide Docker. if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { + var hostConfiguration = Environment.GetEnvironmentVariable("STACKEXCHANGE_REDIS_HOST"); + _hostConfiguration = string.IsNullOrEmpty(hostConfiguration) ? DefaultHostConfiguration : hostConfiguration; + var endpoints = ParseEndpoints(_hostConfiguration); + _primaryEndpoint = endpoints[0]; + _replicaEndpoint = endpoints.Count > 1 ? endpoints[1] : endpoints[0]; + var singleHostConfiguration = Environment.GetEnvironmentVariable("STACKEXCHANGE_REDIS_SINGLE_HOST"); + _singleHostConfiguration = string.IsNullOrEmpty(singleHostConfiguration) ? endpoints[0].ToString() : singleHostConfiguration; return; } @@ -90,10 +100,78 @@ protected override async Task InitializeResources(Action registe throw; } - _resources = resources; + _primaryEndpoint = new Endpoint(primaryContainer.Hostname, primaryContainer.GetMappedPublicPort(RedisPort)); + _replicaEndpoint = new Endpoint(replicaContainer.Hostname, replicaContainer.GetMappedPublicPort(RedisPort)); + _hostConfiguration = $"{_primaryEndpoint},{_replicaEndpoint}"; + _singleHostConfiguration = $"{singleContainer.Hostname}:{singleContainer.GetMappedPublicPort(RedisPort)}"; registerResource("resources", resources); } + private static List ParseEndpoints(string configuration) + { + var endpoints = new List(); + foreach (var value in configuration.Split(',')) + { + var trimmedValue = value.Trim(); + if (trimmedValue.Length == 0 || trimmedValue.Contains("=")) + { + continue; + } + + endpoints.Add(Endpoint.Parse(trimmedValue)); + if (endpoints.Count == 2) + { + break; + } + } + + if (endpoints.Count == 0) + { + throw new InvalidOperationException("STACKEXCHANGE_REDIS_HOST must contain at least one Redis endpoint."); + } + + return endpoints; + } + + private sealed class Endpoint + { + public Endpoint(string host, ushort port) + { + Host = host; + Port = port; + } + + public string Host { get; } + + public ushort Port { get; } + + public static Endpoint Parse(string value) + { + if (value[0] == '[') + { + var closingBracketIndex = value.IndexOf(']'); + if (closingBracketIndex > 0) + { + var host = value.Substring(1, closingBracketIndex - 1); + var portValue = value.Substring(closingBracketIndex + 1).TrimStart(':'); + return new Endpoint(host, ParsePort(portValue)); + } + } + + var separatorIndex = value.LastIndexOf(':'); + if (separatorIndex > 0 && ushort.TryParse(value.Substring(separatorIndex + 1), out var port)) + { + return new Endpoint(value.Substring(0, separatorIndex), port); + } + + return new Endpoint(value, RedisPort); + } + + public override string ToString() => Host.IndexOf(':') >= 0 ? $"[{Host}]:{Port}" : $"{Host}:{Port}"; + + private static ushort ParsePort(string value) => ushort.TryParse(value, out var port) ? port : RedisPort; + } + private sealed class Resources : IAsyncDisposable { public Resources(IContainer primaryContainer, IContainer replicaContainer, IContainer singleContainer, INetwork network) From eefed40478df94494cbc0ba00af232b9124e7d67 Mon Sep 17 00:00:00 2001 From: Steven Bouwkamp Date: Thu, 30 Jul 2026 11:58:17 -0400 Subject: [PATCH 17/28] Fix container fixture startup in Docker CI --- .../Containers/CouchbaseFixture.cs | 112 ++++++++++++------ .../Containers/MySql57Fixture.cs | 7 +- .../Containers/MySql8Fixture.cs | 7 +- .../Containers/StackExchangeRedisFixture.cs | 2 +- 4 files changed, 86 insertions(+), 42 deletions(-) diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/CouchbaseFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/CouchbaseFixture.cs index 8056c80f0be8..bd25040b35f3 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/CouchbaseFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/CouchbaseFixture.cs @@ -10,6 +10,7 @@ using System.Linq; using System.Net.Http; using System.Net.Http.Headers; +using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using DotNet.Testcontainers.Builders; @@ -19,30 +20,32 @@ namespace Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; public class CouchbaseFixture : ContainerFixture { - private const int ManagementPort = 8091; - private const int ManagementSslPort = 18091; - private const int ViewPort = 8092; - private const int ViewSslPort = 18092; - private const int QueryPort = 8093; - private const int QuerySslPort = 18093; - private const int KeyValuePort = 11210; - private const int KeyValueSslPort = 11207; + private const ushort ManagementPort = 8091; + private const ushort ManagementSslPort = 18091; + private const ushort ViewPort = 8092; + private const ushort ViewSslPort = 18092; + private const ushort QueryPort = 8093; + private const ushort QuerySslPort = 18093; + private const ushort KeyValuePort = 11210; + private const ushort KeyValueSslPort = 11207; private const string Image = "couchbase:community-6.6.0@sha256:43103efdd4b562366c7a48afa977c4ad148e09c877da28a83a86b2c5ee2daa97"; private const string AdministratorUsername = "Administrator"; private const string Username = "default"; private const string Password = "password"; - public string Host => Container.Hostname; + private string? _host; + private ushort _managementPort; + private ushort _keyValuePort; - public string BucketName => "default"; + public string Host => _host!; - private IContainer Container => GetResource("container"); + public string BucketName => "default"; public override IEnumerable> GetEnvironmentVariables() { yield return new("COUCHBASE_HOST", Host); - yield return new("COUCHBASE_PORT", Container.GetMappedPublicPort(ManagementPort).ToString()); - yield return new("COUCHBASE_CONNECTION_STRING", $"{Host}:{Container.GetMappedPublicPort(KeyValuePort)}"); + yield return new("COUCHBASE_PORT", _managementPort.ToString()); + yield return new("COUCHBASE_CONNECTION_STRING", $"{Host}:{_keyValuePort}"); yield return new("COUCHBASE_USERNAME", Username); yield return new("COUCHBASE_PASSWORD", Password); yield return new("COUCHBASE_BUCKET", BucketName); @@ -50,23 +53,45 @@ public override IEnumerable> GetEnvironmentVariable protected override async Task InitializeResources(Action registerResource) { - var container = new ContainerBuilder(Image) - // SDK 2.4 predates Couchbase's external-network selection, so it requires the advertised standard ports. - .WithPortBinding(ManagementPort, false) - .WithPortBinding(ManagementSslPort, false) - .WithPortBinding(ViewPort, false) - .WithPortBinding(ViewSslPort, false) - .WithPortBinding(QueryPort, false) - .WithPortBinding(QuerySslPort, false) - .WithPortBinding(KeyValuePort, false) - .WithPortBinding(KeyValueSslPort, false) - .WithWaitStrategy(Wait.ForUnixContainer().UntilInternalTcpPortIsAvailable(ManagementPort)) - .Build(); + // When these tests run in the outer Compose container, Testcontainers reaches published ports through the + // Docker host. Couchbase also binds its configured node hostname, so it must share the host network in that + // scenario. Otherwise, trying to configure the Docker host address as the node hostname fails with eaddrnotavail. + var useHostNetwork = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) + && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CONTAINER_HOSTNAME")); + var builder = new ContainerBuilder(Image) + .WithWaitStrategy(Wait.ForUnixContainer().UntilInternalTcpPortIsAvailable(ManagementPort)); + + if (useHostNetwork) + { + builder = builder.WithNetwork("host"); + } + else + { + // SDK 2.4 predates Couchbase's external-network selection, so it requires the advertised standard ports. + builder = builder.WithPortBinding(ManagementPort, false) + .WithPortBinding(ManagementSslPort, false) + .WithPortBinding(ViewPort, false) + .WithPortBinding(ViewSslPort, false) + .WithPortBinding(QueryPort, false) + .WithPortBinding(QuerySslPort, false) + .WithPortBinding(KeyValuePort, false) + .WithPortBinding(KeyValueSslPort, false); + } + + var container = builder.Build(); try { await container.StartAsync().ConfigureAwait(false); - await ConfigureCouchbaseAsync(container).ConfigureAwait(false); + var host = container.Hostname; + var managementPort = useHostNetwork ? ManagementPort : container.GetMappedPublicPort(ManagementPort); + var keyValuePort = useHostNetwork ? KeyValuePort : container.GetMappedPublicPort(KeyValuePort); + + await ConfigureCouchbaseAsync(container, host, useHostNetwork).ConfigureAwait(false); + + _host = host; + _managementPort = managementPort; + _keyValuePort = keyValuePort; } catch { @@ -77,16 +102,25 @@ protected override async Task InitializeResources(Action registe registerResource("container", container); } - private static async Task ConfigureCouchbaseAsync(IContainer container) + private static async Task ConfigureCouchbaseAsync(IContainer container, string host, bool useHostNetwork) { + var managementPort = useHostNetwork ? ManagementPort : container.GetMappedPublicPort(ManagementPort); + var managementSslPort = useHostNetwork ? ManagementSslPort : container.GetMappedPublicPort(ManagementSslPort); + var viewPort = useHostNetwork ? ViewPort : container.GetMappedPublicPort(ViewPort); + var viewSslPort = useHostNetwork ? ViewSslPort : container.GetMappedPublicPort(ViewSslPort); + var queryPort = useHostNetwork ? QueryPort : container.GetMappedPublicPort(QueryPort); + var querySslPort = useHostNetwork ? QuerySslPort : container.GetMappedPublicPort(QuerySslPort); + var keyValuePort = useHostNetwork ? KeyValuePort : container.GetMappedPublicPort(KeyValuePort); + var keyValueSslPort = useHostNetwork ? KeyValueSslPort : container.GetMappedPublicPort(KeyValueSslPort); + using var client = new HttpClient { - BaseAddress = new UriBuilder(Uri.UriSchemeHttp, container.Hostname, container.GetMappedPublicPort(ManagementPort)).Uri, + BaseAddress = new UriBuilder(Uri.UriSchemeHttp, host, managementPort).Uri, Timeout = TimeSpan.FromSeconds(5), }; await WaitForSuccessAsync(client, "/pools").ConfigureAwait(false); - await PostFormAsync(client, "/node/controller/rename", new() { ["hostname"] = container.Hostname }).ConfigureAwait(false); + await PostFormAsync(client, "/node/controller/rename", new() { ["hostname"] = host }).ConfigureAwait(false); await PostFormAsync(client, "/node/controller/setupServices", new() { ["services"] = "kv,index,n1ql" }).ConfigureAwait(false); await PostFormAsync(client, "/pools/default", new() { ["memoryQuota"] = "256", ["indexMemoryQuota"] = "256" }).ConfigureAwait(false); await PutFormAsync( @@ -94,15 +128,15 @@ await PutFormAsync( "/node/controller/setupAlternateAddresses/external", new() { - ["hostname"] = container.Hostname, - ["mgmt"] = container.GetMappedPublicPort(ManagementPort).ToString(), - ["mgmtSSL"] = container.GetMappedPublicPort(ManagementSslPort).ToString(), - ["kv"] = container.GetMappedPublicPort(KeyValuePort).ToString(), - ["kvSSL"] = container.GetMappedPublicPort(KeyValueSslPort).ToString(), - ["capi"] = container.GetMappedPublicPort(ViewPort).ToString(), - ["capiSSL"] = container.GetMappedPublicPort(ViewSslPort).ToString(), - ["n1ql"] = container.GetMappedPublicPort(QueryPort).ToString(), - ["n1qlSSL"] = container.GetMappedPublicPort(QuerySslPort).ToString(), + ["hostname"] = host, + ["mgmt"] = managementPort.ToString(), + ["mgmtSSL"] = managementSslPort.ToString(), + ["kv"] = keyValuePort.ToString(), + ["kvSSL"] = keyValueSslPort.ToString(), + ["capi"] = viewPort.ToString(), + ["capiSSL"] = viewSslPort.ToString(), + ["n1ql"] = queryPort.ToString(), + ["n1qlSSL"] = querySslPort.ToString(), }).ConfigureAwait(false); await PostFormAsync( client, @@ -126,7 +160,7 @@ await WaitForSuccessAsync( using var queryClient = new HttpClient { - BaseAddress = new UriBuilder(Uri.UriSchemeHttp, container.Hostname, container.GetMappedPublicPort(QueryPort)).Uri, + BaseAddress = new UriBuilder(Uri.UriSchemeHttp, host, queryPort).Uri, Timeout = TimeSpan.FromSeconds(5), }; queryClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials); diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql57Fixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql57Fixture.cs index d38278d48f8f..b4b173c3a9c6 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql57Fixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql57Fixture.cs @@ -51,7 +51,12 @@ protected override async Task InitializeResources(Action registe .WithEnvironment("MYSQL_ROOT_PASSWORD", Password) .WithEnvironment("MYSQL_USER", Username) .WithEnvironment("MYSQL_PASSWORD", Password) - .WithWaitStrategy(Wait.ForUnixContainer().UntilCommandIsCompleted("mysqladmin", "ping", "--silent", "-h", "localhost", "-u", "root", $"-p{Password}")) + .WithWaitStrategy( + Wait.ForUnixContainer() + // mysqladmin can reach the temporary server used by the image's initialization script. + // Wait for that phase to finish before probing the final server. + .UntilMessageIsLogged("MySQL init process done. Ready for start up.") + .UntilCommandIsCompleted("mysqladmin", "ping", "--silent", "-h", "localhost", "-u", "root", $"-p{Password}")) .Build(); await container.StartAsync().ConfigureAwait(false); diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql8Fixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql8Fixture.cs index 961e2ce05d30..441fb8afcfa8 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql8Fixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql8Fixture.cs @@ -41,7 +41,12 @@ protected override async Task InitializeResources(Action registe .WithEnvironment("MYSQL_ROOT_PASSWORD", Password) .WithEnvironment("MYSQL_USER", Username) .WithEnvironment("MYSQL_PASSWORD", Password) - .WithWaitStrategy(Wait.ForUnixContainer().UntilCommandIsCompleted("mysqladmin", "ping", "--silent", "-h", "localhost", "-u", "root", $"-p{Password}")) + .WithWaitStrategy( + Wait.ForUnixContainer() + // mysqladmin can reach the temporary server used by the image's initialization script. + // Wait for that phase to finish before probing the final server. + .UntilMessageIsLogged("MySQL init process done. Ready for start up.") + .UntilCommandIsCompleted("mysqladmin", "ping", "--silent", "-h", "localhost", "-u", "root", $"-p{Password}")) .Build(); await container.StartAsync().ConfigureAwait(false); diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/StackExchangeRedisFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/StackExchangeRedisFixture.cs index 7a16dad2baa2..7f6758052e79 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/StackExchangeRedisFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/StackExchangeRedisFixture.cs @@ -169,7 +169,7 @@ public static Endpoint Parse(string value) public override string ToString() => Host.IndexOf(':') >= 0 ? $"[{Host}]:{Port}" : $"{Host}:{Port}"; - private static ushort ParsePort(string value) => ushort.TryParse(value, out var port) ? port : RedisPort; + private static ushort ParsePort(string value) => ushort.TryParse(value, out var port) ? port : (ushort)RedisPort; } private sealed class Resources : IAsyncDisposable From 43bbce17147eee2d783d18108bf5d83793069cd0 Mon Sep 17 00:00:00 2001 From: Steven Bouwkamp Date: Thu, 30 Jul 2026 13:52:22 -0400 Subject: [PATCH 18/28] Fix Couchbase and SQL Server test containers --- .../Couchbase3Tests.cs | 2 +- .../Containers/CouchbaseFixture.cs | 23 +++++- .../Containers/SqlServerFixture.cs | 10 +-- ...ace.TestHelpers.AutoInstrumentation.csproj | 1 + .../Couchbase3Tests.SchemaV0.verified.txt | 75 +++++++++++++++--- .../Couchbase3Tests.SchemaV1.verified.txt | 77 ++++++++++++++++--- 6 files changed, 157 insertions(+), 31 deletions(-) diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Couchbase3Tests.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Couchbase3Tests.cs index 5d8082424d56..784220183596 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Couchbase3Tests.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Couchbase3Tests.cs @@ -83,7 +83,7 @@ public async Task SubmitTraces(string packageVersion, string metadataSchemaVersi settings.AddSimpleScrubber("peer.service: localhost", "peer.service: couchbase"); settings.AddRegexScrubber(new Regex($@"db.couchbase.seed.nodes: {Regex.Escape(_couchbaseFixture.Host)}(?::[0-9]+)?"), "db.couchbase.seed.nodes: couchbase"); settings.AddSimpleScrubber($"out.host: {_couchbaseFixture.Host}", "out.host: couchbase"); - settings.AddSimpleScrubber($"peer.service: {_couchbaseFixture.Host}", "peer.service: couchbase"); + settings.AddRegexScrubber(new Regex($@"peer.service: {Regex.Escape(_couchbaseFixture.Host)}(?::[0-9]+)?"), "peer.service: couchbase"); settings.AddSimpleScrubber($"couchbase.operation.bucket: {_couchbaseFixture.BucketName}", "couchbase.operation.bucket: default"); // theres' a fair amount less in 3.0.7 - fewer spans, different terminology etc diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/CouchbaseFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/CouchbaseFixture.cs index bd25040b35f3..aba70aca05dd 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/CouchbaseFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/CouchbaseFixture.cs @@ -8,11 +8,15 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Net.Http; using System.Net.Http.Headers; +using System.Net.NetworkInformation; +using System.Net.Sockets; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; +using Docker.DotNet.Models; using DotNet.Testcontainers.Builders; using DotNet.Testcontainers.Containers; @@ -58,12 +62,17 @@ protected override async Task InitializeResources(Action registe // scenario. Otherwise, trying to configure the Docker host address as the node hostname fails with eaddrnotavail. var useHostNetwork = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CONTAINER_HOSTNAME")); + var dockerHost = useHostNetwork ? GetDockerHost() : null; var builder = new ContainerBuilder(Image) .WithWaitStrategy(Wait.ForUnixContainer().UntilInternalTcpPortIsAvailable(ManagementPort)); if (useHostNetwork) { - builder = builder.WithNetwork("host"); + builder = builder.WithCreateParameterModifier(p => + { + p.HostConfig ??= new HostConfig(); + p.HostConfig.NetworkMode = "host"; + }); } else { @@ -83,7 +92,7 @@ protected override async Task InitializeResources(Action registe try { await container.StartAsync().ConfigureAwait(false); - var host = container.Hostname; + var host = dockerHost ?? container.Hostname; var managementPort = useHostNetwork ? ManagementPort : container.GetMappedPublicPort(ManagementPort); var keyValuePort = useHostNetwork ? KeyValuePort : container.GetMappedPublicPort(KeyValuePort); @@ -102,6 +111,14 @@ protected override async Task InitializeResources(Action registe registerResource("container", container); } + private static string GetDockerHost() + => NetworkInterface.GetAllNetworkInterfaces() + .SelectMany(networkInterface => networkInterface.GetIPProperties().GatewayAddresses) + .Select(gateway => gateway.Address) + .FirstOrDefault(address => address.AddressFamily == AddressFamily.InterNetwork + && !IPAddress.IsLoopback(address))?.ToString() + ?? throw new InvalidOperationException("Unable to determine the Docker host from the container's default gateway."); + private static async Task ConfigureCouchbaseAsync(IContainer container, string host, bool useHostNetwork) { var managementPort = useHostNetwork ? ManagementPort : container.GetMappedPublicPort(ManagementPort); @@ -183,7 +200,7 @@ private static async Task SendFormAsync(HttpClient client, HttpMethod method, st { Content = new FormUrlEncodedContent(form.Select(static pair => new KeyValuePair(pair.Key, pair.Value))), }; - using var response = await client.SendAsync(request).ConfigureAwait(false); + using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); if (!response.IsSuccessStatusCode) { diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/SqlServerFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/SqlServerFixture.cs index 208fe581b1b2..b357d738733c 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/SqlServerFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/SqlServerFixture.cs @@ -10,8 +10,7 @@ using System.Data.Common; using System.Runtime.InteropServices; using System.Threading.Tasks; -using DotNet.Testcontainers.Builders; -using DotNet.Testcontainers.Containers; +using Testcontainers.MsSql; namespace Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; @@ -52,11 +51,8 @@ protected override async Task InitializeResources(Action registe // mssql/server has no native arm64 image, so use Azure SQL Edge on arm64. var image = RuntimeInformation.ProcessArchitecture == Architecture.Arm64 ? AzureSqlEdgeImage : SqlServerImage; - var container = new ContainerBuilder(image) - .WithPortBinding(SqlServerPort, true) - .WithEnvironment("ACCEPT_EULA", "Y") - .WithEnvironment("MSSQL_SA_PASSWORD", Password) - .WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged("SQL Server is now ready for client connections")) + var container = new MsSqlBuilder(image) + .WithPassword(Password) .Build(); await container.StartAsync().ConfigureAwait(false); diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Datadog.Trace.TestHelpers.AutoInstrumentation.csproj b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Datadog.Trace.TestHelpers.AutoInstrumentation.csproj index 72e9b4c51e06..7c91046db9fa 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Datadog.Trace.TestHelpers.AutoInstrumentation.csproj +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Datadog.Trace.TestHelpers.AutoInstrumentation.csproj @@ -5,6 +5,7 @@ + diff --git a/tracer/test/snapshots/Couchbase3Tests.SchemaV0.verified.txt b/tracer/test/snapshots/Couchbase3Tests.SchemaV0.verified.txt index 7b2913dfa20a..6a4328036058 100644 --- a/tracer/test/snapshots/Couchbase3Tests.SchemaV0.verified.txt +++ b/tracer/test/snapshots/Couchbase3Tests.SchemaV0.verified.txt @@ -8,6 +8,7 @@ Type: db, Tags: { component: Couchbase, + couchbase.operation.bucket: default, couchbase.operation.code: Delete, couchbase.operation.key: my-document-key, db.couchbase.seed.nodes: couchbase, @@ -35,6 +36,7 @@ Error: 1, Tags: { component: Couchbase, + couchbase.operation.bucket: default, couchbase.operation.code: Delete, couchbase.operation.key: does-not-exist, db.couchbase.seed.nodes: couchbase, @@ -62,6 +64,7 @@ Type: db, Tags: { component: Couchbase, + couchbase.operation.bucket: default, couchbase.operation.code: Get, couchbase.operation.key: my-document-key, db.couchbase.seed.nodes: couchbase, @@ -187,13 +190,13 @@ TraceId: Id_15, SpanId: Id_16, Name: couchbase.query, - Resource: Hello, + Resource: NoOp, Service: Samples.Couchbase3-couchbase, Type: db, Tags: { component: Couchbase, - couchbase.operation.code: Hello, - couchbase.operation.key: obfuscated, + couchbase.operation.code: NoOp, + couchbase.operation.key: , db.couchbase.seed.nodes: couchbase, env: integration_tests, language: dotnet, @@ -213,13 +216,13 @@ TraceId: Id_17, SpanId: Id_18, Name: couchbase.query, - Resource: Hello, + Resource: NoOp, Service: Samples.Couchbase3-couchbase, Type: db, Tags: { component: Couchbase, - couchbase.operation.code: Hello, - couchbase.operation.key: obfuscated, + couchbase.operation.code: NoOp, + couchbase.operation.key: , db.couchbase.seed.nodes: couchbase, env: integration_tests, language: dotnet, @@ -239,6 +242,58 @@ TraceId: Id_19, SpanId: Id_20, Name: couchbase.query, + Resource: SaslList, + Service: Samples.Couchbase3-couchbase, + Type: db, + Tags: { + component: Couchbase, + couchbase.operation.code: SaslList, + couchbase.operation.key: , + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + language: dotnet, + runtime-id: Guid_1, + span.kind: client, + _dd.base_service: Samples.Couchbase3, + _dd.svc_src: couchbase + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + }, + { + TraceId: Id_21, + SpanId: Id_22, + Name: couchbase.query, + Resource: SaslList, + Service: Samples.Couchbase3-couchbase, + Type: db, + Tags: { + component: Couchbase, + couchbase.operation.code: SaslList, + couchbase.operation.key: , + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + language: dotnet, + runtime-id: Guid_1, + span.kind: client, + _dd.base_service: Samples.Couchbase3, + _dd.svc_src: couchbase + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + }, + { + TraceId: Id_23, + SpanId: Id_24, + Name: couchbase.query, Resource: SelectBucket, Service: Samples.Couchbase3-couchbase, Type: db, @@ -262,8 +317,8 @@ } }, { - TraceId: Id_21, - SpanId: Id_22, + TraceId: Id_25, + SpanId: Id_26, Name: couchbase.query, Resource: SelectBucket, Service: Samples.Couchbase3-couchbase, @@ -288,8 +343,8 @@ } }, { - TraceId: Id_23, - SpanId: Id_24, + TraceId: Id_27, + SpanId: Id_28, Name: couchbase.query, Resource: Set, Service: Samples.Couchbase3-couchbase, diff --git a/tracer/test/snapshots/Couchbase3Tests.SchemaV1.verified.txt b/tracer/test/snapshots/Couchbase3Tests.SchemaV1.verified.txt index e8408ab96e75..af9bae346fdc 100644 --- a/tracer/test/snapshots/Couchbase3Tests.SchemaV1.verified.txt +++ b/tracer/test/snapshots/Couchbase3Tests.SchemaV1.verified.txt @@ -8,6 +8,7 @@ Type: db, Tags: { component: Couchbase, + couchbase.operation.bucket: default, couchbase.operation.code: Delete, couchbase.operation.key: my-document-key, db.couchbase.seed.nodes: couchbase, @@ -36,6 +37,7 @@ Error: 1, Tags: { component: Couchbase, + couchbase.operation.bucket: default, couchbase.operation.code: Delete, couchbase.operation.key: does-not-exist, db.couchbase.seed.nodes: couchbase, @@ -64,6 +66,7 @@ Type: db, Tags: { component: Couchbase, + couchbase.operation.bucket: default, couchbase.operation.code: Get, couchbase.operation.key: my-document-key, db.couchbase.seed.nodes: couchbase, @@ -194,13 +197,13 @@ TraceId: Id_15, SpanId: Id_16, Name: couchbase.query, - Resource: Hello, + Resource: NoOp, Service: Samples.Couchbase3, Type: db, Tags: { component: Couchbase, - couchbase.operation.code: Hello, - couchbase.operation.key: obfuscated, + couchbase.operation.code: NoOp, + couchbase.operation.key: , db.couchbase.seed.nodes: couchbase, env: integration_tests, language: dotnet, @@ -221,13 +224,13 @@ TraceId: Id_17, SpanId: Id_18, Name: couchbase.query, - Resource: Hello, + Resource: NoOp, Service: Samples.Couchbase3, Type: db, Tags: { component: Couchbase, - couchbase.operation.code: Hello, - couchbase.operation.key: obfuscated, + couchbase.operation.code: NoOp, + couchbase.operation.key: , db.couchbase.seed.nodes: couchbase, env: integration_tests, language: dotnet, @@ -248,6 +251,60 @@ TraceId: Id_19, SpanId: Id_20, Name: couchbase.query, + Resource: SaslList, + Service: Samples.Couchbase3, + Type: db, + Tags: { + component: Couchbase, + couchbase.operation.code: SaslList, + couchbase.operation.key: , + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + language: dotnet, + peer.service: couchbase, + runtime-id: Guid_1, + span.kind: client, + version: 1.0.0, + _dd.peer.service.source: db.couchbase.seed.nodes + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + }, + { + TraceId: Id_21, + SpanId: Id_22, + Name: couchbase.query, + Resource: SaslList, + Service: Samples.Couchbase3, + Type: db, + Tags: { + component: Couchbase, + couchbase.operation.code: SaslList, + couchbase.operation.key: , + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + language: dotnet, + peer.service: couchbase, + runtime-id: Guid_1, + span.kind: client, + version: 1.0.0, + _dd.peer.service.source: db.couchbase.seed.nodes + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + }, + { + TraceId: Id_23, + SpanId: Id_24, + Name: couchbase.query, Resource: SelectBucket, Service: Samples.Couchbase3, Type: db, @@ -272,8 +329,8 @@ } }, { - TraceId: Id_21, - SpanId: Id_22, + TraceId: Id_25, + SpanId: Id_26, Name: couchbase.query, Resource: SelectBucket, Service: Samples.Couchbase3, @@ -299,8 +356,8 @@ } }, { - TraceId: Id_23, - SpanId: Id_24, + TraceId: Id_27, + SpanId: Id_28, Name: couchbase.query, Resource: Set, Service: Samples.Couchbase3, From faf36c55aec77effcbf21edccb259de9c1164ff8 Mon Sep 17 00:00:00 2001 From: Steven Bouwkamp Date: Thu, 30 Jul 2026 14:14:10 -0400 Subject: [PATCH 19/28] Migrate IAST database tests to Testcontainers --- .../IAST/AspNetCore5IastDbTests.cs | 10 +++++++++- .../IAST/AspNetCore5IastDbTestsCollection.cs | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 tracer/test/Datadog.Trace.Security.IntegrationTests/IAST/AspNetCore5IastDbTestsCollection.cs diff --git a/tracer/test/Datadog.Trace.Security.IntegrationTests/IAST/AspNetCore5IastDbTests.cs b/tracer/test/Datadog.Trace.Security.IntegrationTests/IAST/AspNetCore5IastDbTests.cs index 9db1428b3569..aef4aa4e4916 100644 --- a/tracer/test/Datadog.Trace.Security.IntegrationTests/IAST/AspNetCore5IastDbTests.cs +++ b/tracer/test/Datadog.Trace.Security.IntegrationTests/IAST/AspNetCore5IastDbTests.cs @@ -10,17 +10,25 @@ using System.Threading.Tasks; using Datadog.Trace.Security.IntegrationTests.Iast; using Datadog.Trace.TestHelpers; +using Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; using Xunit; using Xunit.Abstractions; namespace Datadog.Trace.Security.IntegrationTests.IAST; [Trait("RequiresDockerDependency", "true")] +[Collection(AspNetCore5IastDbTestsCollection.Name)] public class AspNetCore5IastDbTests : AspNetCore5IastTests { - public AspNetCore5IastDbTests(AspNetCoreTestFixture fixture, ITestOutputHelper outputHelper) + public AspNetCore5IastDbTests( + AspNetCoreTestFixture fixture, + ITestOutputHelper outputHelper, + SqlServerFixture sqlServerFixture, + PostgresFixture postgresFixture, + MySql8Fixture mySqlFixture) : base(fixture, outputHelper, enableIast: true, testName: "AspNetCore5IastDbTestsIastEnabled", samplingRate: 100, vulnerabilitiesPerRequest: 200, isIastDeduplicationEnabled: false, sampleName: "AspNetCore5") { + ConfigureContainers(sqlServerFixture, postgresFixture, mySqlFixture); } [SkippableTheory] diff --git a/tracer/test/Datadog.Trace.Security.IntegrationTests/IAST/AspNetCore5IastDbTestsCollection.cs b/tracer/test/Datadog.Trace.Security.IntegrationTests/IAST/AspNetCore5IastDbTestsCollection.cs new file mode 100644 index 000000000000..2689eeb13f23 --- /dev/null +++ b/tracer/test/Datadog.Trace.Security.IntegrationTests/IAST/AspNetCore5IastDbTestsCollection.cs @@ -0,0 +1,19 @@ +// +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. +// + +#if NETCOREAPP3_0_OR_GREATER + +using Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; +using Xunit; + +namespace Datadog.Trace.Security.IntegrationTests.IAST; + +[CollectionDefinition(Name, DisableParallelization = true)] +public class AspNetCore5IastDbTestsCollection : ICollectionFixture, ICollectionFixture, ICollectionFixture +{ + public const string Name = "AspNetCore5IastDbTests"; +} + +#endif From 4927bde8ed8a34c0d769548391abc3eb61438995 Mon Sep 17 00:00:00 2001 From: Steven Bouwkamp Date: Thu, 30 Jul 2026 14:14:29 -0400 Subject: [PATCH 20/28] Pin Redis Testcontainer images --- .../Containers/ServiceStackRedisFixture.cs | 2 +- .../Containers/StackExchangeRedisFixture.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ServiceStackRedisFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ServiceStackRedisFixture.cs index e0357c68097b..788080fe2d18 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ServiceStackRedisFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ServiceStackRedisFixture.cs @@ -27,7 +27,7 @@ public override IEnumerable> GetEnvironmentVariable protected override async Task InitializeResources(Action registerResource) { // Keep synchronized with the image version in docker-compose.yml. - var container = new ContainerBuilder("redis:4-alpine") + var container = new ContainerBuilder("redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20") .WithCommand("redis-server", "--bind", "0.0.0.0") .WithPortBinding(RedisPort, true) .WithWaitStrategy(Wait.ForUnixContainer().UntilInternalTcpPortIsAvailable(RedisPort)) diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/StackExchangeRedisFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/StackExchangeRedisFixture.cs index 7f6758052e79..f1af6c943085 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/StackExchangeRedisFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/StackExchangeRedisFixture.cs @@ -61,7 +61,7 @@ protected override async Task InitializeResources(Action registe } // Keep synchronized with the image version in docker-compose.yml. - const string image = "redis:4-alpine"; + const string image = "redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20"; var network = new NetworkBuilder().Build(); var primaryContainer = new ContainerBuilder(image) From 26bb32d79b45dc1cc7e5d78f2dbb0f7c9bc95b08 Mon Sep 17 00:00:00 2001 From: Steven Bouwkamp Date: Thu, 30 Jul 2026 15:14:50 -0400 Subject: [PATCH 21/28] Support SQL Server readiness on ARM64 --- .../Containers/SqlServerFixture.cs | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/SqlServerFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/SqlServerFixture.cs index b357d738733c..fe073bda2228 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/SqlServerFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/SqlServerFixture.cs @@ -10,6 +10,8 @@ using System.Data.Common; using System.Runtime.InteropServices; using System.Threading.Tasks; +using DotNet.Testcontainers.Builders; +using DotNet.Testcontainers.Containers; using Testcontainers.MsSql; namespace Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; @@ -49,11 +51,24 @@ protected override async Task InitializeResources(Action registe return; } - // mssql/server has no native arm64 image, so use Azure SQL Edge on arm64. - var image = RuntimeInformation.ProcessArchitecture == Architecture.Arm64 ? AzureSqlEdgeImage : SqlServerImage; - var container = new MsSqlBuilder(image) + IContainer container; + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + // mssql/server has no native arm64 image. Azure SQL Edge does not include the sqlcmd binary + // used by MsSqlBuilder's readiness check, so wait for its ready message instead. + container = new ContainerBuilder(AzureSqlEdgeImage) + .WithPortBinding(SqlServerPort, true) + .WithEnvironment("ACCEPT_EULA", "Y") + .WithEnvironment("MSSQL_SA_PASSWORD", Password) + .WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged("SQL Server is now ready for client connections")) + .Build(); + } + else + { + container = new MsSqlBuilder(SqlServerImage) .WithPassword(Password) .Build(); + } await container.StartAsync().ConfigureAwait(false); From 82024bfe8c312f6f32dbab02867ef674505b5cb3 Mon Sep 17 00:00:00 2001 From: Steven Bouwkamp Date: Thu, 30 Jul 2026 16:16:50 -0400 Subject: [PATCH 22/28] Harden migrated Testcontainers fixtures --- docker-compose.yml | 28 +++++++++ .../DotNetSettingsExtensions.cs | 3 - .../Containers/AerospikeFixture.cs | 3 +- .../Containers/ContainerFixture.cs | 60 ++++++++++++------- .../Containers/CouchbaseFixture.cs | 27 +++------ .../Containers/KafkaFixture.cs | 46 +++----------- .../Containers/MySql57Fixture.cs | 2 +- .../Containers/MySql8Fixture.cs | 3 +- .../Containers/PostgresFixture.cs | 5 +- .../Containers/RabbitMqFixture.cs | 3 +- .../Containers/ServiceStackRedisFixture.cs | 18 +++++- .../Containers/SqlServerFixture.cs | 30 +++++++++- .../Containers/StackExchangeRedisFixture.cs | 48 +++------------ .../Couchbase3Tests.SchemaV0.verified.txt | 5 +- .../Couchbase3Tests.SchemaV1.verified.txt | 5 +- .../Samples.Couchbase3/Program.cs | 7 ++- 16 files changed, 145 insertions(+), 148 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index a3260c7faa9e..64ffc3ddf675 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,6 @@ services: + # Migrated Testcontainers services use the testcontainers profile so their pinned digests can be maintained + # without starting them during normal Compose workflows. # ARM64 dependencies localstack_arm64: image: localstack/localstack:4.14.0@sha256:3ebc37595918b8accb852f8048fef2aff047d465167edd655528065b07bc364a @@ -27,15 +29,19 @@ services: mysql_arm64: image: mysql/mysql-server:8.0@sha256:d6c8301b7834c5b9c2b733b10b7e630f441af7bc917c74dba379f24eeeb6a313 + profiles: ["testcontainers"] postgres_arm64: image: postgres:10.5-alpine@sha256:295a08ddd9efa1612c46033f0b96c3976f80f49c7ce29e05916b0af557806117 + profiles: ["testcontainers"] rabbitmq_arm64: image: rabbitmq:3-management@sha256:e582c0bc7766f3342496d8485efb5a1df782b5ce3886ad017e2eaae442311f69 + profiles: ["testcontainers"] servicestackredis_arm64: image: redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20 + profiles: ["testcontainers"] cosmosdb-emulator_arm64: image: mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:vnext-preview@sha256:54d7bc334494c50cea867c270880671a7db080626a9732832b34c0d69342f9b0 @@ -43,12 +49,15 @@ services: stackexchangeredis_arm64: image: redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20 + profiles: ["testcontainers"] stackexchangeredis_arm64-replica: image: redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20 + profiles: ["testcontainers"] stackexchangeredis_arm64-single: image: redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20 + profiles: ["testcontainers"] # Dependencies localstack: @@ -66,18 +75,23 @@ services: rabbitmq: image: rabbitmq:3-management@sha256:e582c0bc7766f3342496d8485efb5a1df782b5ce3886ad017e2eaae442311f69 + profiles: ["testcontainers"] servicestackredis: image: redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20 + profiles: ["testcontainers"] stackexchangeredis: image: redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20 + profiles: ["testcontainers"] stackexchangeredis-replica: image: redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20 + profiles: ["testcontainers"] stackexchangeredis-single: image: redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20 + profiles: ["testcontainers"] mongo: image: mongo:5.0.31@sha256:54bcd8da3ea5eec561b68c605046c55c6b304387dc4c2bf5b3a5f5064fbb7495 @@ -88,6 +102,7 @@ services: couchbase: image: couchbase:community-6.6.0@sha256:43103efdd4b562366c7a48afa977c4ad148e09c877da28a83a86b2c5ee2daa97 + profiles: ["testcontainers"] elasticsearch7: image: docker.elastic.co/elasticsearch/elasticsearch:7.14.1@sha256:2dcd2f31e246a8b13995ba24922da2edc3d88e65532ff301d0b92cb1be358af5 @@ -121,15 +136,19 @@ services: postgres: image: postgres:10.5-alpine@sha256:295a08ddd9efa1612c46033f0b96c3976f80f49c7ce29e05916b0af557806117 + profiles: ["testcontainers"] mysql: image: mysql/mysql-server:8.0@sha256:d6c8301b7834c5b9c2b733b10b7e630f441af7bc917c74dba379f24eeeb6a313 + profiles: ["testcontainers"] mysql57: image: mysql/mysql-server:5.7@sha256:1178cdd375f758968cd834ac4057bae41307e64b7c69a9e145896e7b11f48064 + profiles: ["testcontainers"] sqlserver: image: mcr.microsoft.com/mssql/server:latest@sha256:2cd0aec4a3bfc3cf9205bed3f7922f4c6208f7c767dc62edcee308d0fd7d56d0 + profiles: ["testcontainers"] sqledge: image: mcr.microsoft.com/azure-sql-edge:latest@sha256:902628a8be89e35dfb7895ca31d602974c7bafde4d583a0d0873844feb1c42cf @@ -196,9 +215,11 @@ services: kafka-zookeeper: image: confluentinc/cp-zookeeper:6.1.1@sha256:a7c0a20dce46a705300cd464e511e9c70ac55ec7e62c024867470a19ce210563 + profiles: ["testcontainers"] kafka-broker: image: confluentinc/cp-server:6.1.1@sha256:4a1ff92bd03e361759ba339c97b4c4b7dbb52d7cea478dda22d034261a8991e4 + profiles: ["testcontainers"] openldap: image: osixia/openldap:latest@sha256:3f68751292b43564a2586fc29fb7337573e2dad692b92d4e78e49ad5c22e567b @@ -719,24 +740,31 @@ services: mysql_osx_arm64: image: mysql/mysql-server:8.0@sha256:d6c8301b7834c5b9c2b733b10b7e630f441af7bc917c74dba379f24eeeb6a313 + profiles: ["testcontainers"] postgres_osx_arm64: image: postgres:10.5-alpine@sha256:295a08ddd9efa1612c46033f0b96c3976f80f49c7ce29e05916b0af557806117 + profiles: ["testcontainers"] rabbitmq_osx_arm64: image: rabbitmq:3-management@sha256:e582c0bc7766f3342496d8485efb5a1df782b5ce3886ad017e2eaae442311f69 + profiles: ["testcontainers"] servicestackredis_osx_arm64: image: redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20 + profiles: ["testcontainers"] stackexchangeredis_osx_arm64: image: redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20 + profiles: ["testcontainers"] stackexchangeredis_osx_arm64-replica: image: redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20 + profiles: ["testcontainers"] stackexchangeredis_osx_arm64-single: image: redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20 + profiles: ["testcontainers"] # keep syncronized image version with tracer\test\Datadog.Trace.TestHelpers.AutoInstrumentation\Containers\AerospikeFixture.cs aerospike: diff --git a/tracer/build/_build/NukeExtensions/DotNetSettingsExtensions.cs b/tracer/build/_build/NukeExtensions/DotNetSettingsExtensions.cs index 07f896e1d1db..1877d9201454 100644 --- a/tracer/build/_build/NukeExtensions/DotNetSettingsExtensions.cs +++ b/tracer/build/_build/NukeExtensions/DotNetSettingsExtensions.cs @@ -269,9 +269,6 @@ public static T SetLocalOsxEnvironmentVariables(this T toolSettings) { return toolSettings .SetProcessEnvironmentVariable("MONGO_HOST", "localhost") - .SetProcessEnvironmentVariable("SERVICESTACK_REDIS_HOST", "localhost:6379") - .SetProcessEnvironmentVariable("STACKEXCHANGE_REDIS_HOST", "localhost:6392,127.0.0.1:6390") - .SetProcessEnvironmentVariable("STACKEXCHANGE_REDIS_SINGLE_HOST", "localhost:6391") .SetProcessEnvironmentVariable("ELASTICSEARCH7_HOST", "localhost:9200") .SetProcessEnvironmentVariable("ELASTICSEARCH6_HOST", "localhost:9200") .SetProcessEnvironmentVariable("ELASTICSEARCH5_HOST", "localhost:9200") diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/AerospikeFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/AerospikeFixture.cs index 303123d63116..c81392ae3cb8 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/AerospikeFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/AerospikeFixture.cs @@ -45,8 +45,7 @@ protected override async Task InitializeResources(Action registe .WithWaitStrategy(Wait.ForUnixContainer().UntilInternalTcpPortIsAvailable(AerospikePort)) .Build(); - await container.StartAsync(); - registerResource("container", container); + await container.StartAsync(); } } diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ContainerFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ContainerFixture.cs index c0fa6d203fb2..1621d88795c8 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ContainerFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ContainerFixture.cs @@ -15,45 +15,59 @@ namespace Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; public abstract class ContainerFixture : IAsyncLifetime { - private IReadOnlyDictionary? _resources; + private readonly Dictionary _resources = new(); + private readonly List _resourcesForDisposal = []; public async Task InitializeAsync() { - _resources = await InitializeResources().ConfigureAwait(false); - } - - public async Task DisposeAsync() - { - if (_resources is null) + try { - return; + await InitializeResources(RegisterResource).ConfigureAwait(false); } - - foreach (var resource in _resources.Values) + catch { - if (resource is IAsyncDisposable asyncDisposable) - { - await asyncDisposable.DisposeAsync().ConfigureAwait(false); - } - else if (resource is IDisposable disposable) - { - disposable.Dispose(); - } + await DisposeResourcesAsync().ConfigureAwait(false); + throw; } } + public Task DisposeAsync() => DisposeResourcesAsync(); + public virtual IEnumerable> GetEnvironmentVariables() => Enumerable.Empty>(); protected abstract Task InitializeResources(Action registerResource); - protected T GetResource(string key) => (T)_resources![key]; + protected T GetResource(string key) => (T)_resources[key]; - private async Task> InitializeResources() + private void RegisterResource(string key, object resource) { - var resources = new Dictionary(); + _resources.Add(key, resource); + _resourcesForDisposal.Add(resource); + } - await InitializeResources(resources.Add).ConfigureAwait(false); + private async Task DisposeResourcesAsync() + { + for (var i = _resourcesForDisposal.Count - 1; i >= 0; i--) + { + try + { + var resource = _resourcesForDisposal[i]; + if (resource is IAsyncDisposable asyncDisposable) + { + await asyncDisposable.DisposeAsync().ConfigureAwait(false); + } + else if (resource is IDisposable disposable) + { + disposable.Dispose(); + } + } + catch + { + // Continue disposing the remaining resources. + } + } - return resources; + _resources.Clear(); + _resourcesForDisposal.Clear(); } } diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/CouchbaseFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/CouchbaseFixture.cs index aba70aca05dd..1ea4e486f38b 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/CouchbaseFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/CouchbaseFixture.cs @@ -89,26 +89,17 @@ protected override async Task InitializeResources(Action registe var container = builder.Build(); - try - { - await container.StartAsync().ConfigureAwait(false); - var host = dockerHost ?? container.Hostname; - var managementPort = useHostNetwork ? ManagementPort : container.GetMappedPublicPort(ManagementPort); - var keyValuePort = useHostNetwork ? KeyValuePort : container.GetMappedPublicPort(KeyValuePort); + registerResource("container", container); + await container.StartAsync().ConfigureAwait(false); + var host = dockerHost ?? container.Hostname; + var managementPort = useHostNetwork ? ManagementPort : container.GetMappedPublicPort(ManagementPort); + var keyValuePort = useHostNetwork ? KeyValuePort : container.GetMappedPublicPort(KeyValuePort); - await ConfigureCouchbaseAsync(container, host, useHostNetwork).ConfigureAwait(false); + await ConfigureCouchbaseAsync(container, host, useHostNetwork).ConfigureAwait(false); - _host = host; - _managementPort = managementPort; - _keyValuePort = keyValuePort; - } - catch - { - await container.DisposeAsync().ConfigureAwait(false); - throw; - } - - registerResource("container", container); + _host = host; + _managementPort = managementPort; + _keyValuePort = keyValuePort; } private static string GetDockerHost() diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/KafkaFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/KafkaFixture.cs index 908bb714fbe1..5e4be654f854 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/KafkaFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/KafkaFixture.cs @@ -10,7 +10,6 @@ using System.Threading.Tasks; using DotNet.Testcontainers.Builders; using DotNet.Testcontainers.Containers; -using DotNet.Testcontainers.Networks; using Testcontainers.Kafka; namespace Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; @@ -22,7 +21,7 @@ public class KafkaFixture : ContainerFixture private const string ZooKeeperImage = "confluentinc/cp-zookeeper:6.1.1@sha256:a7c0a20dce46a705300cd464e511e9c70ac55ec7e62c024867470a19ce210563"; private const string KafkaImage = "confluentinc/cp-server:6.1.1@sha256:4a1ff92bd03e361759ba339c97b4c4b7dbb52d7cea478dda22d034261a8991e4"; - private KafkaContainer KafkaContainer => GetResource("resources").KafkaContainer; + private KafkaContainer KafkaContainer => GetResource("container"); public override IEnumerable> GetEnvironmentVariables() { @@ -45,43 +44,12 @@ protected override async Task InitializeResources(Action registe .WithPortBinding(KafkaBuilder.ZooKeeperPort, true) .WithEnvironment("KAFKA_AUTO_CREATE_TOPICS_ENABLE", "false") .Build(); - var resources = new Resources(kafkaContainer, zooKeeperContainer, network); - try - { - await network.CreateAsync().ConfigureAwait(false); - await zooKeeperContainer.StartAsync().ConfigureAwait(false); - await kafkaContainer.StartAsync().ConfigureAwait(false); - } - catch - { - await resources.DisposeAsync().ConfigureAwait(false); - throw; - } - - registerResource("resources", resources); - } - - private sealed class Resources : IAsyncDisposable - { - public Resources(KafkaContainer kafkaContainer, IContainer zooKeeperContainer, INetwork network) - { - KafkaContainer = kafkaContainer; - ZooKeeperContainer = zooKeeperContainer; - Network = network; - } - - public KafkaContainer KafkaContainer { get; } - - private IContainer ZooKeeperContainer { get; } - - private INetwork Network { get; } - - public async ValueTask DisposeAsync() - { - await KafkaContainer.DisposeAsync().ConfigureAwait(false); - await ZooKeeperContainer.DisposeAsync().ConfigureAwait(false); - await Network.DisposeAsync().ConfigureAwait(false); - } + registerResource("network", network); + registerResource("zookeeper", zooKeeperContainer); + registerResource("container", kafkaContainer); + await network.CreateAsync().ConfigureAwait(false); + await zooKeeperContainer.StartAsync().ConfigureAwait(false); + await kafkaContainer.StartAsync().ConfigureAwait(false); } } diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql57Fixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql57Fixture.cs index b4b173c3a9c6..144590601460 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql57Fixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql57Fixture.cs @@ -59,9 +59,9 @@ protected override async Task InitializeResources(Action registe .UntilCommandIsCompleted("mysqladmin", "ping", "--silent", "-h", "localhost", "-u", "root", $"-p{Password}")) .Build(); + registerResource("container", container); await container.StartAsync().ConfigureAwait(false); _container = container; - registerResource("container", container); } } diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql8Fixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql8Fixture.cs index 441fb8afcfa8..cbf7201878e3 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql8Fixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql8Fixture.cs @@ -49,8 +49,7 @@ protected override async Task InitializeResources(Action registe .UntilCommandIsCompleted("mysqladmin", "ping", "--silent", "-h", "localhost", "-u", "root", $"-p{Password}")) .Build(); - await container.StartAsync().ConfigureAwait(false); - registerResource("container", container); + await container.StartAsync().ConfigureAwait(false); } } diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/PostgresFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/PostgresFixture.cs index 6692bef9fc1d..bd112067fd72 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/PostgresFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/PostgresFixture.cs @@ -39,11 +39,10 @@ protected override async Task InitializeResources(Action registe .WithEnvironment("POSTGRES_USER", Username) .WithEnvironment("POSTGRES_PASSWORD", Password) .WithEnvironment("POSTGRES_DB", Database) - .WithWaitStrategy(Wait.ForUnixContainer().UntilCommandIsCompleted("pg_isready", "-U", Username)) + .WithWaitStrategy(Wait.ForUnixContainer().UntilCommandIsCompleted("pg_isready", "-h", "localhost", "-U", Username)) .Build(); - await container.StartAsync().ConfigureAwait(false); - registerResource("container", container); + await container.StartAsync().ConfigureAwait(false); } } diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/RabbitMqFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/RabbitMqFixture.cs index e29bfc5b6d42..a6da47ffbf4f 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/RabbitMqFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/RabbitMqFixture.cs @@ -38,8 +38,7 @@ protected override async Task InitializeResources(Action registe .WithWaitStrategy(Wait.ForUnixContainer().UntilInternalTcpPortIsAvailable(RabbitMqPort)) .Build(); - await container.StartAsync().ConfigureAwait(false); - registerResource("container", container); + await container.StartAsync().ConfigureAwait(false); } } diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ServiceStackRedisFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ServiceStackRedisFixture.cs index 788080fe2d18..573730c806b8 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ServiceStackRedisFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ServiceStackRedisFixture.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; +using System.Runtime.InteropServices; using System.Threading.Tasks; using DotNet.Testcontainers.Builders; using DotNet.Testcontainers.Containers; @@ -16,16 +17,28 @@ namespace Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; public class ServiceStackRedisFixture : ContainerFixture { private const int RedisPort = 6379; + private const string DefaultHostConfiguration = "localhost:6379"; + + private string? _hostConfiguration; protected IContainer Container => GetResource("container"); public override IEnumerable> GetEnvironmentVariables() { - yield return new("SERVICESTACK_REDIS_HOST", $"{Container.Hostname}:{Container.GetMappedPublicPort(RedisPort)}"); + yield return new( + "SERVICESTACK_REDIS_HOST", + _hostConfiguration ?? $"{Container.Hostname}:{Container.GetMappedPublicPort(RedisPort)}"); } protected override async Task InitializeResources(Action registerResource) { + var hostConfiguration = Environment.GetEnvironmentVariable("SERVICESTACK_REDIS_HOST"); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || !string.IsNullOrEmpty(hostConfiguration)) + { + _hostConfiguration = string.IsNullOrEmpty(hostConfiguration) ? DefaultHostConfiguration : hostConfiguration; + return; + } + // Keep synchronized with the image version in docker-compose.yml. var container = new ContainerBuilder("redis:4-alpine@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20") .WithCommand("redis-server", "--bind", "0.0.0.0") @@ -33,8 +46,7 @@ protected override async Task InitializeResources(Action registe .WithWaitStrategy(Wait.ForUnixContainer().UntilInternalTcpPortIsAvailable(RedisPort)) .Build(); - await container.StartAsync().ConfigureAwait(false); - registerResource("container", container); + await container.StartAsync().ConfigureAwait(false); } } diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/SqlServerFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/SqlServerFixture.cs index fe073bda2228..53c46acf19ab 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/SqlServerFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/SqlServerFixture.cs @@ -11,6 +11,7 @@ using System.Runtime.InteropServices; using System.Threading.Tasks; using DotNet.Testcontainers.Builders; +using DotNet.Testcontainers.Configurations; using DotNet.Testcontainers.Containers; using Testcontainers.MsSql; @@ -60,7 +61,7 @@ protected override async Task InitializeResources(Action registe .WithPortBinding(SqlServerPort, true) .WithEnvironment("ACCEPT_EULA", "Y") .WithEnvironment("MSSQL_SA_PASSWORD", Password) - .WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged("SQL Server is now ready for client connections")) + .WithWaitStrategy(Wait.ForUnixContainer().AddCustomWaitStrategy(new UntilFinalSqlServerIsReady())) .Build(); } else @@ -70,11 +71,11 @@ protected override async Task InitializeResources(Action registe .Build(); } + registerResource("container", container); await container.StartAsync().ConfigureAwait(false); HostAndPort = $"{container.Hostname},{container.GetMappedPublicPort(SqlServerPort)}"; _connectionString = $"Server={HostAndPort};User=sa;Password={Password};TrustServerCertificate=True"; - registerResource("container", container); } private static string? GetHost(string connectionString) @@ -90,4 +91,29 @@ protected override async Task InitializeResources(Action registe return null; } + + private sealed class UntilFinalSqlServerIsReady : IWaitUntil + { + private const string ReadyMessage = "SQL Server is now ready for client connections"; + + public async Task UntilAsync(IContainer container) + { + // Azure SQL Edge logs this message for its internal setup server and again for the final server. + var (stdout, stderr) = await container.GetLogsAsync(timestampsEnabled: false).ConfigureAwait(false); + return CountOccurrences(stdout) + CountOccurrences(stderr) >= 2; + } + + private static int CountOccurrences(string value) + { + var count = 0; + var startIndex = 0; + while ((startIndex = value.IndexOf(ReadyMessage, startIndex, StringComparison.Ordinal)) >= 0) + { + count++; + startIndex += ReadyMessage.Length; + } + + return count; + } + } } diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/StackExchangeRedisFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/StackExchangeRedisFixture.cs index f1af6c943085..67bf796b613a 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/StackExchangeRedisFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/StackExchangeRedisFixture.cs @@ -11,7 +11,6 @@ using System.Threading.Tasks; using DotNet.Testcontainers.Builders; using DotNet.Testcontainers.Containers; -using DotNet.Testcontainers.Networks; namespace Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; @@ -86,25 +85,19 @@ protected override async Task InitializeResources(Action registe .WithPortBinding(RedisPort, true) .WithWaitStrategy(Wait.ForUnixContainer().UntilInternalTcpPortIsAvailable(RedisPort)) .Build(); - var resources = new Resources(primaryContainer, replicaContainer, singleContainer, network); - try - { - await network.CreateAsync().ConfigureAwait(false); - await primaryContainer.StartAsync().ConfigureAwait(false); - await Task.WhenAll(replicaContainer.StartAsync(), singleContainer.StartAsync()).ConfigureAwait(false); - } - catch - { - await resources.DisposeAsync().ConfigureAwait(false); - throw; - } + registerResource("network", network); + registerResource("primary", primaryContainer); + registerResource("replica", replicaContainer); + registerResource("single", singleContainer); + await network.CreateAsync().ConfigureAwait(false); + await primaryContainer.StartAsync().ConfigureAwait(false); + await Task.WhenAll(replicaContainer.StartAsync(), singleContainer.StartAsync()).ConfigureAwait(false); _primaryEndpoint = new Endpoint(primaryContainer.Hostname, primaryContainer.GetMappedPublicPort(RedisPort)); _replicaEndpoint = new Endpoint(replicaContainer.Hostname, replicaContainer.GetMappedPublicPort(RedisPort)); _hostConfiguration = $"{_primaryEndpoint},{_replicaEndpoint}"; _singleHostConfiguration = $"{singleContainer.Hostname}:{singleContainer.GetMappedPublicPort(RedisPort)}"; - registerResource("resources", resources); } private static List ParseEndpoints(string configuration) @@ -171,31 +164,4 @@ public static Endpoint Parse(string value) private static ushort ParsePort(string value) => ushort.TryParse(value, out var port) ? port : (ushort)RedisPort; } - - private sealed class Resources : IAsyncDisposable - { - public Resources(IContainer primaryContainer, IContainer replicaContainer, IContainer singleContainer, INetwork network) - { - PrimaryContainer = primaryContainer; - ReplicaContainer = replicaContainer; - SingleContainer = singleContainer; - Network = network; - } - - public IContainer PrimaryContainer { get; } - - public IContainer ReplicaContainer { get; } - - public IContainer SingleContainer { get; } - - private INetwork Network { get; } - - public async ValueTask DisposeAsync() - { - await SingleContainer.DisposeAsync().ConfigureAwait(false); - await ReplicaContainer.DisposeAsync().ConfigureAwait(false); - await PrimaryContainer.DisposeAsync().ConfigureAwait(false); - await Network.DisposeAsync().ConfigureAwait(false); - } - } } diff --git a/tracer/test/snapshots/Couchbase3Tests.SchemaV0.verified.txt b/tracer/test/snapshots/Couchbase3Tests.SchemaV0.verified.txt index 6a4328036058..97bafb7f1dbc 100644 --- a/tracer/test/snapshots/Couchbase3Tests.SchemaV0.verified.txt +++ b/tracer/test/snapshots/Couchbase3Tests.SchemaV0.verified.txt @@ -8,7 +8,6 @@ Type: db, Tags: { component: Couchbase, - couchbase.operation.bucket: default, couchbase.operation.code: Delete, couchbase.operation.key: my-document-key, db.couchbase.seed.nodes: couchbase, @@ -36,7 +35,6 @@ Error: 1, Tags: { component: Couchbase, - couchbase.operation.bucket: default, couchbase.operation.code: Delete, couchbase.operation.key: does-not-exist, db.couchbase.seed.nodes: couchbase, @@ -64,7 +62,6 @@ Type: db, Tags: { component: Couchbase, - couchbase.operation.bucket: default, couchbase.operation.code: Get, couchbase.operation.key: my-document-key, db.couchbase.seed.nodes: couchbase, @@ -369,4 +366,4 @@ _sampling_priority_v1: 1.0 } } -] \ No newline at end of file +] diff --git a/tracer/test/snapshots/Couchbase3Tests.SchemaV1.verified.txt b/tracer/test/snapshots/Couchbase3Tests.SchemaV1.verified.txt index af9bae346fdc..8c5099bfc71d 100644 --- a/tracer/test/snapshots/Couchbase3Tests.SchemaV1.verified.txt +++ b/tracer/test/snapshots/Couchbase3Tests.SchemaV1.verified.txt @@ -8,7 +8,6 @@ Type: db, Tags: { component: Couchbase, - couchbase.operation.bucket: default, couchbase.operation.code: Delete, couchbase.operation.key: my-document-key, db.couchbase.seed.nodes: couchbase, @@ -37,7 +36,6 @@ Error: 1, Tags: { component: Couchbase, - couchbase.operation.bucket: default, couchbase.operation.code: Delete, couchbase.operation.key: does-not-exist, db.couchbase.seed.nodes: couchbase, @@ -66,7 +64,6 @@ Type: db, Tags: { component: Couchbase, - couchbase.operation.bucket: default, couchbase.operation.code: Get, couchbase.operation.key: my-document-key, db.couchbase.seed.nodes: couchbase, @@ -383,4 +380,4 @@ _sampling_priority_v1: 1.0 } } -] \ No newline at end of file +] diff --git a/tracer/test/test-applications/integrations/Samples.Couchbase3/Program.cs b/tracer/test/test-applications/integrations/Samples.Couchbase3/Program.cs index e2b6bf03a04a..8aceee897f48 100644 --- a/tracer/test/test-applications/integrations/Samples.Couchbase3/Program.cs +++ b/tracer/test/test-applications/integrations/Samples.Couchbase3/Program.cs @@ -45,8 +45,13 @@ private static async Task Main() // get a bucket reference var bucket = await cluster.BucketAsync(BucketName()); - // get the default collection reference + // get a collection reference +#if COUCHBASE_3_0 var collection = bucket.DefaultCollection(); +#else + var scope = await bucket.ScopeAsync("tenant_agent_00"); + var collection = await scope.CollectionAsync("users"); +#endif // Upsert Document var upsertResult = await collection.UpsertAsync("my-document-key", new { Name = "Ted", Age = 31 }); From 39295d4970520cdfae8b98b5e2f6eaa819bf1459 Mon Sep 17 00:00:00 2001 From: Steven Bouwkamp Date: Fri, 31 Jul 2026 09:02:13 -0400 Subject: [PATCH 23/28] Split Couchbase snapshots for SDK 3.8 --- .../Couchbase3Tests.cs | 2 +- .../Couchbase3Tests_3_8.SchemaV0.verified.txt | 372 +++++++++++++++++ .../Couchbase3Tests_3_8.SchemaV1.verified.txt | 386 ++++++++++++++++++ 3 files changed, 759 insertions(+), 1 deletion(-) create mode 100644 tracer/test/snapshots/Couchbase3Tests_3_8.SchemaV0.verified.txt create mode 100644 tracer/test/snapshots/Couchbase3Tests_3_8.SchemaV1.verified.txt diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Couchbase3Tests.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Couchbase3Tests.cs index 784220183596..b7862b1874d5 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Couchbase3Tests.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/Couchbase3Tests.cs @@ -137,7 +137,7 @@ private static string GetVersionSuffix(string packageVersion) return "_3_4"; } - return string.Empty; + return version < new Version("3.8.0") ? string.Empty : "_3_8"; } private static Version GetPackageVersion(string packageVersion) diff --git a/tracer/test/snapshots/Couchbase3Tests_3_8.SchemaV0.verified.txt b/tracer/test/snapshots/Couchbase3Tests_3_8.SchemaV0.verified.txt new file mode 100644 index 000000000000..9572b1cef4ac --- /dev/null +++ b/tracer/test/snapshots/Couchbase3Tests_3_8.SchemaV0.verified.txt @@ -0,0 +1,372 @@ +[ + { + TraceId: Id_1, + SpanId: Id_2, + Name: couchbase.query, + Resource: Delete, + Service: Samples.Couchbase3-couchbase, + Type: db, + Tags: { + component: Couchbase, + couchbase.operation.bucket: default, + couchbase.operation.code: Delete, + couchbase.operation.key: my-document-key, + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + language: dotnet, + runtime-id: Guid_1, + span.kind: client, + _dd.base_service: Samples.Couchbase3, + _dd.svc_src: couchbase + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + }, + { + TraceId: Id_3, + SpanId: Id_4, + Name: couchbase.query, + Resource: Delete, + Service: Samples.Couchbase3-couchbase, + Type: db, + Error: 1, + Tags: { + component: Couchbase, + couchbase.operation.bucket: default, + couchbase.operation.code: Delete, + couchbase.operation.key: does-not-exist, + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + error.msg: KeyNotFound, + language: dotnet, + runtime-id: Guid_1, + span.kind: client, + _dd.base_service: Samples.Couchbase3, + _dd.svc_src: couchbase + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + }, + { + TraceId: Id_5, + SpanId: Id_6, + Name: couchbase.query, + Resource: Get, + Service: Samples.Couchbase3-couchbase, + Type: db, + Tags: { + component: Couchbase, + couchbase.operation.bucket: default, + couchbase.operation.code: Get, + couchbase.operation.key: my-document-key, + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + language: dotnet, + runtime-id: Guid_1, + span.kind: client, + _dd.base_service: Samples.Couchbase3, + _dd.svc_src: couchbase + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + }, + { + TraceId: Id_7, + SpanId: Id_8, + Name: couchbase.query, + Resource: GetErrorMap, + Service: Samples.Couchbase3-couchbase, + Type: db, + Tags: { + component: Couchbase, + couchbase.operation.code: GetErrorMap, + couchbase.operation.key: , + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + language: dotnet, + runtime-id: Guid_1, + span.kind: client, + _dd.base_service: Samples.Couchbase3, + _dd.svc_src: couchbase + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + }, + { + TraceId: Id_9, + SpanId: Id_10, + Name: couchbase.query, + Resource: GetErrorMap, + Service: Samples.Couchbase3-couchbase, + Type: db, + Tags: { + component: Couchbase, + couchbase.operation.code: GetErrorMap, + couchbase.operation.key: , + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + language: dotnet, + runtime-id: Guid_1, + span.kind: client, + _dd.base_service: Samples.Couchbase3, + _dd.svc_src: couchbase + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + }, + { + TraceId: Id_11, + SpanId: Id_12, + Name: couchbase.query, + Resource: Hello, + Service: Samples.Couchbase3-couchbase, + Type: db, + Tags: { + component: Couchbase, + couchbase.operation.code: Hello, + couchbase.operation.key: obfuscated, + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + language: dotnet, + runtime-id: Guid_1, + span.kind: client, + _dd.base_service: Samples.Couchbase3, + _dd.svc_src: couchbase + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + }, + { + TraceId: Id_13, + SpanId: Id_14, + Name: couchbase.query, + Resource: Hello, + Service: Samples.Couchbase3-couchbase, + Type: db, + Tags: { + component: Couchbase, + couchbase.operation.code: Hello, + couchbase.operation.key: obfuscated, + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + language: dotnet, + runtime-id: Guid_1, + span.kind: client, + _dd.base_service: Samples.Couchbase3, + _dd.svc_src: couchbase + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + }, + { + TraceId: Id_15, + SpanId: Id_16, + Name: couchbase.query, + Resource: NoOp, + Service: Samples.Couchbase3-couchbase, + Type: db, + Tags: { + component: Couchbase, + couchbase.operation.code: NoOp, + couchbase.operation.key: , + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + language: dotnet, + runtime-id: Guid_1, + span.kind: client, + _dd.base_service: Samples.Couchbase3, + _dd.svc_src: couchbase + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + }, + { + TraceId: Id_17, + SpanId: Id_18, + Name: couchbase.query, + Resource: NoOp, + Service: Samples.Couchbase3-couchbase, + Type: db, + Tags: { + component: Couchbase, + couchbase.operation.code: NoOp, + couchbase.operation.key: , + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + language: dotnet, + runtime-id: Guid_1, + span.kind: client, + _dd.base_service: Samples.Couchbase3, + _dd.svc_src: couchbase + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + }, + { + TraceId: Id_19, + SpanId: Id_20, + Name: couchbase.query, + Resource: SaslList, + Service: Samples.Couchbase3-couchbase, + Type: db, + Tags: { + component: Couchbase, + couchbase.operation.code: SaslList, + couchbase.operation.key: , + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + language: dotnet, + runtime-id: Guid_1, + span.kind: client, + _dd.base_service: Samples.Couchbase3, + _dd.svc_src: couchbase + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + }, + { + TraceId: Id_21, + SpanId: Id_22, + Name: couchbase.query, + Resource: SaslList, + Service: Samples.Couchbase3-couchbase, + Type: db, + Tags: { + component: Couchbase, + couchbase.operation.code: SaslList, + couchbase.operation.key: , + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + language: dotnet, + runtime-id: Guid_1, + span.kind: client, + _dd.base_service: Samples.Couchbase3, + _dd.svc_src: couchbase + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + }, + { + TraceId: Id_23, + SpanId: Id_24, + Name: couchbase.query, + Resource: SelectBucket, + Service: Samples.Couchbase3-couchbase, + Type: db, + Tags: { + component: Couchbase, + couchbase.operation.code: SelectBucket, + couchbase.operation.key: default, + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + language: dotnet, + runtime-id: Guid_1, + span.kind: client, + _dd.base_service: Samples.Couchbase3, + _dd.svc_src: couchbase + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + }, + { + TraceId: Id_25, + SpanId: Id_26, + Name: couchbase.query, + Resource: SelectBucket, + Service: Samples.Couchbase3-couchbase, + Type: db, + Tags: { + component: Couchbase, + couchbase.operation.code: SelectBucket, + couchbase.operation.key: default, + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + language: dotnet, + runtime-id: Guid_1, + span.kind: client, + _dd.base_service: Samples.Couchbase3, + _dd.svc_src: couchbase + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + }, + { + TraceId: Id_27, + SpanId: Id_28, + Name: couchbase.query, + Resource: Set, + Service: Samples.Couchbase3-couchbase, + Type: db, + Tags: { + component: Couchbase, + couchbase.operation.bucket: default, + couchbase.operation.code: Set, + couchbase.operation.key: my-document-key, + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + language: dotnet, + runtime-id: Guid_1, + span.kind: client, + _dd.base_service: Samples.Couchbase3, + _dd.svc_src: couchbase + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + } +] diff --git a/tracer/test/snapshots/Couchbase3Tests_3_8.SchemaV1.verified.txt b/tracer/test/snapshots/Couchbase3Tests_3_8.SchemaV1.verified.txt new file mode 100644 index 000000000000..ab7e2f325f2e --- /dev/null +++ b/tracer/test/snapshots/Couchbase3Tests_3_8.SchemaV1.verified.txt @@ -0,0 +1,386 @@ +[ + { + TraceId: Id_1, + SpanId: Id_2, + Name: couchbase.query, + Resource: Delete, + Service: Samples.Couchbase3, + Type: db, + Tags: { + component: Couchbase, + couchbase.operation.bucket: default, + couchbase.operation.code: Delete, + couchbase.operation.key: my-document-key, + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + language: dotnet, + peer.service: couchbase, + runtime-id: Guid_1, + span.kind: client, + version: 1.0.0, + _dd.peer.service.source: db.couchbase.seed.nodes + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + }, + { + TraceId: Id_3, + SpanId: Id_4, + Name: couchbase.query, + Resource: Delete, + Service: Samples.Couchbase3, + Type: db, + Error: 1, + Tags: { + component: Couchbase, + couchbase.operation.bucket: default, + couchbase.operation.code: Delete, + couchbase.operation.key: does-not-exist, + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + error.msg: KeyNotFound, + language: dotnet, + peer.service: couchbase, + runtime-id: Guid_1, + span.kind: client, + version: 1.0.0, + _dd.peer.service.source: db.couchbase.seed.nodes + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + }, + { + TraceId: Id_5, + SpanId: Id_6, + Name: couchbase.query, + Resource: Get, + Service: Samples.Couchbase3, + Type: db, + Tags: { + component: Couchbase, + couchbase.operation.bucket: default, + couchbase.operation.code: Get, + couchbase.operation.key: my-document-key, + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + language: dotnet, + peer.service: couchbase, + runtime-id: Guid_1, + span.kind: client, + version: 1.0.0, + _dd.peer.service.source: db.couchbase.seed.nodes + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + }, + { + TraceId: Id_7, + SpanId: Id_8, + Name: couchbase.query, + Resource: GetErrorMap, + Service: Samples.Couchbase3, + Type: db, + Tags: { + component: Couchbase, + couchbase.operation.code: GetErrorMap, + couchbase.operation.key: , + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + language: dotnet, + peer.service: couchbase, + runtime-id: Guid_1, + span.kind: client, + version: 1.0.0, + _dd.peer.service.source: db.couchbase.seed.nodes + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + }, + { + TraceId: Id_9, + SpanId: Id_10, + Name: couchbase.query, + Resource: GetErrorMap, + Service: Samples.Couchbase3, + Type: db, + Tags: { + component: Couchbase, + couchbase.operation.code: GetErrorMap, + couchbase.operation.key: , + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + language: dotnet, + peer.service: couchbase, + runtime-id: Guid_1, + span.kind: client, + version: 1.0.0, + _dd.peer.service.source: db.couchbase.seed.nodes + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + }, + { + TraceId: Id_11, + SpanId: Id_12, + Name: couchbase.query, + Resource: Hello, + Service: Samples.Couchbase3, + Type: db, + Tags: { + component: Couchbase, + couchbase.operation.code: Hello, + couchbase.operation.key: obfuscated, + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + language: dotnet, + peer.service: couchbase, + runtime-id: Guid_1, + span.kind: client, + version: 1.0.0, + _dd.peer.service.source: db.couchbase.seed.nodes + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + }, + { + TraceId: Id_13, + SpanId: Id_14, + Name: couchbase.query, + Resource: Hello, + Service: Samples.Couchbase3, + Type: db, + Tags: { + component: Couchbase, + couchbase.operation.code: Hello, + couchbase.operation.key: obfuscated, + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + language: dotnet, + peer.service: couchbase, + runtime-id: Guid_1, + span.kind: client, + version: 1.0.0, + _dd.peer.service.source: db.couchbase.seed.nodes + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + }, + { + TraceId: Id_15, + SpanId: Id_16, + Name: couchbase.query, + Resource: NoOp, + Service: Samples.Couchbase3, + Type: db, + Tags: { + component: Couchbase, + couchbase.operation.code: NoOp, + couchbase.operation.key: , + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + language: dotnet, + peer.service: couchbase, + runtime-id: Guid_1, + span.kind: client, + version: 1.0.0, + _dd.peer.service.source: db.couchbase.seed.nodes + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + }, + { + TraceId: Id_17, + SpanId: Id_18, + Name: couchbase.query, + Resource: NoOp, + Service: Samples.Couchbase3, + Type: db, + Tags: { + component: Couchbase, + couchbase.operation.code: NoOp, + couchbase.operation.key: , + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + language: dotnet, + peer.service: couchbase, + runtime-id: Guid_1, + span.kind: client, + version: 1.0.0, + _dd.peer.service.source: db.couchbase.seed.nodes + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + }, + { + TraceId: Id_19, + SpanId: Id_20, + Name: couchbase.query, + Resource: SaslList, + Service: Samples.Couchbase3, + Type: db, + Tags: { + component: Couchbase, + couchbase.operation.code: SaslList, + couchbase.operation.key: , + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + language: dotnet, + peer.service: couchbase, + runtime-id: Guid_1, + span.kind: client, + version: 1.0.0, + _dd.peer.service.source: db.couchbase.seed.nodes + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + }, + { + TraceId: Id_21, + SpanId: Id_22, + Name: couchbase.query, + Resource: SaslList, + Service: Samples.Couchbase3, + Type: db, + Tags: { + component: Couchbase, + couchbase.operation.code: SaslList, + couchbase.operation.key: , + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + language: dotnet, + peer.service: couchbase, + runtime-id: Guid_1, + span.kind: client, + version: 1.0.0, + _dd.peer.service.source: db.couchbase.seed.nodes + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + }, + { + TraceId: Id_23, + SpanId: Id_24, + Name: couchbase.query, + Resource: SelectBucket, + Service: Samples.Couchbase3, + Type: db, + Tags: { + component: Couchbase, + couchbase.operation.code: SelectBucket, + couchbase.operation.key: default, + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + language: dotnet, + peer.service: couchbase, + runtime-id: Guid_1, + span.kind: client, + version: 1.0.0, + _dd.peer.service.source: db.couchbase.seed.nodes + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + }, + { + TraceId: Id_25, + SpanId: Id_26, + Name: couchbase.query, + Resource: SelectBucket, + Service: Samples.Couchbase3, + Type: db, + Tags: { + component: Couchbase, + couchbase.operation.code: SelectBucket, + couchbase.operation.key: default, + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + language: dotnet, + peer.service: couchbase, + runtime-id: Guid_1, + span.kind: client, + version: 1.0.0, + _dd.peer.service.source: db.couchbase.seed.nodes + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + }, + { + TraceId: Id_27, + SpanId: Id_28, + Name: couchbase.query, + Resource: Set, + Service: Samples.Couchbase3, + Type: db, + Tags: { + component: Couchbase, + couchbase.operation.bucket: default, + couchbase.operation.code: Set, + couchbase.operation.key: my-document-key, + db.couchbase.seed.nodes: couchbase, + env: integration_tests, + language: dotnet, + peer.service: couchbase, + runtime-id: Guid_1, + span.kind: client, + version: 1.0.0, + _dd.peer.service.source: db.couchbase.seed.nodes + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + } +] From 69284037e94eeeae35f46d55ecfe553297fc910f Mon Sep 17 00:00:00 2001 From: Steven Bouwkamp Date: Fri, 31 Jul 2026 10:38:38 -0400 Subject: [PATCH 24/28] Remove trailing newlines --- tracer/test/snapshots/Couchbase3Tests.SchemaV0.verified.txt | 2 +- tracer/test/snapshots/Couchbase3Tests.SchemaV1.verified.txt | 2 +- tracer/test/snapshots/Couchbase3Tests_3_8.SchemaV0.verified.txt | 2 +- tracer/test/snapshots/Couchbase3Tests_3_8.SchemaV1.verified.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tracer/test/snapshots/Couchbase3Tests.SchemaV0.verified.txt b/tracer/test/snapshots/Couchbase3Tests.SchemaV0.verified.txt index 97bafb7f1dbc..7282cecde5b5 100644 --- a/tracer/test/snapshots/Couchbase3Tests.SchemaV0.verified.txt +++ b/tracer/test/snapshots/Couchbase3Tests.SchemaV0.verified.txt @@ -366,4 +366,4 @@ _sampling_priority_v1: 1.0 } } -] +] \ No newline at end of file diff --git a/tracer/test/snapshots/Couchbase3Tests.SchemaV1.verified.txt b/tracer/test/snapshots/Couchbase3Tests.SchemaV1.verified.txt index 8c5099bfc71d..8782d778479b 100644 --- a/tracer/test/snapshots/Couchbase3Tests.SchemaV1.verified.txt +++ b/tracer/test/snapshots/Couchbase3Tests.SchemaV1.verified.txt @@ -380,4 +380,4 @@ _sampling_priority_v1: 1.0 } } -] +] \ No newline at end of file diff --git a/tracer/test/snapshots/Couchbase3Tests_3_8.SchemaV0.verified.txt b/tracer/test/snapshots/Couchbase3Tests_3_8.SchemaV0.verified.txt index 9572b1cef4ac..6a4328036058 100644 --- a/tracer/test/snapshots/Couchbase3Tests_3_8.SchemaV0.verified.txt +++ b/tracer/test/snapshots/Couchbase3Tests_3_8.SchemaV0.verified.txt @@ -369,4 +369,4 @@ _sampling_priority_v1: 1.0 } } -] +] \ No newline at end of file diff --git a/tracer/test/snapshots/Couchbase3Tests_3_8.SchemaV1.verified.txt b/tracer/test/snapshots/Couchbase3Tests_3_8.SchemaV1.verified.txt index ab7e2f325f2e..af9bae346fdc 100644 --- a/tracer/test/snapshots/Couchbase3Tests_3_8.SchemaV1.verified.txt +++ b/tracer/test/snapshots/Couchbase3Tests_3_8.SchemaV1.verified.txt @@ -383,4 +383,4 @@ _sampling_priority_v1: 1.0 } } -] +] \ No newline at end of file From 318c11a09d4c2c92aa9b2c1c855df4111367297a Mon Sep 17 00:00:00 2001 From: Steven Bouwkamp Date: Fri, 31 Jul 2026 12:11:41 -0400 Subject: [PATCH 25/28] Fix ARM64 SQL Server readiness wait --- .../Containers/SqlServerFixture.cs | 35 ++++--------------- 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/SqlServerFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/SqlServerFixture.cs index 53c46acf19ab..07eb764f0616 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/SqlServerFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/SqlServerFixture.cs @@ -11,7 +11,6 @@ using System.Runtime.InteropServices; using System.Threading.Tasks; using DotNet.Testcontainers.Builders; -using DotNet.Testcontainers.Configurations; using DotNet.Testcontainers.Containers; using Testcontainers.MsSql; @@ -56,12 +55,17 @@ protected override async Task InitializeResources(Action registe if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) { // mssql/server has no native arm64 image. Azure SQL Edge does not include the sqlcmd binary - // used by MsSqlBuilder's readiness check, so wait for its ready message instead. + // used by MsSqlBuilder's readiness check, so wait for its ready message instead. Explicitly + // bound the wait because Testcontainers defaults to one hour. container = new ContainerBuilder(AzureSqlEdgeImage) .WithPortBinding(SqlServerPort, true) .WithEnvironment("ACCEPT_EULA", "Y") .WithEnvironment("MSSQL_SA_PASSWORD", Password) - .WithWaitStrategy(Wait.ForUnixContainer().AddCustomWaitStrategy(new UntilFinalSqlServerIsReady())) + .WithWaitStrategy( + Wait.ForUnixContainer() + .UntilMessageIsLogged( + "SQL Server is now ready for client connections", + strategy => strategy.WithTimeout(TimeSpan.FromMinutes(2)))) .Build(); } else @@ -91,29 +95,4 @@ protected override async Task InitializeResources(Action registe return null; } - - private sealed class UntilFinalSqlServerIsReady : IWaitUntil - { - private const string ReadyMessage = "SQL Server is now ready for client connections"; - - public async Task UntilAsync(IContainer container) - { - // Azure SQL Edge logs this message for its internal setup server and again for the final server. - var (stdout, stderr) = await container.GetLogsAsync(timestampsEnabled: false).ConfigureAwait(false); - return CountOccurrences(stdout) + CountOccurrences(stderr) >= 2; - } - - private static int CountOccurrences(string value) - { - var count = 0; - var startIndex = 0; - while ((startIndex = value.IndexOf(ReadyMessage, startIndex, StringComparison.Ordinal)) >= 0) - { - count++; - startIndex += ReadyMessage.Length; - } - - return count; - } - } } From b012a71e52b7a17c7b69431f0b293e30a7c03bcf Mon Sep 17 00:00:00 2001 From: Steven Bouwkamp Date: Fri, 31 Jul 2026 13:27:18 -0400 Subject: [PATCH 26/28] Wait for RabbitMQ mapped port readiness --- .../Containers/RabbitMqFixture.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/RabbitMqFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/RabbitMqFixture.cs index a6da47ffbf4f..ea2735a029b2 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/RabbitMqFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/RabbitMqFixture.cs @@ -33,9 +33,13 @@ public override IEnumerable> GetEnvironmentVariable protected override async Task InitializeResources(Action registerResource) { // rabbitmq-diagnostics can race startup and create a root-owned Erlang cookie. + // The samples connect through the mapped host port, so wait for that endpoint too. var container = new ContainerBuilder(Image) .WithPortBinding(RabbitMqPort, true) - .WithWaitStrategy(Wait.ForUnixContainer().UntilInternalTcpPortIsAvailable(RabbitMqPort)) + .WithWaitStrategy( + Wait.ForUnixContainer() + .UntilInternalTcpPortIsAvailable(RabbitMqPort) + .UntilExternalTcpPortIsAvailable(RabbitMqPort)) .Build(); registerResource("container", container); From 6e8c4647f818a5bcc9a595bf0eb1aa8aa48b0ec7 Mon Sep 17 00:00:00 2001 From: Steven Bouwkamp Date: Fri, 31 Jul 2026 14:07:10 -0400 Subject: [PATCH 27/28] Use RabbitMQ Testcontainers readiness --- .../Containers/RabbitMqFixture.cs | 19 ++++++++----------- ...ace.TestHelpers.AutoInstrumentation.csproj | 1 + 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/RabbitMqFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/RabbitMqFixture.cs index ea2735a029b2..5574a02c8c79 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/RabbitMqFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/RabbitMqFixture.cs @@ -8,19 +8,20 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using DotNet.Testcontainers.Builders; using DotNet.Testcontainers.Containers; +using Testcontainers.RabbitMq; namespace Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; public class RabbitMqFixture : ContainerFixture { - private const int RabbitMqPort = 5672; private const string Image = "rabbitmq:3-management@sha256:e582c0bc7766f3342496d8485efb5a1df782b5ce3886ad017e2eaae442311f69"; + private const string Username = "guest"; + private const string Password = "guest"; public string Host => Container.Hostname; - public ushort Port => Container.GetMappedPublicPort(RabbitMqPort); + public ushort Port => Container.GetMappedPublicPort(RabbitMqBuilder.RabbitMqPort); private IContainer Container => GetResource("container"); @@ -32,14 +33,10 @@ public override IEnumerable> GetEnvironmentVariable protected override async Task InitializeResources(Action registerResource) { - // rabbitmq-diagnostics can race startup and create a root-owned Erlang cookie. - // The samples connect through the mapped host port, so wait for that endpoint too. - var container = new ContainerBuilder(Image) - .WithPortBinding(RabbitMqPort, true) - .WithWaitStrategy( - Wait.ForUnixContainer() - .UntilInternalTcpPortIsAvailable(RabbitMqPort) - .UntilExternalTcpPortIsAvailable(RabbitMqPort)) + // Preserve the RabbitMQ.Client default credentials used by the samples. + var container = new RabbitMqBuilder(Image) + .WithUsername(Username) + .WithPassword(Password) .Build(); registerResource("container", container); diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Datadog.Trace.TestHelpers.AutoInstrumentation.csproj b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Datadog.Trace.TestHelpers.AutoInstrumentation.csproj index 7c91046db9fa..be3eb5d4de62 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Datadog.Trace.TestHelpers.AutoInstrumentation.csproj +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Datadog.Trace.TestHelpers.AutoInstrumentation.csproj @@ -6,6 +6,7 @@ + From 4acaa81b5d7764e5d078c2f7bd5da737b1586482 Mon Sep 17 00:00:00 2001 From: Steven Bouwkamp Date: Fri, 31 Jul 2026 16:01:16 -0400 Subject: [PATCH 28/28] Retry transient Testcontainers cgroup failures --- .../Containers/AerospikeFixture.cs | 2 +- .../Containers/ContainerFixture.cs | 41 +++++++++++++++++++ .../Containers/CouchbaseFixture.cs | 2 +- .../Containers/KafkaFixture.cs | 4 +- .../Containers/MySql57Fixture.cs | 2 +- .../Containers/MySql8Fixture.cs | 2 +- .../Containers/PostgresFixture.cs | 2 +- .../Containers/RabbitMqFixture.cs | 2 +- .../Containers/ServiceStackRedisFixture.cs | 2 +- .../Containers/SqlServerFixture.cs | 2 +- .../Containers/StackExchangeRedisFixture.cs | 4 +- 11 files changed, 53 insertions(+), 12 deletions(-) diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/AerospikeFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/AerospikeFixture.cs index c81392ae3cb8..7c4112436712 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/AerospikeFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/AerospikeFixture.cs @@ -46,6 +46,6 @@ protected override async Task InitializeResources(Action registe .Build(); registerResource("container", container); - await container.StartAsync(); + await StartContainerAsync(container).ConfigureAwait(false); } } diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ContainerFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ContainerFixture.cs index 1621d88795c8..7740130ed62c 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ContainerFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ContainerFixture.cs @@ -8,13 +8,20 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Threading.Tasks; +using Docker.DotNet; +using DotNet.Testcontainers.Containers; +using Microsoft.Extensions.Logging; using Xunit; namespace Datadog.Trace.TestHelpers.AutoInstrumentation.Containers; public abstract class ContainerFixture : IAsyncLifetime { + private const int MaxContainerStartAttempts = 3; + private static readonly TimeSpan ContainerStartRetryDelay = TimeSpan.FromSeconds(10); + private readonly Dictionary _resources = new(); private readonly List _resourcesForDisposal = []; @@ -39,6 +46,40 @@ public async Task InitializeAsync() protected T GetResource(string key) => (T)_resources[key]; + protected static async Task StartContainerAsync(IContainer container) + { + var attempt = 1; + while (true) + { + try + { + await container.StartAsync().ConfigureAwait(false); + return; + } + catch (DockerApiException exception) when (attempt < MaxContainerStartAttempts && IsTransientSystemdCgroupFailure(exception)) + { + container.Logger.LogWarning( + "Docker failed to start container {ContainerId} because its systemd cgroup request was interrupted. Retrying in {RetryDelaySeconds} seconds (attempt {NextAttempt}/{MaxAttempts}).", + container.Id, + ContainerStartRetryDelay.TotalSeconds, + attempt + 1, + MaxContainerStartAttempts); + + attempt++; + await Task.Delay(ContainerStartRetryDelay).ConfigureAwait(false); + } + } + } + + private static bool IsTransientSystemdCgroupFailure(DockerApiException exception) + { + var responseBody = exception.ResponseBody; + return exception.StatusCode == HttpStatusCode.InternalServerError + && responseBody is not null + && responseBody.IndexOf("unable to apply cgroup configuration", StringComparison.Ordinal) >= 0 + && responseBody.IndexOf("Message recipient disconnected from message bus without replying", StringComparison.Ordinal) >= 0; + } + private void RegisterResource(string key, object resource) { _resources.Add(key, resource); diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/CouchbaseFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/CouchbaseFixture.cs index 1ea4e486f38b..4544f96baa4c 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/CouchbaseFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/CouchbaseFixture.cs @@ -90,7 +90,7 @@ protected override async Task InitializeResources(Action registe var container = builder.Build(); registerResource("container", container); - await container.StartAsync().ConfigureAwait(false); + await StartContainerAsync(container).ConfigureAwait(false); var host = dockerHost ?? container.Hostname; var managementPort = useHostNetwork ? ManagementPort : container.GetMappedPublicPort(ManagementPort); var keyValuePort = useHostNetwork ? KeyValuePort : container.GetMappedPublicPort(KeyValuePort); diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/KafkaFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/KafkaFixture.cs index 5e4be654f854..6e30c6cce5d1 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/KafkaFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/KafkaFixture.cs @@ -49,7 +49,7 @@ protected override async Task InitializeResources(Action registe registerResource("zookeeper", zooKeeperContainer); registerResource("container", kafkaContainer); await network.CreateAsync().ConfigureAwait(false); - await zooKeeperContainer.StartAsync().ConfigureAwait(false); - await kafkaContainer.StartAsync().ConfigureAwait(false); + await StartContainerAsync(zooKeeperContainer).ConfigureAwait(false); + await StartContainerAsync(kafkaContainer).ConfigureAwait(false); } } diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql57Fixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql57Fixture.cs index 144590601460..3863f6ae8b75 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql57Fixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql57Fixture.cs @@ -60,7 +60,7 @@ protected override async Task InitializeResources(Action registe .Build(); registerResource("container", container); - await container.StartAsync().ConfigureAwait(false); + await StartContainerAsync(container).ConfigureAwait(false); _container = container; } diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql8Fixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql8Fixture.cs index cbf7201878e3..8b5a0e4aebaf 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql8Fixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/MySql8Fixture.cs @@ -50,6 +50,6 @@ protected override async Task InitializeResources(Action registe .Build(); registerResource("container", container); - await container.StartAsync().ConfigureAwait(false); + await StartContainerAsync(container).ConfigureAwait(false); } } diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/PostgresFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/PostgresFixture.cs index bd112067fd72..a9d68fab4388 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/PostgresFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/PostgresFixture.cs @@ -43,6 +43,6 @@ protected override async Task InitializeResources(Action registe .Build(); registerResource("container", container); - await container.StartAsync().ConfigureAwait(false); + await StartContainerAsync(container).ConfigureAwait(false); } } diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/RabbitMqFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/RabbitMqFixture.cs index 5574a02c8c79..857e0fbc8373 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/RabbitMqFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/RabbitMqFixture.cs @@ -40,6 +40,6 @@ protected override async Task InitializeResources(Action registe .Build(); registerResource("container", container); - await container.StartAsync().ConfigureAwait(false); + await StartContainerAsync(container).ConfigureAwait(false); } } diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ServiceStackRedisFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ServiceStackRedisFixture.cs index 573730c806b8..679f21e63959 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ServiceStackRedisFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/ServiceStackRedisFixture.cs @@ -47,6 +47,6 @@ protected override async Task InitializeResources(Action registe .Build(); registerResource("container", container); - await container.StartAsync().ConfigureAwait(false); + await StartContainerAsync(container).ConfigureAwait(false); } } diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/SqlServerFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/SqlServerFixture.cs index 07eb764f0616..252cf2b9a7f3 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/SqlServerFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/SqlServerFixture.cs @@ -76,7 +76,7 @@ protected override async Task InitializeResources(Action registe } registerResource("container", container); - await container.StartAsync().ConfigureAwait(false); + await StartContainerAsync(container).ConfigureAwait(false); HostAndPort = $"{container.Hostname},{container.GetMappedPublicPort(SqlServerPort)}"; _connectionString = $"Server={HostAndPort};User=sa;Password={Password};TrustServerCertificate=True"; diff --git a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/StackExchangeRedisFixture.cs b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/StackExchangeRedisFixture.cs index 67bf796b613a..14a8c2996e97 100644 --- a/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/StackExchangeRedisFixture.cs +++ b/tracer/test/Datadog.Trace.TestHelpers.AutoInstrumentation/Containers/StackExchangeRedisFixture.cs @@ -91,8 +91,8 @@ protected override async Task InitializeResources(Action registe registerResource("replica", replicaContainer); registerResource("single", singleContainer); await network.CreateAsync().ConfigureAwait(false); - await primaryContainer.StartAsync().ConfigureAwait(false); - await Task.WhenAll(replicaContainer.StartAsync(), singleContainer.StartAsync()).ConfigureAwait(false); + await StartContainerAsync(primaryContainer).ConfigureAwait(false); + await Task.WhenAll(StartContainerAsync(replicaContainer), StartContainerAsync(singleContainer)).ConfigureAwait(false); _primaryEndpoint = new Endpoint(primaryContainer.Hostname, primaryContainer.GetMappedPublicPort(RedisPort)); _replicaEndpoint = new Endpoint(replicaContainer.Hostname, replicaContainer.GetMappedPublicPort(RedisPort));