From 533513f65b5abb007f51eb6a6c9335053522a200 Mon Sep 17 00:00:00 2001
From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com>
Date: Thu, 30 Mar 2023 01:16:00 +0200
Subject: [PATCH] feat: Enable SuccessfulCommandTest by leveraging
Testcontainers (#4198)
Co-authored-by: Alan West <3676547+alanwest@users.noreply.github.com>
---
.github/workflows/integration.yml | 12 --
build/Common.nonprod.props | 1 +
.../Dockerfile | 22 ---
...try.Instrumentation.SqlClient.Tests.csproj | 3 +
.../SqlClientIntegrationTests.cs | 131 ++++++++++++++++++
.../SqlClientTests.cs | 90 +-----------
.../docker-compose.yml | 25 ----
.../EnabledOnDockerPlatformTheoryAttribute.cs | 86 ++++++++++++
8 files changed, 224 insertions(+), 146 deletions(-)
delete mode 100644 test/OpenTelemetry.Instrumentation.SqlClient.Tests/Dockerfile
create mode 100644 test/OpenTelemetry.Instrumentation.SqlClient.Tests/SqlClientIntegrationTests.cs
delete mode 100644 test/OpenTelemetry.Instrumentation.SqlClient.Tests/docker-compose.yml
create mode 100644 test/OpenTelemetry.Tests/Shared/EnabledOnDockerPlatformTheoryAttribute.cs
diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index f28c7a51e3e..3127c132789 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -11,18 +11,6 @@ on:
- '**.md'
jobs:
- sql-test:
- runs-on: ubuntu-latest
- strategy:
- fail-fast: false
- matrix:
- version: [net6.0,net7.0]
- steps:
- - uses: actions/checkout@v3
-
- - name: Run sql docker-compose.integration
- run: docker-compose --file=test/OpenTelemetry.Instrumentation.SqlClient.Tests/docker-compose.yml --file=build/docker-compose.${{ matrix.version }}.yml --project-directory=. up --exit-code-from=tests --build
-
w3c-trace-context-test:
runs-on: ubuntu-latest
strategy:
diff --git a/build/Common.nonprod.props b/build/Common.nonprod.props
index fc4cb09c5a8..2f550fb8ff0 100644
--- a/build/Common.nonprod.props
+++ b/build/Common.nonprod.props
@@ -42,6 +42,7 @@
[6.0.0,)
[17.4.1]
[4.18.3,5.0)
+ 3.0.0
[6.4.0,7.0)
[1.0.0,2.0)
[6.4.0]
diff --git a/test/OpenTelemetry.Instrumentation.SqlClient.Tests/Dockerfile b/test/OpenTelemetry.Instrumentation.SqlClient.Tests/Dockerfile
deleted file mode 100644
index 1083201dec8..00000000000
--- a/test/OpenTelemetry.Instrumentation.SqlClient.Tests/Dockerfile
+++ /dev/null
@@ -1,22 +0,0 @@
-# Create a container for running the OpenTelemetry SQL Client integration tests.
-# This should be run from the root of the repo:
-# docker build --file test/OpenTelemetry.Instrumentation.SqlClient.Tests/Dockerfile .
-
-ARG BUILD_SDK_VERSION=7.0
-ARG TEST_SDK_VERSION=7.0
-
-FROM mcr.microsoft.com/dotnet/sdk:${BUILD_SDK_VERSION} AS build
-ARG PUBLISH_CONFIGURATION=Release
-ARG PUBLISH_FRAMEWORK=net7.0
-WORKDIR /repo
-COPY . ./
-RUN ls -la /repo
-WORKDIR "/repo/test/OpenTelemetry.Instrumentation.SqlClient.Tests"
-RUN dotnet publish "OpenTelemetry.Instrumentation.SqlClient.Tests.csproj" -c "${PUBLISH_CONFIGURATION}" -f "${PUBLISH_FRAMEWORK}" -o /drop -p:IntegrationBuild=true -p:TARGET_FRAMEWORK=${PUBLISH_FRAMEWORK}
-
-FROM mcr.microsoft.com/dotnet/sdk:${TEST_SDK_VERSION} AS final
-ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.7.3/wait /wait
-RUN chmod +x /wait
-WORKDIR /test
-COPY --from=build /drop .
-ENTRYPOINT ["dotnet", "vstest", "OpenTelemetry.Instrumentation.SqlClient.Tests.dll", "--logger:console;verbosity=detailed"]
diff --git a/test/OpenTelemetry.Instrumentation.SqlClient.Tests/OpenTelemetry.Instrumentation.SqlClient.Tests.csproj b/test/OpenTelemetry.Instrumentation.SqlClient.Tests/OpenTelemetry.Instrumentation.SqlClient.Tests.csproj
index 3d0c9a1acba..67a1af302c9 100644
--- a/test/OpenTelemetry.Instrumentation.SqlClient.Tests/OpenTelemetry.Instrumentation.SqlClient.Tests.csproj
+++ b/test/OpenTelemetry.Instrumentation.SqlClient.Tests/OpenTelemetry.Instrumentation.SqlClient.Tests.csproj
@@ -11,6 +11,7 @@
+
@@ -21,6 +22,8 @@
+
+
all
diff --git a/test/OpenTelemetry.Instrumentation.SqlClient.Tests/SqlClientIntegrationTests.cs b/test/OpenTelemetry.Instrumentation.SqlClient.Tests/SqlClientIntegrationTests.cs
new file mode 100644
index 00000000000..45f4408d61f
--- /dev/null
+++ b/test/OpenTelemetry.Instrumentation.SqlClient.Tests/SqlClientIntegrationTests.cs
@@ -0,0 +1,131 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System.Data;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using DotNet.Testcontainers.Containers;
+using Microsoft.Data.SqlClient;
+using OpenTelemetry.Tests;
+using OpenTelemetry.Trace;
+using Testcontainers.MsSql;
+using Testcontainers.SqlEdge;
+using Xunit;
+
+namespace OpenTelemetry.Instrumentation.SqlClient.Tests
+{
+ public sealed class SqlClientIntegrationTests : IAsyncLifetime
+ {
+ // The Microsoft SQL Server Docker image is not compatible with ARM devices, such as Macs with Apple Silicon.
+ private readonly IContainer databaseContainer = Architecture.Arm64.Equals(RuntimeInformation.ProcessArchitecture) ? new SqlEdgeBuilder().Build() : new MsSqlBuilder().Build();
+
+ public Task InitializeAsync()
+ {
+ return this.databaseContainer.StartAsync();
+ }
+
+ public Task DisposeAsync()
+ {
+ return this.databaseContainer.DisposeAsync().AsTask();
+ }
+
+ [Trait("CategoryName", "SqlIntegrationTests")]
+ [EnabledOnDockerPlatformTheory(EnabledOnDockerPlatformTheoryAttribute.DockerPlatform.Linux)]
+ [InlineData(CommandType.Text, "select 1/1", false)]
+ [InlineData(CommandType.Text, "select 1/1", false, true)]
+ [InlineData(CommandType.Text, "select 1/0", false, false, true)]
+ [InlineData(CommandType.Text, "select 1/0", false, false, true, false, false)]
+ [InlineData(CommandType.Text, "select 1/0", false, false, true, true, false)]
+ [InlineData(CommandType.StoredProcedure, "sp_who", false)]
+ [InlineData(CommandType.StoredProcedure, "sp_who", true)]
+ public void SuccessfulCommandTest(
+ CommandType commandType,
+ string commandText,
+ bool captureStoredProcedureCommandName,
+ bool captureTextCommandContent = false,
+ bool isFailure = false,
+ bool recordException = false,
+ bool shouldEnrich = true)
+ {
+#if NETFRAMEWORK
+ // Disable things not available on netfx
+ recordException = false;
+ shouldEnrich = false;
+#endif
+
+ var sampler = new TestSampler();
+ var activities = new List();
+ using var tracerProvider = Sdk.CreateTracerProviderBuilder()
+ .SetSampler(sampler)
+ .AddInMemoryExporter(activities)
+ .AddSqlClientInstrumentation(options =>
+ {
+#if !NETFRAMEWORK
+ options.SetDbStatementForStoredProcedure = captureStoredProcedureCommandName;
+ options.SetDbStatementForText = captureTextCommandContent;
+#else
+ options.SetDbStatementForText = captureStoredProcedureCommandName || captureTextCommandContent;
+#endif
+ options.RecordException = recordException;
+ if (shouldEnrich)
+ {
+ options.Enrich = SqlClientTests.ActivityEnrichment;
+ }
+ })
+ .Build();
+
+ using SqlConnection sqlConnection = new SqlConnection(this.GetConnectionString());
+
+ sqlConnection.Open();
+
+ string dataSource = sqlConnection.DataSource;
+
+ sqlConnection.ChangeDatabase("master");
+
+ using SqlCommand sqlCommand = new SqlCommand(commandText, sqlConnection)
+ {
+ CommandType = commandType,
+ };
+
+ try
+ {
+ sqlCommand.ExecuteNonQuery();
+ }
+ catch
+ {
+ }
+
+ Assert.Single(activities);
+ var activity = activities[0];
+
+ SqlClientTests.VerifyActivityData(commandType, commandText, captureStoredProcedureCommandName, captureTextCommandContent, isFailure, recordException, shouldEnrich, dataSource, activity);
+ SqlClientTests.VerifySamplingParameters(sampler.LatestSamplingParameters);
+ }
+
+ private string GetConnectionString()
+ {
+ switch (this.databaseContainer)
+ {
+ case SqlEdgeContainer container:
+ return container.GetConnectionString();
+ case MsSqlContainer container:
+ return container.GetConnectionString();
+ default:
+ throw new InvalidOperationException($"Container type ${this.databaseContainer.GetType().Name} not supported.");
+ }
+ }
+ }
+}
diff --git a/test/OpenTelemetry.Instrumentation.SqlClient.Tests/SqlClientTests.cs b/test/OpenTelemetry.Instrumentation.SqlClient.Tests/SqlClientTests.cs
index b72bcbacf5a..b7124668b22 100644
--- a/test/OpenTelemetry.Instrumentation.SqlClient.Tests/SqlClientTests.cs
+++ b/test/OpenTelemetry.Instrumentation.SqlClient.Tests/SqlClientTests.cs
@@ -27,19 +27,8 @@ namespace OpenTelemetry.Instrumentation.SqlClient.Tests
{
public class SqlClientTests : IDisposable
{
- /*
- To run the integration tests, set the OTEL_SQLCONNECTIONSTRING machine-level environment variable to a valid Sql Server connection string.
-
- To use Docker...
- 1) Run: docker run -d --name sql2019 -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=Pass@word" -p 5433:1433 mcr.microsoft.com/mssql/server:2019-latest
- 2) Set OTEL_SQLCONNECTIONSTRING as: Data Source=127.0.0.1,5433; User ID=sa; Password=Pass@word
- */
-
- private const string SqlConnectionStringEnvVarName = "OTEL_SQLCONNECTIONSTRING";
private const string TestConnectionString = "Data Source=(localdb)\\MSSQLLocalDB;Database=master";
- private static readonly string SqlConnectionString = SkipUnlessEnvVarFoundTheoryAttribute.GetEnvironmentVariable(SqlConnectionStringEnvVarName);
-
private readonly FakeSqlClientDiagnosticSource fakeSqlClientDiagnosticSource;
public SqlClientTests()
@@ -81,79 +70,6 @@ public void SqlClient_NamedOptions()
Assert.Equal(1, namedExporterOptionsConfigureOptionsInvocations);
}
- [Trait("CategoryName", "SqlIntegrationTests")]
- [SkipUnlessEnvVarFoundTheory(SqlConnectionStringEnvVarName)]
- [InlineData(CommandType.Text, "select 1/1", false)]
- [InlineData(CommandType.Text, "select 1/1", false, true)]
- [InlineData(CommandType.Text, "select 1/0", false, false, true)]
- [InlineData(CommandType.Text, "select 1/0", false, false, true, false, false)]
- [InlineData(CommandType.Text, "select 1/0", false, false, true, true, false)]
- [InlineData(CommandType.StoredProcedure, "sp_who", false)]
- [InlineData(CommandType.StoredProcedure, "sp_who", true)]
- public void SuccessfulCommandTest(
- CommandType commandType,
- string commandText,
- bool captureStoredProcedureCommandName,
- bool captureTextCommandContent = false,
- bool isFailure = false,
- bool recordException = false,
- bool shouldEnrich = true)
- {
-#if NETFRAMEWORK
- // Disable things not available on netfx
- recordException = false;
- shouldEnrich = false;
-#endif
-
- var sampler = new TestSampler();
- var activities = new List();
- using var tracerProvider = Sdk.CreateTracerProviderBuilder()
- .SetSampler(sampler)
- .AddInMemoryExporter(activities)
- .AddSqlClientInstrumentation(options =>
- {
-#if !NETFRAMEWORK
- options.SetDbStatementForStoredProcedure = captureStoredProcedureCommandName;
- options.SetDbStatementForText = captureTextCommandContent;
-#else
- options.SetDbStatementForText = captureStoredProcedureCommandName || captureTextCommandContent;
-#endif
- options.RecordException = recordException;
- if (shouldEnrich)
- {
- options.Enrich = ActivityEnrichment;
- }
- })
- .Build();
-
- using SqlConnection sqlConnection = new SqlConnection(SqlConnectionString);
-
- sqlConnection.Open();
-
- string dataSource = sqlConnection.DataSource;
-
- sqlConnection.ChangeDatabase("master");
-
- using SqlCommand sqlCommand = new SqlCommand(commandText, sqlConnection)
- {
- CommandType = commandType,
- };
-
- try
- {
- sqlCommand.ExecuteNonQuery();
- }
- catch
- {
- }
-
- Assert.Single(activities);
- var activity = activities[0];
-
- VerifyActivityData(commandType, commandText, captureStoredProcedureCommandName, captureTextCommandContent, isFailure, recordException, shouldEnrich, dataSource, activity);
- VerifySamplingParameters(sampler.LatestSamplingParameters);
- }
-
// DiagnosticListener-based instrumentation is only available on .NET Core
#if !NETFRAMEWORK
[Theory]
@@ -384,7 +300,7 @@ public void ShouldNotCollectTelemetryAndShouldNotPropagateExceptionWhenFilterThr
}
#endif
- private static void VerifyActivityData(
+ internal static void VerifyActivityData(
CommandType commandType,
string commandText,
bool captureStoredProcedureCommandName,
@@ -464,7 +380,7 @@ private static void VerifyActivityData(
Assert.Equal(dataSource, activity.GetTagValue(SemanticConventions.AttributePeerService));
}
- private static void VerifySamplingParameters(SamplingParameters samplingParameters)
+ internal static void VerifySamplingParameters(SamplingParameters samplingParameters)
{
Assert.NotNull(samplingParameters.Tags);
Assert.Contains(
@@ -473,7 +389,7 @@ private static void VerifySamplingParameters(SamplingParameters samplingParamete
&& (string)kvp.Value == SqlActivitySourceHelper.MicrosoftSqlServerDatabaseSystemName);
}
- private static void ActivityEnrichment(Activity activity, string method, object obj)
+ internal static void ActivityEnrichment(Activity activity, string method, object obj)
{
activity.SetTag("enriched", "yes");
diff --git a/test/OpenTelemetry.Instrumentation.SqlClient.Tests/docker-compose.yml b/test/OpenTelemetry.Instrumentation.SqlClient.Tests/docker-compose.yml
deleted file mode 100644
index 2c4b2b2763a..00000000000
--- a/test/OpenTelemetry.Instrumentation.SqlClient.Tests/docker-compose.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-# Start a sql container and then run OpenTelemetry sql integration tests.
-# This should be run from the root of the repo:
-# opentelemetry>docker-compose --file=test/OpenTelemetry.Instrumentation.SqlClient.Tests/docker-compose.yml --project-directory=. up --exit-code-from=tests --build
-version: '3.7'
-
-services:
- sql:
- image: mcr.microsoft.com/mssql/server:2019-latest
- environment:
- - ACCEPT_EULA=Y
- # Note: This password is for the ephemeral sql instance running in the container used for tests. Nothing that needs to be handled securely.
- - SA_PASSWORD=Pass@word18
- ports:
- - "1433:1433"
-
- tests:
- build:
- context: .
- dockerfile: ./test/OpenTelemetry.Instrumentation.SqlClient.Tests/Dockerfile
- entrypoint: ["bash", "-c", "/wait && dotnet vstest OpenTelemetry.Instrumentation.SqlClient.Tests.dll --TestCaseFilter:CategoryName=SqlIntegrationTests"]
- environment:
- - OTEL_SQLCONNECTIONSTRING=Data Source=sql; User ID=sa; Password=Pass@word18
- - WAIT_HOSTS=sql:1433
- depends_on:
- - sql
diff --git a/test/OpenTelemetry.Tests/Shared/EnabledOnDockerPlatformTheoryAttribute.cs b/test/OpenTelemetry.Tests/Shared/EnabledOnDockerPlatformTheoryAttribute.cs
new file mode 100644
index 00000000000..fd99e0c4499
--- /dev/null
+++ b/test/OpenTelemetry.Tests/Shared/EnabledOnDockerPlatformTheoryAttribute.cs
@@ -0,0 +1,86 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System.Diagnostics;
+using System.Text;
+using Xunit;
+
+namespace OpenTelemetry.Tests;
+
+///
+/// This skips tests if the required Docker engine is not available.
+///
+internal class EnabledOnDockerPlatformTheoryAttribute : TheoryAttribute
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public EnabledOnDockerPlatformTheoryAttribute(DockerPlatform dockerPlatform)
+ {
+ const string executable = "docker";
+
+ var stdout = new StringBuilder();
+ var stderr = new StringBuilder();
+
+ void AppendStdout(object sender, DataReceivedEventArgs e) => stdout.Append(e.Data);
+ void AppendStderr(object sender, DataReceivedEventArgs e) => stderr.Append(e.Data);
+
+ var processStartInfo = new ProcessStartInfo();
+ processStartInfo.FileName = executable;
+ processStartInfo.Arguments = string.Join(" ", "version", "--format '{{.Server.Os}}'");
+ processStartInfo.RedirectStandardOutput = true;
+ processStartInfo.RedirectStandardError = true;
+ processStartInfo.UseShellExecute = false;
+
+ var process = new Process();
+ process.StartInfo = processStartInfo;
+ process.OutputDataReceived += AppendStdout;
+ process.ErrorDataReceived += AppendStderr;
+
+ try
+ {
+ process.Start();
+ process.BeginOutputReadLine();
+ process.BeginErrorReadLine();
+ process.WaitForExit();
+ }
+ finally
+ {
+ process.OutputDataReceived -= AppendStdout;
+ process.ErrorDataReceived -= AppendStderr;
+ }
+
+ if (0.Equals(process.ExitCode) && stdout.ToString().Contains(dockerPlatform.ToString().ToLowerInvariant()))
+ {
+ return;
+ }
+
+ this.Skip = $"The Docker {dockerPlatform} engine is not available.";
+ }
+
+ public enum DockerPlatform
+ {
+ ///
+ /// Docker Linux engine.
+ ///
+ Linux,
+
+ ///
+ /// Docker Windows engine.
+ ///
+ Windows,
+ }
+}