Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Enable SuccessfulCommandTest by leveraging Testcontainers #4198

Merged
merged 11 commits into from
Mar 29, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions build/Common.nonprod.props
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
<MicrosoftExtensionsLoggingAbstractionsPkgVer>[6.0.0,)</MicrosoftExtensionsLoggingAbstractionsPkgVer>
<MicrosoftNETTestSdkPkgVer>[17.4.1]</MicrosoftNETTestSdkPkgVer>
<MoqPkgVer>[4.18.3,5.0)</MoqPkgVer>
<TestcontainersPkgVer>3.0.0</TestcontainersPkgVer>
<RabbitMQClientPkgVer>[6.4.0,7.0)</RabbitMQClientPkgVer>
<RuntimeInstrumentationPkgVer>[1.0.0,2.0)</RuntimeInstrumentationPkgVer>
<SwashbuckleAspNetCorePkgVer>[6.4.0]</SwashbuckleAspNetCorePkgVer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
</PropertyGroup>

<ItemGroup>
<Compile Include="$(RepoRoot)\test\OpenTelemetry.Tests\Shared\EnabledOnDockerPlatformTheoryAttribute.cs" Link="EnabledOnDockerPlatformTheoryAttribute.cs" />
<Compile Include="$(RepoRoot)\test\OpenTelemetry.Tests\Shared\SkipUnlessEnvVarFoundTheoryAttribute.cs" Link="SkipUnlessEnvVarFoundTheoryAttribute.cs" />
<Compile Include="$(RepoRoot)\test\OpenTelemetry.Tests\Shared\EventSourceTestHelper.cs" Link="EventSourceTestHelper.cs" />
<Compile Include="$(RepoRoot)\test\OpenTelemetry.Tests\Shared\TestEventListener.cs" Link="TestEventListener.cs" />
Expand All @@ -21,6 +22,8 @@
<PackageReference Include="Microsoft.Data.SqlClient" Version="2.1.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPkgVer)" />
<PackageReference Include="Moq" Version="$(MoqPkgVer)" />
<PackageReference Include="Testcontainers.MsSql" Version="$(TestcontainersPkgVer)" />
<PackageReference Include="Testcontainers.SqlEdge" Version="$(TestcontainersPkgVer)" />
<PackageReference Include="xunit" Version="$(XUnitPkgVer)" />
<PackageReference Include="xunit.runner.visualstudio" Version="$(XUnitRunnerVisualStudioPkgVer)">
<PrivateAssets>all</PrivateAssets>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// <copyright file="SqlClientIntegrationTests.cs" company="OpenTelemetry Authors">
alanwest marked this conversation as resolved.
Show resolved Hide resolved
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

using System.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")]
HofmeisterAn marked this conversation as resolved.
Show resolved Hide resolved
[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<Activity>();
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.");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,8 @@ To use Docker...
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()
Expand Down Expand Up @@ -81,79 +78,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<Activity>();
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]
Expand Down Expand Up @@ -384,7 +308,7 @@ public void ShouldNotCollectTelemetryAndShouldNotPropagateExceptionWhenFilterThr
}
#endif

private static void VerifyActivityData(
internal static void VerifyActivityData(
CommandType commandType,
string commandText,
bool captureStoredProcedureCommandName,
Expand Down Expand Up @@ -464,7 +388,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(
Expand All @@ -473,7 +397,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");

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// <copyright file="EnabledOnDockerPlatformTheoryAttribute.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

using System.Diagnostics;
using System.Text;
using Xunit;

namespace OpenTelemetry.Tests;

/// <summary>
/// This <see cref="TheoryAttribute" /> skips tests if the required Docker engine is not available.
/// </summary>
internal class EnabledOnDockerPlatformTheoryAttribute : TheoryAttribute
{
/// <summary>
/// Initializes a new instance of the <see cref="EnabledOnDockerPlatformTheoryAttribute" /> class.
/// </summary>
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
{
/// <summary>
/// Docker Linux engine.
/// </summary>
Linux,

/// <summary>
/// Docker Windows engine.
/// </summary>
Windows,
}
}