// // 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.Reflection; using FluentAssertions; using IntegrationTests.Helpers; using Xunit.Abstractions; #if NETFRAMEWORK using System.Net; using FluentAssertions.Extensions; using IntegrationTests.Helpers.Compatibility; #endif namespace IntegrationTests; public class SmokeTests : TestHelper { private const string ServiceName = "TestApplication.Smoke"; public SmokeTests(ITestOutputHelper output) : base("Smoke", output) { SetEnvironmentVariable("OTEL_SERVICE_NAME", ServiceName); } [Fact] [Trait("Category", "EndToEnd")] public void SubmitsTraces() { VerifyTestApplicationInstrumented(); } [Fact] [Trait("Category", "EndToEnd")] public void WhenStartupHookIsNotEnabled() { SetEnvironmentVariable("DOTNET_STARTUP_HOOKS", null); #if NETFRAMEWORK VerifyTestApplicationInstrumented(); #else // on .NET it is required to set DOTNET_STARTUP_HOOKS VerifyTestApplicationNotInstrumented(); #endif } [Fact] [Trait("Category", "EndToEnd")] public void WhenClrProfilerIsNotEnabled() { SetEnvironmentVariable("COR_ENABLE_PROFILING", "0"); SetEnvironmentVariable("CORECLR_ENABLE_PROFILING", "0"); #if NETFRAMEWORK // on .NET Framework it is required to set the CLR .NET Profiler VerifyTestApplicationNotInstrumented(); #else VerifyTestApplicationInstrumented(); #endif } [Fact] [Trait("Category", "EndToEnd")] public void ApplicationIsNotExcluded() { SetEnvironmentVariable("OTEL_DOTNET_AUTO_EXCLUDE_PROCESSES", "dotnet,dotnet.exe"); VerifyTestApplicationInstrumented(); } [Fact] [Trait("Category", "EndToEnd")] public void ApplicationIsExcluded() { SetEnvironmentVariable("OTEL_DOTNET_AUTO_EXCLUDE_PROCESSES", $"dotnet,dotnet.exe,{EnvironmentHelper.FullTestApplicationName},{EnvironmentHelper.FullTestApplicationName}.exe"); VerifyTestApplicationNotInstrumented(); } [Fact] [Trait("Category", "EndToEnd")] public void SubmitMetrics() { using var collector = new MockMetricsCollector(Output); SetExporter(collector); collector.Expect("MyCompany.MyProduct.MyLibrary", metric => metric.Name == "MyFruitCounter"); SetEnvironmentVariable("OTEL_DOTNET_AUTO_METRICS_ADDITIONAL_SOURCES", "MyCompany.MyProduct.MyLibrary"); RunTestApplication(); collector.AssertExpectations(); } [Fact] [Trait("Category", "EndToEnd")] public void TracesResource() { using var collector = new MockSpansCollector(Output); SetExporter(collector); collector.ResourceExpector.Expect("service.name", ServiceName); // this is set via env var and App.config, but env var has precedence #if NETFRAMEWORK collector.ResourceExpector.Expect("deployment.environment", "test"); // this is set via App.config #endif collector.ResourceExpector.Expect("telemetry.sdk.name", "opentelemetry"); collector.ResourceExpector.Expect("telemetry.sdk.language", "dotnet"); collector.ResourceExpector.Expect("telemetry.sdk.version", typeof(OpenTelemetry.Resources.Resource).Assembly.GetCustomAttribute()!.InformationalVersion.Split('+')[0]); collector.ResourceExpector.Expect("telemetry.auto.version", OpenTelemetry.AutoInstrumentation.Constants.Tracer.Version); EnableOnlyHttpClientTraceInstrumentation(); SetEnvironmentVariable("OTEL_DOTNET_AUTO_TRACES_ADDITIONAL_SOURCES", "MyCompany.MyProduct.MyLibrary"); RunTestApplication(); collector.ResourceExpector.AssertExpectations(); } [Fact] [Trait("Category", "EndToEnd")] public void MetricsResource() { using var collector = new MockMetricsCollector(Output); SetExporter(collector); collector.ResourceExpector.Expect("service.name", ServiceName); collector.ResourceExpector.Expect("telemetry.sdk.name", "opentelemetry"); collector.ResourceExpector.Expect("telemetry.sdk.language", "dotnet"); collector.ResourceExpector.Expect("telemetry.sdk.version", typeof(OpenTelemetry.Resources.Resource).Assembly.GetCustomAttribute()!.InformationalVersion.Split('+')[0]); collector.ResourceExpector.Expect("telemetry.auto.version", OpenTelemetry.AutoInstrumentation.Constants.Tracer.Version); EnableOnlyHttpClientTraceInstrumentation(); SetEnvironmentVariable("OTEL_DOTNET_AUTO_METRICS_ADDITIONAL_SOURCES", "MyCompany.MyProduct.MyLibrary"); RunTestApplication(); collector.ResourceExpector.AssertExpectations(); } #if NET6_0_OR_GREATER // The feature is not supported on .NET Framework [Fact] [Trait("Category", "EndToEnd")] public void LogsResource() { using var collector = new MockLogsCollector(Output); SetExporter(collector); collector.ResourceExpector.Expect("service.name", ServiceName); collector.ResourceExpector.Expect("telemetry.sdk.name", "opentelemetry"); collector.ResourceExpector.Expect("telemetry.sdk.language", "dotnet"); collector.ResourceExpector.Expect("telemetry.sdk.version", typeof(OpenTelemetry.Resources.Resource).Assembly.GetCustomAttribute()!.InformationalVersion.Split('+')[0]); collector.ResourceExpector.Expect("telemetry.auto.version", OpenTelemetry.AutoInstrumentation.Constants.Tracer.Version); EnableOnlyHttpClientTraceInstrumentation(); EnableBytecodeInstrumentation(); RunTestApplication(); collector.ResourceExpector.AssertExpectations(); } #endif [Fact] [Trait("Category", "EndToEnd")] public void OtlpTracesExporter() { using var collector = new MockSpansCollector(Output); SetExporter(collector); collector.Expect("MyCompany.MyProduct.MyLibrary", span => span.Name == "SayHello"); EnableOnlyHttpClientTraceInstrumentation(); SetEnvironmentVariable("OTEL_DOTNET_AUTO_TRACES_ADDITIONAL_SOURCES", "MyCompany.MyProduct.MyLibrary"); RunTestApplication(); collector.AssertExpectations(); } [Fact] [Trait("Category", "EndToEnd")] public void ZipkinExporter() { using var collector = new MockZipkinCollector(Output); collector.Expect(span => span.Name == "SayHello" && span.Tags?.GetValueOrDefault("otel.library.name") == "MyCompany.MyProduct.MyLibrary"); EnableOnlyHttpClientTraceInstrumentation(); SetEnvironmentVariable("OTEL_TRACES_EXPORTER", "zipkin"); SetEnvironmentVariable("OTEL_EXPORTER_ZIPKIN_ENDPOINT", $"http://localhost:{collector.Port}/api/v2/spans"); SetEnvironmentVariable("OTEL_DOTNET_AUTO_TRACES_ADDITIONAL_SOURCES", "MyCompany.MyProduct.MyLibrary"); RunTestApplication(); collector.AssertExpectations(); } #if NETFRAMEWORK // The test is flaky on Linux and macOS, because of https://github.com/dotnet/runtime/issues/28658#issuecomment-462062760 [Fact] [Trait("Category", "EndToEnd")] public void PrometheusExporter() { EnableOnlyHttpClientTraceInstrumentation(); SetEnvironmentVariable("LONG_RUNNING", "true"); SetEnvironmentVariable("OTEL_METRICS_EXPORTER", "prometheus"); SetEnvironmentVariable("OTEL_DOTNET_AUTO_METRICS_ADDITIONAL_SOURCES", "MyCompany.MyProduct.MyLibrary"); const string defaultPrometheusMetricsEndpoint = "http://localhost:9464/metrics"; using var process = StartTestApplication(); using var helper = new ProcessHelper(process); try { var assert = async () => { var httpClient = new HttpClient { Timeout = 5.Seconds() }; var response = await httpClient.GetAsync(defaultPrometheusMetricsEndpoint); response.StatusCode.Should().Be(HttpStatusCode.OK); var content = await response.Content.ReadAsStringAsync(); Output.WriteLine("Raw metrics from Prometheus:"); Output.WriteLine(content); content.Should().Contain("TYPE ", "should export any metric"); }; assert.Should().NotThrowAfterAsync( waitTime: 1.Minutes(), pollInterval: 1.Seconds()); } finally { if (helper?.Process != null && !helper.Process.HasExited) { helper.Process.Kill(); helper.Process.WaitForExit(); Output.WriteLine("ProcessId: " + helper.Process.Id); Output.WriteLine("Exit Code: " + helper.Process.ExitCode); Output.WriteResult(helper); } } } #endif #if NET6_0_OR_GREATER // The feature is not supported on .NET Framework [Fact] [Trait("Category", "EndToEnd")] public void SubmitLogs() { using var collector = new MockLogsCollector(Output); SetExporter(collector); collector.Expect(logRecord => Convert.ToString(logRecord.Body) == "{ \"stringValue\": \"Example log message\" }"); EnableOnlyHttpClientTraceInstrumentation(); SetEnvironmentVariable("OTEL_DOTNET_AUTO_LOGS_INCLUDE_FORMATTED_MESSAGE", "true"); EnableBytecodeInstrumentation(); RunTestApplication(); collector.AssertExpectations(); } [Theory] [InlineData("OTEL_DOTNET_AUTO_INSTRUMENTATION_ENABLED", "false")] [InlineData("OTEL_DOTNET_AUTO_LOGS_INSTRUMENTATION_ENABLED", "false")] [InlineData("OTEL_DOTNET_AUTO_LOGS_ENABLED", "false")] [InlineData("OTEL_LOGS_EXPORTER", "none")] [Trait("Category", "EndToEnd")] public void LogsNoneInstrumentations(string envVarName, string envVarVal) { using var collector = new MockLogsCollector(Output); SetExporter(collector); EnableOnlyHttpClientTraceInstrumentation(); SetEnvironmentVariable(envVarName, envVarVal); EnableBytecodeInstrumentation(); RunTestApplication(); collector.AssertEmpty(); } #endif [Theory] [InlineData("OTEL_DOTNET_AUTO_INSTRUMENTATION_ENABLED", "false")] [InlineData("OTEL_DOTNET_AUTO_TRACES_INSTRUMENTATION_ENABLED", "false")] [InlineData("OTEL_TRACES_EXPORTER", "none")] [Trait("Category", "EndToEnd")] public void TracesNoneInstrumentations(string envVarName, string envVarVal) { using var collector = new MockSpansCollector(Output); SetExporter(collector); SetEnvironmentVariable(envVarName, envVarVal); RunTestApplication(); collector.AssertEmpty(); } [Theory] [InlineData("OTEL_DOTNET_AUTO_INSTRUMENTATION_ENABLED", "false")] [InlineData("OTEL_DOTNET_AUTO_METRICS_INSTRUMENTATION_ENABLED", "false")] [InlineData("OTEL_DOTNET_AUTO_METRICS_ENABLED", "false")] [InlineData("OTEL_METRICS_EXPORTER", "none")] [Trait("Category", "EndToEnd")] public void MetricsNoneInstrumentations(string envVarName, string envVarVal) { using var collector = new MockMetricsCollector(Output); SetExporter(collector); EnableOnlyHttpClientTraceInstrumentation(); SetEnvironmentVariable(envVarName, envVarVal); RunTestApplication(); collector.AssertEmpty(); } [Fact] [Trait("Category", "EndToEnd")] public void LogsDisabledInstrumentation() { using var collector = new MockLogsCollector(Output); SetEnvironmentVariable("OTEL_DOTNET_AUTO_LOGS_DISABLED_INSTRUMENTATIONS", "ILogger"); EnableOnlyHttpClientTraceInstrumentation(); EnableBytecodeInstrumentation(); RunTestApplication(); collector.AssertEmpty(); } [Fact] [Trait("Category", "EndToEnd")] public void MetricsDisabledInstrumentation() { using var collector = new MockMetricsCollector(Output); SetEnvironmentVariable("OTEL_DOTNET_AUTO_METRICS_HTTPCLIENT_INSTRUMENTATION_ENABLED", "false"); EnableOnlyHttpClientTraceInstrumentation(); EnableBytecodeInstrumentation(); RunTestApplication(); collector.AssertEmpty(); } [Fact] [Trait("Category", "EndToEnd")] public void TracesDisabledInstrumentation() { using var collector = new MockSpansCollector(Output); SetExporter(collector); SetEnvironmentVariable("OTEL_DOTNET_AUTO_TRACES_HTTPCLIENT_INSTRUMENTATION_ENABLED", "false"); RunTestApplication(); collector.AssertEmpty(); } [Fact] [Trait("Category", "EndToEnd")] public void ApplicationFailFastDisabled() { SetEnvironmentVariable("OTEL_DOTNET_AUTO_EXCLUDE_PROCESSES", $"dotnet,dotnet.exe,{EnvironmentHelper.FullTestApplicationName},{EnvironmentHelper.FullTestApplicationName}.exe"); SetEnvironmentVariable("OTEL_DOTNET_AUTO_FAIL_FAST_ENABLED", "false"); VerifyTestApplicationNotInstrumented(); } [Theory] [Trait("Category", "EndToEnd")] [InlineData("OTEL_TRACES_EXPORTER", "non-supported")] [InlineData("OTEL_DOTNET_AUTO_EXCLUDE_PROCESSES", $"dotnet,dotnet.exe,TestApplication.Smoke,TestApplication.Smoke.exe")] public void ApplicationFailFastEnabled(string additionalVariableKey, string additionalVariableValue) { SetEnvironmentVariable("OTEL_DOTNET_AUTO_FAIL_FAST_ENABLED", "true"); SetEnvironmentVariable(additionalVariableKey, additionalVariableValue); using var process = StartTestApplication(); using var helper = new ProcessHelper(process); process.Should().NotBeNull(); var processTimeout = !process!.WaitForExit((int)TestTimeout.ProcessExit.TotalMilliseconds); if (processTimeout) { process.Kill(); } Output.WriteResult(helper); processTimeout.Should().BeFalse(); process!.ExitCode.Should().NotBe(0); } private void VerifyTestApplicationInstrumented() { using var collector = new MockSpansCollector(Output); SetExporter(collector); collector.Expect("MyCompany.MyProduct.MyLibrary"); #if NETFRAMEWORK collector.Expect("OpenTelemetry.Instrumentation.Http.HttpWebRequest"); #elif NET7_0_OR_GREATER collector.Expect("System.Net.Http"); #else collector.Expect("OpenTelemetry.Instrumentation.Http.HttpClient"); #endif EnableOnlyHttpClientTraceInstrumentation(); SetEnvironmentVariable("OTEL_DOTNET_AUTO_TRACES_ADDITIONAL_SOURCES", "MyCompany.MyProduct.MyLibrary"); RunTestApplication(); collector.AssertExpectations(); } private void VerifyTestApplicationNotInstrumented() { using var collector = new MockSpansCollector(Output); SetExporter(collector); EnableOnlyHttpClientTraceInstrumentation(); SetEnvironmentVariable("OTEL_DOTNET_AUTO_TRACES_ADDITIONAL_SOURCES", "MyCompany.MyProduct.MyLibrary"); RunTestApplication(); collector.AssertEmpty(); } private void EnableOnlyHttpClientTraceInstrumentation() { SetEnvironmentVariable("OTEL_DOTNET_AUTO_TRACES_INSTRUMENTATION_ENABLED", "false"); SetEnvironmentVariable("OTEL_DOTNET_AUTO_TRACES_HTTPCLIENT_INSTRUMENTATION_ENABLED", "true"); } }