From a92da7a62410e2845f0a3fde281d95490a3c773b Mon Sep 17 00:00:00 2001 From: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Wed, 28 Aug 2024 10:03:01 -0500 Subject: [PATCH 1/5] Updated instrumentation for Oracle to support newer versions; created initial Console MFA tests for Oracle --- .../Providers/Wrapper/Sql/Instrumentation.xml | 13 +- .../MFALatestPackages.csproj | 5 + .../MultiFunctionApplicationHelpers.csproj | 5 + ...nOracleManagedDataAccessClientExerciser.cs | 122 ++++++++++++ .../Oracle/ModernOracleTests.cs | 179 ++++++++++++++++++ 5 files changed, 322 insertions(+), 2 deletions(-) create mode 100644 tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Oracle/ModernOracleManagedDataAccessClientExerciser.cs create mode 100644 tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/ModernOracleTests.cs diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Sql/Instrumentation.xml b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Sql/Instrumentation.xml index 386c2adcf8..0ead5dfaf6 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Sql/Instrumentation.xml +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Sql/Instrumentation.xml @@ -70,13 +70,22 @@ SPDX-License-Identifier: Apache-2.0 - - + + + + + + + + + + + diff --git a/tests/Agent/IntegrationTests/SharedApplications/Common/MFALatestPackages/MFALatestPackages.csproj b/tests/Agent/IntegrationTests/SharedApplications/Common/MFALatestPackages/MFALatestPackages.csproj index b30c668a81..5526da8af0 100644 --- a/tests/Agent/IntegrationTests/SharedApplications/Common/MFALatestPackages/MFALatestPackages.csproj +++ b/tests/Agent/IntegrationTests/SharedApplications/Common/MFALatestPackages/MFALatestPackages.csproj @@ -66,6 +66,11 @@ + + + + + diff --git a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/MultiFunctionApplicationHelpers.csproj b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/MultiFunctionApplicationHelpers.csproj index 4fa25338c4..fb5a5929a8 100644 --- a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/MultiFunctionApplicationHelpers.csproj +++ b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/MultiFunctionApplicationHelpers.csproj @@ -80,6 +80,11 @@ + + + + + diff --git a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Oracle/ModernOracleManagedDataAccessClientExerciser.cs b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Oracle/ModernOracleManagedDataAccessClientExerciser.cs new file mode 100644 index 0000000000..ddbe6ee4b9 --- /dev/null +++ b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Oracle/ModernOracleManagedDataAccessClientExerciser.cs @@ -0,0 +1,122 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +// can only test "modern" oracle on net472+ and .NET6/8/+ +#if NET472_OR_GREATER || NET +using System; +using OracleConfiguration = NewRelic.Agent.IntegrationTests.Shared.OracleConfiguration; +using Oracle.ManagedDataAccess.Client; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using NewRelic.Agent.IntegrationTests.Shared.ReflectionHelpers; +using NewRelic.Api.Agent; + + +namespace MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle +{ + [Library] + public class ModernOracleManagedDataAccessClientExerciser : IDisposable + { + private const string CreateHotelTableOracleSql = "CREATE TABLE {0} (HOTEL_ID INT NOT NULL, BOOKING_DATE DATE NOT NULL, " + + "ROOMS_TAKEN INT DEFAULT 0, PRIMARY KEY (HOTEL_ID, BOOKING_DATE))"; + private const string DropHotelTableOracleSql = "DROP TABLE {0}"; + private const string InsertHotelOracleSql = "INSERT INTO {0} (HOTEL_ID, BOOKING_DATE) VALUES (1, SYSDATE)"; + private const string DeleteHotelOracleSql = "DELETE FROM {0} WHERE HOTEL_ID = 1"; + private const string CountHotelOracleSql = "SELECT COUNT(*) FROM {0}"; + + + private string _tableName; + + [LibraryMethod] + public void Initialize(string tableName) + { + _tableName = tableName; + CreateTable(); + } + + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public string ExerciseSync(string tableName) + { + var teamMembers = new List(); + + var connectionString = OracleConfiguration.OracleConnectionString; + + using (var connection = new OracleConnection(connectionString)) + { + connection.Open(); + + using (var command = new OracleCommand("SELECT DEGREE FROM user_tables WHERE ROWNUM <= 1", connection)) + { + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + teamMembers.Add(reader.GetString(reader.GetOrdinal("DEGREE"))); + } + } + } + + var insertSql = string.Format(InsertHotelOracleSql, tableName); + var countSql = string.Format(CountHotelOracleSql, tableName); + var deleteSql = string.Format(DeleteHotelOracleSql, tableName); + + using (var command = new OracleCommand(insertSql, connection)) + { + var insertCount = command.ExecuteNonQuery(); + } + + using (var command = new OracleCommand(countSql, connection)) + { + var hotelCount = command.ExecuteScalar(); + } + + using (var command = new OracleCommand(deleteSql, connection)) + { + var deleteCount = command.ExecuteNonQuery(); + } + } + + return string.Join(",", teamMembers); + } + + private void CreateTable() + { + var createTable = string.Format(CreateHotelTableOracleSql, _tableName); + + var connectionString = OracleConfiguration.OracleConnectionString; + + using (var connection = new OracleConnection(connectionString)) + { + connection.Open(); + + using (var command = new OracleCommand(createTable, connection)) + { + command.ExecuteNonQuery(); + } + } + } + + private void DropTable() + { + var dropTableSql = string.Format(DropHotelTableOracleSql, _tableName); + + using (var connection = new OracleConnection(OracleConfiguration.OracleConnectionString)) + { + connection.Open(); + + using (var command = new OracleCommand(dropTableSql, connection)) + { + command.ExecuteNonQuery(); + } + } + } + + public void Dispose() + { + DropTable(); + } + } +} +#endif diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/ModernOracleTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/ModernOracleTests.cs new file mode 100644 index 0000000000..9866d1f205 --- /dev/null +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/ModernOracleTests.cs @@ -0,0 +1,179 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.Linq; +using NewRelic.Agent.IntegrationTestHelpers; +using NewRelic.Agent.IntegrationTestHelpers.RemoteServiceFixtures; +using NewRelic.Agent.IntegrationTests.Shared; +using NewRelic.Agent.Tests.TestSerializationHelpers.Models; +using NewRelic.Testing.Assertions; +using Xunit; +using Xunit.Abstractions; + +namespace NewRelic.Agent.UnboundedIntegrationTests.Oracle +{ + public abstract class ModernOracleTestsBase : NewRelicIntegrationTest + where TFixture : ConsoleDynamicMethodFixture + { + private readonly ConsoleDynamicMethodFixture _fixture; + private readonly string _tableName; + + protected ModernOracleTestsBase(TFixture fixture, ITestOutputHelper output) : base(fixture) + { + _fixture = fixture; + _fixture.TestLogger = output; + _tableName = GenerateTableName(); + + _fixture.AddCommand($"ModernOracleManagedDataAccessClientExerciser Initialize {_tableName}"); // creates a new table. The table gets dropped automatically when the exerciser goes out of scope + _fixture.AddCommand($"ModernOracleManagedDataAccessClientExerciser ExerciseSync {_tableName}"); + + _fixture.AddActions + ( + setupConfiguration: () => + { + var configPath = fixture.DestinationNewRelicConfigFilePath; + var configModifier = new NewRelicConfigModifier(configPath); + configModifier.ConfigureFasterMetricsHarvestCycle(15); + configModifier.ConfigureFasterTransactionTracesHarvestCycle(15); + configModifier.ConfigureFasterSqlTracesHarvestCycle(15); + + configModifier.ForceTransactionTraces(); + + CommonUtils.ModifyOrCreateXmlAttributeInNewRelicConfig(configPath, new[] { "configuration", "transactionTracer" }, "explainThreshold", "1"); + + var instrumentationFilePath = $@"{fixture.DestinationNewRelicExtensionsDirectoryPath}\NewRelic.Providers.Wrapper.Sql.Instrumentation.xml"; + CommonUtils.SetAttributeOnTracerFactoryInNewRelicInstrumentation(instrumentationFilePath, "", "enabled", "true"); + }, + exerciseApplication: () => + { + _fixture.AgentLog.WaitForLogLine(AgentLogBase.AgentConnectedLogLineRegex, TimeSpan.FromMinutes(1)); + _fixture.AgentLog.WaitForLogLine(AgentLogBase.SqlTraceDataLogLineRegex, TimeSpan.FromMinutes(1)); + } + ); + + _fixture.Initialize(); + } + + [Fact] + public void Test() + { + var expectedDatastoreCallCount = 4; // SELECT, INSERT, COUNT, and DELETE from GetOracle() above + + //This value is dictated by the query that is being run as part of this test. In this case, we're running a query that returns a single row. + //This results in two calls to read. Therefore the call count for the Iterate metric should be 2. + var expectedIterateCallCount = 2; + + var expectedMetrics = new List + { + new() { metricName = "Datastore/all", callCount = expectedDatastoreCallCount }, + new() { metricName = "Datastore/allOther", callCount = expectedDatastoreCallCount }, + new() { metricName = "Datastore/Oracle/all", callCount = expectedDatastoreCallCount }, + new() { metricName = "Datastore/Oracle/allOther", callCount = expectedDatastoreCallCount }, + new() { metricName = $"Datastore/instance/Oracle/{OracleConfiguration.OracleServer}/{OracleConfiguration.OraclePort}", callCount = expectedDatastoreCallCount}, + new() { metricName = "Datastore/operation/Oracle/select", callCount = 2 }, + new() { metricName = "Datastore/statement/Oracle/user_tables/select", callCount = 1 }, + new() { metricName = "Datastore/statement/Oracle/user_tables/select", callCount = 1, metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.ModernOracleManagedDataAccessClientExerciser/ExerciseSync"}, + new() { metricName = $"Datastore/statement/Oracle/{_tableName}/select", callCount = 1 }, + new() { metricName = $"Datastore/statement/Oracle/{_tableName}/select", callCount = 1, metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.ModernOracleManagedDataAccessClientExerciser/ExerciseSync"}, + new() { metricName = "Datastore/operation/Oracle/insert", callCount = 1 }, + new() { metricName = $"Datastore/statement/Oracle/{_tableName}/insert", callCount = 1 }, + new() { metricName = $"Datastore/statement/Oracle/{_tableName}/insert", callCount = 1, metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.ModernOracleManagedDataAccessClientExerciser/ExerciseSync"}, + new() { metricName = "Datastore/operation/Oracle/delete", callCount = 1 }, + new() { metricName = $"Datastore/statement/Oracle/{_tableName}/delete", callCount = 1 }, + new() { metricName = $"Datastore/statement/Oracle/{_tableName}/delete", callCount = 1, metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.ModernOracleManagedDataAccessClientExerciser/ExerciseSync"}, + + new() { metricName = "DotNet/DatabaseResult/Iterate" , callCount = expectedIterateCallCount }, + new() { metricName = "DotNet/DatabaseResult/Iterate", callCount = expectedIterateCallCount, metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.ModernOracleManagedDataAccessClientExerciser/ExerciseSync"} + }; + var unexpectedMetrics = new List + { + // The datastore operation happened inside a web transaction so there should be no allOther metrics + new() { metricName = "Datastore/allWeb" }, + new() { metricName = "Datastore/Oracle/allWeb"}, + + // The operation metric should not be scoped because the statement metric is scoped instead + new() { metricName = "Datastore/operation/Oracle/select", metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.ModernOracleManagedDataAccessClientExerciser/ExerciseSync" }, + new() { metricName = "Datastore/operation/Oracle/insert", metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.ModernOracleManagedDataAccessClientExerciser/ExerciseSync" }, + new() { metricName = "Datastore/operation/Oracle/delete", metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.ModernOracleManagedDataAccessClientExerciser/ExerciseSync" } + }; + var expectedTransactionTraceSegments = new List + { + "Datastore/statement/Oracle/user_tables/select" + }; + + var expectedTransactionEventIntrinsicAttributes = new List + { + "databaseDuration" + }; + var expectedSqlTraces = new List + { + new() + { + TransactionName = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.ModernOracleManagedDataAccessClientExerciser/ExerciseSync", + Sql = "SELECT DEGREE FROM user_tables WHERE ROWNUM <= ?", + DatastoreMetricName = "Datastore/statement/Oracle/user_tables/select", + HasExplainPlan = false + }, + new() + { + TransactionName = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.ModernOracleManagedDataAccessClientExerciser/ExerciseSync", + Sql = $"SELECT COUNT(*) FROM {_tableName}", + DatastoreMetricName = $"Datastore/statement/Oracle/{_tableName}/select", + + HasExplainPlan = false + }, + new() + { + TransactionName = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.ModernOracleManagedDataAccessClientExerciser/ExerciseSync", + Sql = $"INSERT INTO {_tableName} (HOTEL_ID, BOOKING_DATE) VALUES (?, SYSDATE)", + DatastoreMetricName = $"Datastore/statement/Oracle/{_tableName}/insert", + + HasExplainPlan = false + }, + new() + { + TransactionName = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.ModernOracleManagedDataAccessClientExerciser/ExerciseSync", + Sql = $"DELETE FROM {_tableName} WHERE HOTEL_ID = ?", + DatastoreMetricName = $"Datastore/statement/Oracle/{_tableName}/delete", + + HasExplainPlan = false + } + }; + + var metrics = _fixture.AgentLog.GetMetrics().ToList(); + var transactionSample = _fixture.AgentLog.TryGetTransactionSample("OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.ModernOracleManagedDataAccessClientExerciser/ExerciseSync"); + var transactionEvent = _fixture.AgentLog.TryGetTransactionEvent("OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.ModernOracleManagedDataAccessClientExerciser/ExerciseSync"); + var sqlTraces = _fixture.AgentLog.GetSqlTraces().ToList(); + + NrAssert.Multiple( + () => Assert.NotNull(transactionSample), + () => Assert.NotNull(transactionEvent) + ); + + NrAssert.Multiple + ( + () => Assertions.MetricsExist(expectedMetrics, metrics), + () => Assertions.MetricsDoNotExist(unexpectedMetrics, metrics), + () => Assertions.TransactionTraceSegmentsExist(expectedTransactionTraceSegments, transactionSample), + () => Assertions.TransactionEventHasAttributes(expectedTransactionEventIntrinsicAttributes, TransactionEventAttributeType.Intrinsic, transactionEvent), + () => Assertions.SqlTraceExists(expectedSqlTraces, sqlTraces) + ); + } + private static string GenerateTableName() + { + //Oracle tables must start w/ character and be <= 30 length. Table name = H{tableId} + var tableId = Guid.NewGuid().ToString("N").Substring(2, 29).ToLower(); + return $"h{tableId}"; + } + } + + [NetCoreTest] + public class ModernOracleTestsCoreLatest : ModernOracleTestsBase + { + public ModernOracleTestsCoreLatest(ConsoleDynamicMethodFixtureCoreLatest fixture, ITestOutputHelper output) : base(fixture, output) + { + } + } +} From 2832ce57ce5e6d68a305b0a7b7caf37c4f5b9d1d Mon Sep 17 00:00:00 2001 From: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Thu, 29 Aug 2024 09:54:41 -0500 Subject: [PATCH 2/5] Updated instrumentation points for oracle --- .../Providers/Wrapper/Sql/Instrumentation.xml | 59 +++++++++++++++++-- 1 file changed, 53 insertions(+), 6 deletions(-) diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Sql/Instrumentation.xml b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Sql/Instrumentation.xml index 0ead5dfaf6..f75714b70e 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Sql/Instrumentation.xml +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Sql/Instrumentation.xml @@ -70,22 +70,29 @@ SPDX-License-Identifier: Apache-2.0 - - + + - - + + - + + + + + + + + @@ -205,6 +212,32 @@ SPDX-License-Identifier: Apache-2.0 + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -338,6 +371,19 @@ SPDX-License-Identifier: Apache-2.0 + + + + + + + + + + + + + @@ -368,9 +414,10 @@ SPDX-License-Identifier: Apache-2.0 - + + From cdbb2b34286a0a04865e8ae6d601f7c1b96c5ea7 Mon Sep 17 00:00:00 2001 From: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Thu, 29 Aug 2024 11:37:24 -0500 Subject: [PATCH 3/5] Migrate Oracle tests to ConsoleMFA pattern. --- .../MultiFunctionApplicationHelpers.csproj | 10 +- ...nOracleManagedDataAccessClientExerciser.cs | 122 ---------- .../Oracle/OracleExerciser.cs | 219 ++++++++++++++++++ .../Controllers/OracleController.cs | 147 ------------ .../Oracle/OracleAsyncTests.cs | 168 +++++++++----- .../Oracle/OracleStoredProcedureTests.cs | 82 +++++-- ...odernOracleTests.cs => OracleSyncTests.cs} | 107 +++++---- .../Oracle/OracleTests.cs | 161 ------------- 8 files changed, 456 insertions(+), 560 deletions(-) delete mode 100644 tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Oracle/ModernOracleManagedDataAccessClientExerciser.cs create mode 100644 tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Oracle/OracleExerciser.cs rename tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/{ModernOracleTests.cs => OracleSyncTests.cs} (51%) delete mode 100644 tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/OracleTests.cs diff --git a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/MultiFunctionApplicationHelpers.csproj b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/MultiFunctionApplicationHelpers.csproj index fb5a5929a8..13c4870720 100644 --- a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/MultiFunctionApplicationHelpers.csproj +++ b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/MultiFunctionApplicationHelpers.csproj @@ -80,10 +80,12 @@ - - - - + + + + + + diff --git a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Oracle/ModernOracleManagedDataAccessClientExerciser.cs b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Oracle/ModernOracleManagedDataAccessClientExerciser.cs deleted file mode 100644 index ddbe6ee4b9..0000000000 --- a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Oracle/ModernOracleManagedDataAccessClientExerciser.cs +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright 2020 New Relic, Inc. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// can only test "modern" oracle on net472+ and .NET6/8/+ -#if NET472_OR_GREATER || NET -using System; -using OracleConfiguration = NewRelic.Agent.IntegrationTests.Shared.OracleConfiguration; -using Oracle.ManagedDataAccess.Client; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using NewRelic.Agent.IntegrationTests.Shared.ReflectionHelpers; -using NewRelic.Api.Agent; - - -namespace MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle -{ - [Library] - public class ModernOracleManagedDataAccessClientExerciser : IDisposable - { - private const string CreateHotelTableOracleSql = "CREATE TABLE {0} (HOTEL_ID INT NOT NULL, BOOKING_DATE DATE NOT NULL, " + - "ROOMS_TAKEN INT DEFAULT 0, PRIMARY KEY (HOTEL_ID, BOOKING_DATE))"; - private const string DropHotelTableOracleSql = "DROP TABLE {0}"; - private const string InsertHotelOracleSql = "INSERT INTO {0} (HOTEL_ID, BOOKING_DATE) VALUES (1, SYSDATE)"; - private const string DeleteHotelOracleSql = "DELETE FROM {0} WHERE HOTEL_ID = 1"; - private const string CountHotelOracleSql = "SELECT COUNT(*) FROM {0}"; - - - private string _tableName; - - [LibraryMethod] - public void Initialize(string tableName) - { - _tableName = tableName; - CreateTable(); - } - - [LibraryMethod] - [Transaction] - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - public string ExerciseSync(string tableName) - { - var teamMembers = new List(); - - var connectionString = OracleConfiguration.OracleConnectionString; - - using (var connection = new OracleConnection(connectionString)) - { - connection.Open(); - - using (var command = new OracleCommand("SELECT DEGREE FROM user_tables WHERE ROWNUM <= 1", connection)) - { - using (var reader = command.ExecuteReader()) - { - while (reader.Read()) - { - teamMembers.Add(reader.GetString(reader.GetOrdinal("DEGREE"))); - } - } - } - - var insertSql = string.Format(InsertHotelOracleSql, tableName); - var countSql = string.Format(CountHotelOracleSql, tableName); - var deleteSql = string.Format(DeleteHotelOracleSql, tableName); - - using (var command = new OracleCommand(insertSql, connection)) - { - var insertCount = command.ExecuteNonQuery(); - } - - using (var command = new OracleCommand(countSql, connection)) - { - var hotelCount = command.ExecuteScalar(); - } - - using (var command = new OracleCommand(deleteSql, connection)) - { - var deleteCount = command.ExecuteNonQuery(); - } - } - - return string.Join(",", teamMembers); - } - - private void CreateTable() - { - var createTable = string.Format(CreateHotelTableOracleSql, _tableName); - - var connectionString = OracleConfiguration.OracleConnectionString; - - using (var connection = new OracleConnection(connectionString)) - { - connection.Open(); - - using (var command = new OracleCommand(createTable, connection)) - { - command.ExecuteNonQuery(); - } - } - } - - private void DropTable() - { - var dropTableSql = string.Format(DropHotelTableOracleSql, _tableName); - - using (var connection = new OracleConnection(OracleConfiguration.OracleConnectionString)) - { - connection.Open(); - - using (var command = new OracleCommand(dropTableSql, connection)) - { - command.ExecuteNonQuery(); - } - } - } - - public void Dispose() - { - DropTable(); - } - } -} -#endif diff --git a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Oracle/OracleExerciser.cs b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Oracle/OracleExerciser.cs new file mode 100644 index 0000000000..b1bafb55cc --- /dev/null +++ b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Oracle/OracleExerciser.cs @@ -0,0 +1,219 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using OracleConfiguration = NewRelic.Agent.IntegrationTests.Shared.OracleConfiguration; +using Oracle.ManagedDataAccess.Client; +using System.Data; +using System.Runtime.CompilerServices; +using NewRelic.Agent.IntegrationTests.Shared.ReflectionHelpers; +using NewRelic.Api.Agent; +using System.Linq; +using System.Threading.Tasks; +using NewRelic.Agent.IntegrationTests.Shared; + + +namespace MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle +{ + [Library] + public class OracleExerciser : IDisposable + { + private const string CreateHotelTableOracleSql = "CREATE TABLE {0} (HOTEL_ID INT NOT NULL, BOOKING_DATE DATE NOT NULL, " + + "ROOMS_TAKEN INT DEFAULT 0, PRIMARY KEY (HOTEL_ID, BOOKING_DATE))"; + private const string DropHotelTableOracleSql = "DROP TABLE {0}"; + private const string InsertHotelOracleSql = "INSERT INTO {0} (HOTEL_ID, BOOKING_DATE) VALUES (1, SYSDATE)"; + private const string DeleteHotelOracleSql = "DELETE FROM {0} WHERE HOTEL_ID = 1"; + private const string CountHotelOracleSql = "SELECT COUNT(*) FROM {0}"; + + + private string _tableName; + + [LibraryMethod] + public void InitializeTable(string tableName) + { + _tableName = tableName; + CreateTable(); + } + + [LibraryMethod] + public void InitializeStoredProcedure(string storedProcName) + { + _storedProcedureName = storedProcName; + CreateProcedure(); + } + + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public void ExerciseSync() + { + if (string.IsNullOrEmpty(_tableName)) + throw new Exception("Initialize table before exercising."); + + var connectionString = OracleConfiguration.OracleConnectionString; + + using var connection = new OracleConnection(connectionString); + connection.Open(); + + using (var command = new OracleCommand("SELECT DEGREE FROM user_tables WHERE ROWNUM <= 1", connection)) + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + var foo = reader.GetString(reader.GetOrdinal("DEGREE")); + } + } + + var insertSql = string.Format(InsertHotelOracleSql, _tableName); + var countSql = string.Format(CountHotelOracleSql, _tableName); + var deleteSql = string.Format(DeleteHotelOracleSql, _tableName); + + using (var command = new OracleCommand(insertSql, connection)) + { + var insertCount = command.ExecuteNonQuery(); + } + + using (var command = new OracleCommand(countSql, connection)) + { + var hotelCount = command.ExecuteScalar(); + } + + using (var command = new OracleCommand(deleteSql, connection)) + { + var deleteCount = command.ExecuteNonQuery(); + } + } + + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public async Task ExerciseAsync() + { + if (string.IsNullOrEmpty(_tableName)) + throw new Exception("Initialize table before exercising."); + + var connectionString = OracleConfiguration.OracleConnectionString; + + using var connection = new OracleConnection(connectionString); + await connection.OpenAsync(); + + using (var command = new OracleCommand("SELECT DEGREE FROM user_tables WHERE ROWNUM <= 1", connection)) + using (var reader = await command.ExecuteReaderAsync()) + { + while (await reader.ReadAsync()) + { + var foo = reader.GetString(reader.GetOrdinal("DEGREE")); + } + } + + var insertSql = string.Format(InsertHotelOracleSql, _tableName); + var countSql = string.Format(CountHotelOracleSql, _tableName); + var deleteSql = string.Format(DeleteHotelOracleSql, _tableName); + + using (var command = new OracleCommand(insertSql, connection)) + { + var insertCount = await command.ExecuteNonQueryAsync(); + } + + using (var command = new OracleCommand(countSql, connection)) + { + var hotelCount = await command.ExecuteScalarAsync(); + } + + using (var command = new OracleCommand(deleteSql, connection)) + { + var deleteCount = await command.ExecuteNonQueryAsync(); + } + } + + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public async Task ExerciseStoredProcedure() + { + if (string.IsNullOrEmpty(_storedProcedureName)) + throw new Exception("Initialize stored procedure before exercising."); + + using (var connection = new OracleConnection(OracleConfiguration.OracleConnectionString)) + { + using var command = new OracleCommand(_storedProcedureName, connection); + + await connection.OpenAsync(); + + command.CommandType = CommandType.StoredProcedure; + + foreach (var p in DbParameterData.OracleParameters) + { + command.Parameters.Add(p.ParameterName, p.Value); + } + + await command.ExecuteNonQueryAsync(); + } + } + + private void CreateTable() + { + var createTable = string.Format(CreateHotelTableOracleSql, _tableName); + + var connectionString = OracleConfiguration.OracleConnectionString; + + using var connection = new OracleConnection(connectionString); + connection.Open(); + + using var command = new OracleCommand(createTable, connection); + command.ExecuteNonQuery(); + } + + private void DropTable() + { + if (!string.IsNullOrEmpty(_tableName)) + { + var dropTableSql = string.Format(DropHotelTableOracleSql, _tableName); + + using var connection = new OracleConnection(OracleConfiguration.OracleConnectionString); + connection.Open(); + + using var command = new OracleCommand(dropTableSql, connection); + command.ExecuteNonQuery(); + } + } + + public void Dispose() + { + DropTable(); + DropProcedure(); + _tableName = null; + _storedProcedureName = null; + } + + private readonly string createProcedureStatment = @"CREATE PROCEDURE {0} ({1}) IS BEGIN NULL; END {0};"; + private readonly string dropProcedureStatement = @"DROP PROCEDURE {0}"; + private string _storedProcedureName; + + private void CreateProcedure() + { + var parameters = string.Join(", ", DbParameterData.OracleParameters.Select(x => $"{x.ParameterName} IN {x.DbTypeName}")); + var statement = string.Format(createProcedureStatment, _storedProcedureName, parameters); + using (var connection = new OracleConnection(OracleConfiguration.OracleConnectionString)) + using (var command = new OracleCommand(statement, connection)) + { + connection.Open(); + command.ExecuteNonQuery(); + } + } + + private void DropProcedure() + { + if (!string.IsNullOrEmpty(_storedProcedureName)) + { + var statement = string.Format(dropProcedureStatement, _storedProcedureName); + using (var connection = new OracleConnection(OracleConfiguration.OracleConnectionString)) + using (var command = new OracleCommand(statement, connection)) + { + connection.Open(); + command.ExecuteNonQuery(); + } + } + } + } +} diff --git a/tests/Agent/IntegrationTests/UnboundedApplications/BasicMvcApplication/Controllers/OracleController.cs b/tests/Agent/IntegrationTests/UnboundedApplications/BasicMvcApplication/Controllers/OracleController.cs index 73167f61b0..7c7efe4a4b 100644 --- a/tests/Agent/IntegrationTests/UnboundedApplications/BasicMvcApplication/Controllers/OracleController.cs +++ b/tests/Agent/IntegrationTests/UnboundedApplications/BasicMvcApplication/Controllers/OracleController.cs @@ -5,13 +5,9 @@ using Microsoft.Practices.EnterpriseLibrary.Data; using Microsoft.Practices.EnterpriseLibrary.Common.Configuration; using NewRelic.Agent.IntegrationTests.Shared; -using Oracle.ManagedDataAccess.Client; -using System; using System.Collections.Generic; using System.Configuration; using System.Data; -using System.Linq; -using System.Threading.Tasks; using System.Web.Mvc; namespace BasicMvcApplication.Controllers @@ -22,96 +18,6 @@ public class OracleController : Controller private const string DeleteHotelOracleSql = "DELETE FROM {0} WHERE HOTEL_ID = 1"; private const string CountHotelOracleSql = "SELECT COUNT(*) FROM {0}"; - [HttpGet] - public string Oracle(string tableName) - { - var teamMembers = new List(); - - var connectionString = OracleConfiguration.OracleConnectionString; - - using (var connection = new OracleConnection(connectionString)) - { - connection.Open(); - - using (var command = new OracleCommand("SELECT DEGREE FROM user_tables WHERE ROWNUM <= 1", connection)) - { - using (var reader = command.ExecuteReader()) - { - while (reader.Read()) - { - teamMembers.Add(reader.GetString(reader.GetOrdinal("DEGREE"))); - } - } - } - - var insertSql = string.Format(InsertHotelOracleSql, tableName); - var countSql = string.Format(CountHotelOracleSql, tableName); - var deleteSql = string.Format(DeleteHotelOracleSql, tableName); - - using (var command = new OracleCommand(insertSql, connection)) - { - var insertCount = command.ExecuteNonQuery(); - } - - using (var command = new OracleCommand(countSql, connection)) - { - var hotelCount = command.ExecuteScalar(); - } - - using (var command = new OracleCommand(deleteSql, connection)) - { - var deleteCount = command.ExecuteNonQuery(); - } - } - - return string.Join(",", teamMembers); - } - - [HttpGet] - public async Task OracleAsync(string tableName) - { - var teamMembers = new List(); - - var connectionString = OracleConfiguration.OracleConnectionString; - - using (var connection = new OracleConnection(connectionString)) - { - await connection.OpenAsync(); - - using (var command = new OracleCommand("SELECT DEGREE FROM user_tables WHERE ROWNUM <= 1", connection)) - { - using (var reader = await command.ExecuteReaderAsync()) - { - while (await reader.ReadAsync()) - { - teamMembers.Add(reader.GetString(reader.GetOrdinal("DEGREE"))); - } - } - } - - var insertSql = string.Format(InsertHotelOracleSql, tableName); - var countSql = string.Format(CountHotelOracleSql, tableName); - var deleteSql = string.Format(DeleteHotelOracleSql, tableName); - - using (var command = new OracleCommand(insertSql, connection)) - { - var insertCount = await command.ExecuteNonQueryAsync(); - } - - using (var command = new OracleCommand(countSql, connection)) - { - var hotelCount = await command.ExecuteScalarAsync(); - } - - using (var command = new OracleCommand(deleteSql, connection)) - { - var deleteCount = await command.ExecuteNonQueryAsync(); - } - } - - return string.Join(",", teamMembers); - } - [HttpGet] public string EnterpriseLibraryOracle(string tableName) { @@ -143,58 +49,5 @@ public string EnterpriseLibraryOracle(string tableName) return string.Join(",", teamMembers); } - - [HttpGet] - public void OracleParameterizedStoredProcedure(string procedureName) - { - CreateProcedure(procedureName); - - try - { - using (var connection = new OracleConnection(OracleConfiguration.OracleConnectionString)) - using (var command = new OracleCommand(procedureName, connection)) - { - connection.Open(); - command.CommandType = CommandType.StoredProcedure; - - foreach (var p in DbParameterData.OracleParameters) - { - command.Parameters.Add(p.ParameterName, p.Value); - } - - command.ExecuteNonQuery(); - } - } - finally - { - DropProcedure(procedureName); - } - } - - private readonly string createProcedureStatment = @"CREATE PROCEDURE {0} ({1}) IS BEGIN NULL; END {0};"; - private readonly string dropProcedureStatement = @"DROP PROCEDURE {0}"; - - private void CreateProcedure(string procedureName) - { - var parameters = string.Join(", ", DbParameterData.OracleParameters.Select(x => $"{x.ParameterName} IN {x.DbTypeName}")); - var statement = string.Format(createProcedureStatment, procedureName, parameters); - using (var connection = new OracleConnection(OracleConfiguration.OracleConnectionString)) - using (var command = new OracleCommand(statement, connection)) - { - connection.Open(); - command.ExecuteNonQuery(); - } - } - - private void DropProcedure(string procedureName) - { - var statement = string.Format(dropProcedureStatement, procedureName); - using (var connection = new OracleConnection(OracleConfiguration.OracleConnectionString)) - using (var command = new OracleCommand(statement, connection)) - { - connection.Open(); - command.ExecuteNonQuery(); - } - } } } diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/OracleAsyncTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/OracleAsyncTests.cs index 3479fde241..29810e8738 100644 --- a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/OracleAsyncTests.cs +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/OracleAsyncTests.cs @@ -1,28 +1,32 @@ // Copyright 2020 New Relic, Inc. All rights reserved. // SPDX-License-Identifier: Apache-2.0 - using System; using System.Collections.Generic; using System.Linq; using NewRelic.Agent.IntegrationTestHelpers; -using NewRelic.Agent.Tests.TestSerializationHelpers.Models; +using NewRelic.Agent.IntegrationTestHelpers.RemoteServiceFixtures; using NewRelic.Agent.IntegrationTests.Shared; -using NewRelic.Testing.Assertions; +using NewRelic.Agent.Tests.TestSerializationHelpers.Models; using Xunit; using Xunit.Abstractions; namespace NewRelic.Agent.UnboundedIntegrationTests.Oracle { - [NetFrameworkTest] - public class OracleAsyncTests : NewRelicIntegrationTest + public abstract class OracleAsyncTestsBase : NewRelicIntegrationTest + where TFixture : ConsoleDynamicMethodFixture { - private readonly RemoteServiceFixtures.OracleBasicMvcFixture _fixture; + private readonly ConsoleDynamicMethodFixture _fixture; + private readonly string _tableName; - public OracleAsyncTests(RemoteServiceFixtures.OracleBasicMvcFixture fixture, ITestOutputHelper output) : base(fixture) + protected OracleAsyncTestsBase(TFixture fixture, ITestOutputHelper output) : base(fixture) { _fixture = fixture; _fixture.TestLogger = output; + _tableName = GenerateTableName(); + + _fixture.AddCommand($"OracleExerciser InitializeTable {_tableName}"); // creates a new table. The table gets dropped automatically when the exerciser goes out of scope + _fixture.AddCommand($"OracleExerciser ExerciseAsync"); _fixture.AddActions ( @@ -37,17 +41,15 @@ public OracleAsyncTests(RemoteServiceFixtures.OracleBasicMvcFixture fixture, ITe configModifier.ForceTransactionTraces(); CommonUtils.ModifyOrCreateXmlAttributeInNewRelicConfig(configPath, new[] { "configuration", "transactionTracer" }, "explainThreshold", "1"); + CommonUtils.ModifyOrCreateXmlAttributeInNewRelicConfig(configPath, new[] { "configuration", "transactionTracer" }, "explainEnabled", "true"); var instrumentationFilePath = $@"{fixture.DestinationNewRelicExtensionsDirectoryPath}\NewRelic.Providers.Wrapper.Sql.Instrumentation.xml"; - CommonUtils.SetAttributeOnTracerFactoryInNewRelicInstrumentation( - instrumentationFilePath, - "", "enabled", "true"); + CommonUtils.SetAttributeOnTracerFactoryInNewRelicInstrumentation(instrumentationFilePath, "", "enabled", "true"); }, exerciseApplication: () => { - _fixture.GetOracleAsync(); _fixture.AgentLog.WaitForLogLine(AgentLogBase.AgentConnectedLogLineRegex, TimeSpan.FromMinutes(1)); - _fixture.AgentLog.WaitForLogLine(AgentLogBase.SqlTraceDataLogLineRegex, TimeSpan.FromMinutes(1)); + _fixture.AgentLog.WaitForLogLine(AgentLogBase.ShutdownLogLineRegex, TimeSpan.FromMinutes(1)); } ); @@ -57,39 +59,45 @@ public OracleAsyncTests(RemoteServiceFixtures.OracleBasicMvcFixture fixture, ITe [Fact] public void Test() { - var expectedDatastoreCallCount = 4; // SELECT, INSERT, SELECT COUNT, DELETE from the endpoint exercised by GetOracleAsync() + var expectedDatastoreCallCount = 4; // SELECT, INSERT, COUNT, and DELETE from GetOracle() above + + //This value is dictated by the query that is being run as part of this test. In this case, we're running a query that returns a single row. + //This results in two calls to read. Therefore the call count for the Iterate metric should be 2. + var expectedIterateCallCount = 2; var expectedMetrics = new List { - new Assertions.ExpectedMetric { metricName = @"Datastore/all", callCount = expectedDatastoreCallCount }, - new Assertions.ExpectedMetric { metricName = @"Datastore/allWeb", callCount = expectedDatastoreCallCount }, - new Assertions.ExpectedMetric { metricName = @"Datastore/Oracle/all", callCount = expectedDatastoreCallCount }, - new Assertions.ExpectedMetric { metricName = @"Datastore/Oracle/allWeb", callCount = expectedDatastoreCallCount }, - new Assertions.ExpectedMetric { metricName = $@"Datastore/instance/Oracle/{OracleConfiguration.OracleServer}/{OracleConfiguration.OraclePort}", callCount = expectedDatastoreCallCount}, - new Assertions.ExpectedMetric { metricName = @"Datastore/operation/Oracle/select", callCount = 2 }, - new Assertions.ExpectedMetric { metricName = @"Datastore/statement/Oracle/user_tables/select", callCount = 1 }, - new Assertions.ExpectedMetric { metricName = @"Datastore/statement/Oracle/user_tables/select", callCount = 1, metricScope = "WebTransaction/MVC/OracleController/OracleAsync"}, - new Assertions.ExpectedMetric { metricName = $@"Datastore/statement/Oracle/{_fixture.TableName}/select", callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $@"Datastore/statement/Oracle/{_fixture.TableName}/select", callCount = 1, metricScope = "WebTransaction/MVC/OracleController/OracleAsync"}, - new Assertions.ExpectedMetric { metricName = @"Datastore/operation/Oracle/insert", callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $@"Datastore/statement/Oracle/{_fixture.TableName}/insert", callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $@"Datastore/statement/Oracle/{_fixture.TableName}/insert", callCount = 1, metricScope = "WebTransaction/MVC/OracleController/OracleAsync"}, - new Assertions.ExpectedMetric { metricName = @"Datastore/operation/Oracle/delete", callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $@"Datastore/statement/Oracle/{_fixture.TableName}/delete", callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $@"Datastore/statement/Oracle/{_fixture.TableName}/delete", callCount = 1, metricScope = "WebTransaction/MVC/OracleController/OracleAsync"} + new() { metricName = @"Datastore/all", callCount = expectedDatastoreCallCount }, + new() { metricName = @"Datastore/allOther", callCount = expectedDatastoreCallCount }, + new() { metricName = @"Datastore/Oracle/all", callCount = expectedDatastoreCallCount }, + new() { metricName = @"Datastore/Oracle/allOther", callCount = expectedDatastoreCallCount }, + new() { metricName = $@"Datastore/instance/Oracle/{OracleConfiguration.OracleServer}/{OracleConfiguration.OraclePort}", callCount = expectedDatastoreCallCount}, + new() { metricName = @"Datastore/operation/Oracle/select", callCount = 2 }, + new() { metricName = @"Datastore/statement/Oracle/user_tables/select", callCount = 1 }, + new() { metricName = @"Datastore/statement/Oracle/user_tables/select", callCount = 1, metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseAsync"}, + new() { metricName = $@"Datastore/statement/Oracle/{_tableName}/select", callCount = 1 }, + new() { metricName = $@"Datastore/statement/Oracle/{_tableName}/select", callCount = 1, metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseAsync"}, + new() { metricName = @"Datastore/operation/Oracle/insert", callCount = 1 }, + new() { metricName = $@"Datastore/statement/Oracle/{_tableName}/insert", callCount = 1 }, + new() { metricName = $@"Datastore/statement/Oracle/{_tableName}/insert", callCount = 1, metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseAsync"}, + new() { metricName = @"Datastore/operation/Oracle/delete", callCount = 1 }, + new() { metricName = $@"Datastore/statement/Oracle/{_tableName}/delete", callCount = 1 }, + new() { metricName = $@"Datastore/statement/Oracle/{_tableName}/delete", callCount = 1, metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseAsync"}, + + new() { metricName = @"DotNet/DatabaseResult/Iterate" , callCount = expectedIterateCallCount }, + new() { metricName = @"DotNet/DatabaseResult/Iterate", callCount = expectedIterateCallCount, metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseAsync"} }; var unexpectedMetrics = new List { - // The datastore operation happened inside a web transaction so there should be no allOther metrics - new Assertions.ExpectedMetric { metricName = @"Datastore/allOther" }, - new Assertions.ExpectedMetric { metricName = @"Datastore/Oracle/allOther" }, - - // The operation metric should not be scoped because the statement metric is scoped instead - new Assertions.ExpectedMetric { metricName = @"Datastore/operation/Oracle/select", metricScope = "WebTransaction/MVC/OracleController/OracleAsync" }, - new Assertions.ExpectedMetric { metricName = @"Datastore/operation/Oracle/insert", metricScope = "WebTransaction/MVC/OracleController/OracleAsync" }, - new Assertions.ExpectedMetric { metricName = @"Datastore/operation/Oracle/delete", metricScope = "WebTransaction/MVC/OracleController/OracleAsync" } + // The datastore operation happened inside a non-web transaction so there should be no allWeb metrics + new() { metricName = @"Datastore/allWeb" }, + new() { metricName = @"Datastore/Oracle/allWeb"}, + + // The operation metric should not be scoped because the statement metric is scoped instead + new() { metricName = @"Datastore/operation/Oracle/select", metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseAsync" }, + new() { metricName = @"Datastore/operation/Oracle/insert", metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseAsync" }, + new() { metricName = @"Datastore/operation/Oracle/delete", metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseAsync" } }; - var expectedTransactionTraceSegments = new List { "Datastore/statement/Oracle/user_tables/select" @@ -99,66 +107,98 @@ public void Test() { "databaseDuration" }; - - var expectedTransactionTraceSegmentParameters = new List - { - new Assertions.ExpectedSegmentParameter { segmentName = "Datastore/statement/Oracle/user_tables/select", parameterName = "sql", parameterValue = "SELECT DEGREE FROM user_tables WHERE ROWNUM <= ?"} - }; - var expectedSqlTraces = new List { - new Assertions.ExpectedSqlTrace + new() { - TransactionName = "WebTransaction/MVC/OracleController/OracleAsync", + TransactionName = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseAsync", Sql = "SELECT DEGREE FROM user_tables WHERE ROWNUM <= ?", DatastoreMetricName = "Datastore/statement/Oracle/user_tables/select", HasExplainPlan = false }, - new Assertions.ExpectedSqlTrace + new() { - TransactionName = "WebTransaction/MVC/OracleController/OracleAsync", - Sql = $"SELECT COUNT(*) FROM {_fixture.TableName}", - DatastoreMetricName = $"Datastore/statement/Oracle/{_fixture.TableName}/select", + TransactionName = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseAsync", + Sql = $"SELECT COUNT(*) FROM {_tableName}", + DatastoreMetricName = $"Datastore/statement/Oracle/{_tableName}/select", HasExplainPlan = false }, - new Assertions.ExpectedSqlTrace + new() { - TransactionName = "WebTransaction/MVC/OracleController/OracleAsync", - Sql = $"INSERT INTO {_fixture.TableName} (HOTEL_ID, BOOKING_DATE) VALUES (?, SYSDATE)", - DatastoreMetricName = $"Datastore/statement/Oracle/{_fixture.TableName}/insert", + TransactionName = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseAsync", + Sql = $"INSERT INTO {_tableName} (HOTEL_ID, BOOKING_DATE) VALUES (?, SYSDATE)", + DatastoreMetricName = $"Datastore/statement/Oracle/{_tableName}/insert", HasExplainPlan = false }, - new Assertions.ExpectedSqlTrace + new() { - TransactionName = "WebTransaction/MVC/OracleController/OracleAsync", - Sql = $"DELETE FROM {_fixture.TableName} WHERE HOTEL_ID = ?", - DatastoreMetricName = $"Datastore/statement/Oracle/{_fixture.TableName}/delete", + TransactionName = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseAsync", + Sql = $"DELETE FROM {_tableName} WHERE HOTEL_ID = ?", + DatastoreMetricName = $"Datastore/statement/Oracle/{_tableName}/delete", HasExplainPlan = false } }; var metrics = _fixture.AgentLog.GetMetrics().ToList(); - var transactionSample = _fixture.AgentLog.TryGetTransactionSample("WebTransaction/MVC/OracleController/OracleAsync"); - var transactionEvent = _fixture.AgentLog.TryGetTransactionEvent("WebTransaction/MVC/OracleController/OracleAsync"); + var transactionSample = _fixture.AgentLog.TryGetTransactionSample("OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseAsync"); + var transactionEvent = _fixture.AgentLog.TryGetTransactionEvent("OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseAsync"); var sqlTraces = _fixture.AgentLog.GetSqlTraces().ToList(); - NrAssert.Multiple( + Assert.Multiple( () => Assert.NotNull(transactionSample), () => Assert.NotNull(transactionEvent) ); - NrAssert.Multiple + Assert.Multiple ( () => Assertions.MetricsExist(expectedMetrics, metrics), () => Assertions.MetricsDoNotExist(unexpectedMetrics, metrics), () => Assertions.TransactionTraceSegmentsExist(expectedTransactionTraceSegments, transactionSample), () => Assertions.TransactionEventHasAttributes(expectedTransactionEventIntrinsicAttributes, TransactionEventAttributeType.Intrinsic, transactionEvent), - () => Assertions.SqlTraceExists(expectedSqlTraces, sqlTraces), - () => Assertions.TransactionTraceSegmentParametersExist(expectedTransactionTraceSegmentParameters, transactionSample) + () => Assertions.SqlTraceExists(expectedSqlTraces, sqlTraces) ); } + + private static string GenerateTableName() + { + //Oracle tables must start w/ character and be <= 30 length. Table name = H{tableId} + var tableId = Guid.NewGuid().ToString("N").Substring(2, 29).ToLower(); + return $"h{tableId}"; + } + } + + [NetFrameworkTest] + public class OracleAsyncTestsFramework462 : OracleAsyncTestsBase + { + public OracleAsyncTestsFramework462(ConsoleDynamicMethodFixtureFW462 fixture, ITestOutputHelper output) : base(fixture, output) + { + } + } + + [NetFrameworkTest] + public class OracleAsyncTestsFramework471 : OracleAsyncTestsBase + { + public OracleAsyncTestsFramework471(ConsoleDynamicMethodFixtureFW471 fixture, ITestOutputHelper output) : base(fixture, output) + { + } + } + + [NetFrameworkTest] + public class OracleAsyncTestsFrameworkLatest : OracleAsyncTestsBase + { + public OracleAsyncTestsFrameworkLatest(ConsoleDynamicMethodFixtureFWLatest fixture, ITestOutputHelper output) : base(fixture, output) + { + } + } + + [NetCoreTest] + public class OracleAsyncTestsCoreLatest : OracleAsyncTestsBase + { + public OracleAsyncTestsCoreLatest(ConsoleDynamicMethodFixtureCoreLatest fixture, ITestOutputHelper output) : base(fixture, output) + { + } } } diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/OracleStoredProcedureTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/OracleStoredProcedureTests.cs index e301319585..703731bd13 100644 --- a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/OracleStoredProcedureTests.cs +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/OracleStoredProcedureTests.cs @@ -1,30 +1,34 @@ // Copyright 2020 New Relic, Inc. All rights reserved. // SPDX-License-Identifier: Apache-2.0 - using System; using System.Collections.Generic; using System.Linq; using NewRelic.Agent.IntegrationTestHelpers; +using NewRelic.Agent.IntegrationTestHelpers.RemoteServiceFixtures; using NewRelic.Agent.IntegrationTests.Shared; -using NewRelic.Agent.UnboundedIntegrationTests.RemoteServiceFixtures; -using NewRelic.Testing.Assertions; +using NewRelic.Agent.Tests.TestSerializationHelpers.Models; using Xunit; using Xunit.Abstractions; namespace NewRelic.Agent.UnboundedIntegrationTests.Oracle { - [NetFrameworkTest] - public class OracleStoredProcedureTests : NewRelicIntegrationTest + public abstract class OracleStoredProcedureTestsBase : NewRelicIntegrationTest + where TFixture : ConsoleDynamicMethodFixture { - private readonly OracleBasicMvcFixture _fixture; - private readonly string _procedureName = $"OracleTestStoredProc{Guid.NewGuid():N}".Substring(0, 30); + private readonly ConsoleDynamicMethodFixture _fixture; + private readonly string _storedProcedureName; - public OracleStoredProcedureTests(OracleBasicMvcFixture fixture, ITestOutputHelper output) : base(fixture) + protected OracleStoredProcedureTestsBase(TFixture fixture, ITestOutputHelper output) : base(fixture) { _fixture = fixture; _fixture.TestLogger = output; + _storedProcedureName = GenerateStoredProcedureName(); + + _fixture.AddCommand($"OracleExerciser InitializeStoredProcedure {_storedProcedureName}"); // creates a new stored procedure. The stored procedure gets dropped automatically when the exerciser goes out of scope) + _fixture.AddCommand($"OracleExerciser ExerciseStoredProcedure"); + _fixture.AddActions ( setupConfiguration: () => @@ -45,7 +49,6 @@ public OracleStoredProcedureTests(OracleBasicMvcFixture fixture, ITestOutputHelp }, exerciseApplication: () => { - _fixture.OracleParameterizedStoredProcedure(_procedureName); _fixture.AgentLog.WaitForLogLine(AgentLogBase.AgentConnectedLogLineRegex, TimeSpan.FromMinutes(1)); _fixture.AgentLog.WaitForLogLine(AgentLogBase.TransactionTransformCompletedLogLineRegex, TimeSpan.FromMinutes(2)); _fixture.AgentLog.WaitForLogLine(AgentLogBase.SqlTraceDataLogLineRegex, TimeSpan.FromMinutes(1)); @@ -60,42 +63,42 @@ public void Test() { var expectedMetrics = new List { - new Assertions.ExpectedMetric { metricName = $@"Datastore/statement/Oracle/{_procedureName.ToLower()}/ExecuteProcedure", callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $@"Datastore/statement/Oracle/{_procedureName.ToLower()}/ExecuteProcedure", callCount = 1, metricScope = "WebTransaction/MVC/OracleController/OracleParameterizedStoredProcedure"} + new() { metricName = $@"Datastore/statement/Oracle/{_storedProcedureName.ToLower()}/ExecuteProcedure", callCount = 1 }, + new() { metricName = $@"Datastore/statement/Oracle/{_storedProcedureName.ToLower()}/ExecuteProcedure", callCount = 1, metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseStoredProcedure"} }; var expectedTransactionTraceSegments = new List { - $"Datastore/statement/Oracle/{_procedureName.ToLower()}/ExecuteProcedure" + $"Datastore/statement/Oracle/{_storedProcedureName.ToLower()}/ExecuteProcedure" }; var expectedQueryParameters = DbParameterData.OracleParameters.ToDictionary(p => p.ParameterName, p => p.ExpectedValue); - var expectedTransactionTraceQueryParameters = new Assertions.ExpectedSegmentQueryParameters { segmentName = $"Datastore/statement/Oracle/{_procedureName.ToLower()}/ExecuteProcedure", QueryParameters = expectedQueryParameters }; + var expectedTransactionTraceQueryParameters = new Assertions.ExpectedSegmentQueryParameters { segmentName = $"Datastore/statement/Oracle/{_storedProcedureName.ToLower()}/ExecuteProcedure", QueryParameters = expectedQueryParameters }; var expectedSqlTraces = new List { - new Assertions.ExpectedSqlTrace + new() { - TransactionName = "WebTransaction/MVC/OracleController/OracleParameterizedStoredProcedure", - Sql = _procedureName, - DatastoreMetricName = $"Datastore/statement/Oracle/{_procedureName.ToLower()}/ExecuteProcedure", + TransactionName = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseStoredProcedure", + Sql = _storedProcedureName, + DatastoreMetricName = $"Datastore/statement/Oracle/{_storedProcedureName.ToLower()}/ExecuteProcedure", QueryParameters = expectedQueryParameters, HasExplainPlan = false } }; var metrics = _fixture.AgentLog.GetMetrics().ToList(); - var transactionSample = _fixture.AgentLog.TryGetTransactionSample("WebTransaction/MVC/OracleController/OracleParameterizedStoredProcedure"); - var transactionEvent = _fixture.AgentLog.TryGetTransactionEvent("WebTransaction/MVC/OracleController/OracleParameterizedStoredProcedure"); + var transactionSample = _fixture.AgentLog.TryGetTransactionSample("OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseStoredProcedure"); + var transactionEvent = _fixture.AgentLog.TryGetTransactionEvent("OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseStoredProcedure"); var sqlTraces = _fixture.AgentLog.GetSqlTraces().ToList(); - NrAssert.Multiple( + Assert.Multiple( () => Assert.NotNull(transactionSample), () => Assert.NotNull(transactionEvent) ); - NrAssert.Multiple + Assert.Multiple ( () => Assertions.MetricsExist(expectedMetrics, metrics), () => Assertions.TransactionTraceSegmentsExist(expectedTransactionTraceSegments, transactionSample), @@ -103,5 +106,42 @@ public void Test() () => Assertions.SqlTraceExists(expectedSqlTraces, sqlTraces) ); } + + private string GenerateStoredProcedureName() + { + return $"OracleStoredProcedureTest{Guid.NewGuid():N}".Substring(0, 30); + } + } + + [NetFrameworkTest] + public class OracleStoredProcedureTestsFramework462 : OracleStoredProcedureTestsBase + { + public OracleStoredProcedureTestsFramework462(ConsoleDynamicMethodFixtureFW462 fixture, ITestOutputHelper output) : base(fixture, output) + { + } + } + + + [NetFrameworkTest] + public class OracleStoredProcedureTestsFramework471 : OracleStoredProcedureTestsBase + { + public OracleStoredProcedureTestsFramework471(ConsoleDynamicMethodFixtureFW471 fixture, ITestOutputHelper output) : base(fixture, output) + { + } + } + [NetFrameworkTest] + public class OracleStoredProcedureTestsFrameworkLatest : OracleStoredProcedureTestsBase + { + public OracleStoredProcedureTestsFrameworkLatest(ConsoleDynamicMethodFixtureFWLatest fixture, ITestOutputHelper output) : base(fixture, output) + { + } + } + + [NetCoreTest] + public class OracleStoredProcedureTestsCoreLatest : OracleStoredProcedureTestsBase + { + public OracleStoredProcedureTestsCoreLatest(ConsoleDynamicMethodFixtureCoreLatest fixture, ITestOutputHelper output) : base(fixture, output) + { + } } } diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/ModernOracleTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/OracleSyncTests.cs similarity index 51% rename from tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/ModernOracleTests.cs rename to tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/OracleSyncTests.cs index 9866d1f205..d62d928623 100644 --- a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/ModernOracleTests.cs +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/OracleSyncTests.cs @@ -8,26 +8,25 @@ using NewRelic.Agent.IntegrationTestHelpers.RemoteServiceFixtures; using NewRelic.Agent.IntegrationTests.Shared; using NewRelic.Agent.Tests.TestSerializationHelpers.Models; -using NewRelic.Testing.Assertions; using Xunit; using Xunit.Abstractions; namespace NewRelic.Agent.UnboundedIntegrationTests.Oracle { - public abstract class ModernOracleTestsBase : NewRelicIntegrationTest + public abstract class OracleSyncTestsBase : NewRelicIntegrationTest where TFixture : ConsoleDynamicMethodFixture { private readonly ConsoleDynamicMethodFixture _fixture; private readonly string _tableName; - protected ModernOracleTestsBase(TFixture fixture, ITestOutputHelper output) : base(fixture) + protected OracleSyncTestsBase(TFixture fixture, ITestOutputHelper output) : base(fixture) { _fixture = fixture; _fixture.TestLogger = output; _tableName = GenerateTableName(); - _fixture.AddCommand($"ModernOracleManagedDataAccessClientExerciser Initialize {_tableName}"); // creates a new table. The table gets dropped automatically when the exerciser goes out of scope - _fixture.AddCommand($"ModernOracleManagedDataAccessClientExerciser ExerciseSync {_tableName}"); + _fixture.AddCommand($"OracleExerciser InitializeTable {_tableName}"); // creates a new table. The table gets dropped automatically when the exerciser goes out of scope + _fixture.AddCommand($"OracleExerciser ExerciseSync"); _fixture.AddActions ( @@ -42,6 +41,7 @@ protected ModernOracleTestsBase(TFixture fixture, ITestOutputHelper output) : ba configModifier.ForceTransactionTraces(); CommonUtils.ModifyOrCreateXmlAttributeInNewRelicConfig(configPath, new[] { "configuration", "transactionTracer" }, "explainThreshold", "1"); + CommonUtils.ModifyOrCreateXmlAttributeInNewRelicConfig(configPath, new[] { "configuration", "transactionTracer" }, "explainEnabled", "true"); var instrumentationFilePath = $@"{fixture.DestinationNewRelicExtensionsDirectoryPath}\NewRelic.Providers.Wrapper.Sql.Instrumentation.xml"; CommonUtils.SetAttributeOnTracerFactoryInNewRelicInstrumentation(instrumentationFilePath, "", "enabled", "true"); @@ -49,7 +49,7 @@ protected ModernOracleTestsBase(TFixture fixture, ITestOutputHelper output) : ba exerciseApplication: () => { _fixture.AgentLog.WaitForLogLine(AgentLogBase.AgentConnectedLogLineRegex, TimeSpan.FromMinutes(1)); - _fixture.AgentLog.WaitForLogLine(AgentLogBase.SqlTraceDataLogLineRegex, TimeSpan.FromMinutes(1)); + _fixture.AgentLog.WaitForLogLine(AgentLogBase.ShutdownLogLineRegex, TimeSpan.FromMinutes(1)); } ); @@ -67,36 +67,36 @@ public void Test() var expectedMetrics = new List { - new() { metricName = "Datastore/all", callCount = expectedDatastoreCallCount }, - new() { metricName = "Datastore/allOther", callCount = expectedDatastoreCallCount }, - new() { metricName = "Datastore/Oracle/all", callCount = expectedDatastoreCallCount }, - new() { metricName = "Datastore/Oracle/allOther", callCount = expectedDatastoreCallCount }, - new() { metricName = $"Datastore/instance/Oracle/{OracleConfiguration.OracleServer}/{OracleConfiguration.OraclePort}", callCount = expectedDatastoreCallCount}, - new() { metricName = "Datastore/operation/Oracle/select", callCount = 2 }, - new() { metricName = "Datastore/statement/Oracle/user_tables/select", callCount = 1 }, - new() { metricName = "Datastore/statement/Oracle/user_tables/select", callCount = 1, metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.ModernOracleManagedDataAccessClientExerciser/ExerciseSync"}, - new() { metricName = $"Datastore/statement/Oracle/{_tableName}/select", callCount = 1 }, - new() { metricName = $"Datastore/statement/Oracle/{_tableName}/select", callCount = 1, metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.ModernOracleManagedDataAccessClientExerciser/ExerciseSync"}, - new() { metricName = "Datastore/operation/Oracle/insert", callCount = 1 }, - new() { metricName = $"Datastore/statement/Oracle/{_tableName}/insert", callCount = 1 }, - new() { metricName = $"Datastore/statement/Oracle/{_tableName}/insert", callCount = 1, metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.ModernOracleManagedDataAccessClientExerciser/ExerciseSync"}, - new() { metricName = "Datastore/operation/Oracle/delete", callCount = 1 }, - new() { metricName = $"Datastore/statement/Oracle/{_tableName}/delete", callCount = 1 }, - new() { metricName = $"Datastore/statement/Oracle/{_tableName}/delete", callCount = 1, metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.ModernOracleManagedDataAccessClientExerciser/ExerciseSync"}, - - new() { metricName = "DotNet/DatabaseResult/Iterate" , callCount = expectedIterateCallCount }, - new() { metricName = "DotNet/DatabaseResult/Iterate", callCount = expectedIterateCallCount, metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.ModernOracleManagedDataAccessClientExerciser/ExerciseSync"} + new() { metricName = @"Datastore/all", callCount = expectedDatastoreCallCount }, + new() { metricName = @"Datastore/allOther", callCount = expectedDatastoreCallCount }, + new() { metricName = @"Datastore/Oracle/all", callCount = expectedDatastoreCallCount }, + new() { metricName = @"Datastore/Oracle/allOther", callCount = expectedDatastoreCallCount }, + new() { metricName = $@"Datastore/instance/Oracle/{OracleConfiguration.OracleServer}/{OracleConfiguration.OraclePort}", callCount = expectedDatastoreCallCount}, + new() { metricName = @"Datastore/operation/Oracle/select", callCount = 2 }, + new() { metricName = @"Datastore/statement/Oracle/user_tables/select", callCount = 1 }, + new() { metricName = @"Datastore/statement/Oracle/user_tables/select", callCount = 1, metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseSync"}, + new() { metricName = $@"Datastore/statement/Oracle/{_tableName}/select", callCount = 1 }, + new() { metricName = $@"Datastore/statement/Oracle/{_tableName}/select", callCount = 1, metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseSync"}, + new() { metricName = @"Datastore/operation/Oracle/insert", callCount = 1 }, + new() { metricName = $@"Datastore/statement/Oracle/{_tableName}/insert", callCount = 1 }, + new() { metricName = $@"Datastore/statement/Oracle/{_tableName}/insert", callCount = 1, metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseSync"}, + new() { metricName = @"Datastore/operation/Oracle/delete", callCount = 1 }, + new() { metricName = $@"Datastore/statement/Oracle/{_tableName}/delete", callCount = 1 }, + new() { metricName = $@"Datastore/statement/Oracle/{_tableName}/delete", callCount = 1, metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseSync"}, + + new() { metricName = @"DotNet/DatabaseResult/Iterate" , callCount = expectedIterateCallCount }, + new() { metricName = @"DotNet/DatabaseResult/Iterate", callCount = expectedIterateCallCount, metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseSync"} }; var unexpectedMetrics = new List { - // The datastore operation happened inside a web transaction so there should be no allOther metrics - new() { metricName = "Datastore/allWeb" }, - new() { metricName = "Datastore/Oracle/allWeb"}, + // The datastore operation happened inside a non-web transaction so there should be no allWeb metrics + new() { metricName = @"Datastore/allWeb" }, + new() { metricName = @"Datastore/Oracle/allWeb"}, // The operation metric should not be scoped because the statement metric is scoped instead - new() { metricName = "Datastore/operation/Oracle/select", metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.ModernOracleManagedDataAccessClientExerciser/ExerciseSync" }, - new() { metricName = "Datastore/operation/Oracle/insert", metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.ModernOracleManagedDataAccessClientExerciser/ExerciseSync" }, - new() { metricName = "Datastore/operation/Oracle/delete", metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.ModernOracleManagedDataAccessClientExerciser/ExerciseSync" } + new() { metricName = @"Datastore/operation/Oracle/select", metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseSync" }, + new() { metricName = @"Datastore/operation/Oracle/insert", metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseSync" }, + new() { metricName = @"Datastore/operation/Oracle/delete", metricScope = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseSync" } }; var expectedTransactionTraceSegments = new List { @@ -111,14 +111,14 @@ public void Test() { new() { - TransactionName = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.ModernOracleManagedDataAccessClientExerciser/ExerciseSync", + TransactionName = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseSync", Sql = "SELECT DEGREE FROM user_tables WHERE ROWNUM <= ?", DatastoreMetricName = "Datastore/statement/Oracle/user_tables/select", HasExplainPlan = false }, new() { - TransactionName = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.ModernOracleManagedDataAccessClientExerciser/ExerciseSync", + TransactionName = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseSync", Sql = $"SELECT COUNT(*) FROM {_tableName}", DatastoreMetricName = $"Datastore/statement/Oracle/{_tableName}/select", @@ -126,7 +126,7 @@ public void Test() }, new() { - TransactionName = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.ModernOracleManagedDataAccessClientExerciser/ExerciseSync", + TransactionName = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseSync", Sql = $"INSERT INTO {_tableName} (HOTEL_ID, BOOKING_DATE) VALUES (?, SYSDATE)", DatastoreMetricName = $"Datastore/statement/Oracle/{_tableName}/insert", @@ -134,7 +134,7 @@ public void Test() }, new() { - TransactionName = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.ModernOracleManagedDataAccessClientExerciser/ExerciseSync", + TransactionName = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseSync", Sql = $"DELETE FROM {_tableName} WHERE HOTEL_ID = ?", DatastoreMetricName = $"Datastore/statement/Oracle/{_tableName}/delete", @@ -143,16 +143,16 @@ public void Test() }; var metrics = _fixture.AgentLog.GetMetrics().ToList(); - var transactionSample = _fixture.AgentLog.TryGetTransactionSample("OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.ModernOracleManagedDataAccessClientExerciser/ExerciseSync"); - var transactionEvent = _fixture.AgentLog.TryGetTransactionEvent("OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.ModernOracleManagedDataAccessClientExerciser/ExerciseSync"); + var transactionSample = _fixture.AgentLog.TryGetTransactionSample("OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseSync"); + var transactionEvent = _fixture.AgentLog.TryGetTransactionEvent("OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseSync"); var sqlTraces = _fixture.AgentLog.GetSqlTraces().ToList(); - NrAssert.Multiple( + Assert.Multiple( () => Assert.NotNull(transactionSample), () => Assert.NotNull(transactionEvent) ); - NrAssert.Multiple + Assert.Multiple ( () => Assertions.MetricsExist(expectedMetrics, metrics), () => Assertions.MetricsDoNotExist(unexpectedMetrics, metrics), @@ -161,6 +161,7 @@ public void Test() () => Assertions.SqlTraceExists(expectedSqlTraces, sqlTraces) ); } + private static string GenerateTableName() { //Oracle tables must start w/ character and be <= 30 length. Table name = H{tableId} @@ -169,10 +170,34 @@ private static string GenerateTableName() } } + [NetFrameworkTest] + public class OracleSyncTestsFramework462 : OracleSyncTestsBase + { + public OracleSyncTestsFramework462(ConsoleDynamicMethodFixtureFW462 fixture, ITestOutputHelper output) : base(fixture, output) + { + } + } + + [NetFrameworkTest] + public class OracleSyncTestsFramework471 : OracleSyncTestsBase + { + public OracleSyncTestsFramework471(ConsoleDynamicMethodFixtureFW471 fixture, ITestOutputHelper output) : base(fixture, output) + { + } + } + + [NetFrameworkTest] + public class OracleSyncTestsFrameworkLatest : OracleSyncTestsBase + { + public OracleSyncTestsFrameworkLatest(ConsoleDynamicMethodFixtureFWLatest fixture, ITestOutputHelper output) : base(fixture, output) + { + } + } + [NetCoreTest] - public class ModernOracleTestsCoreLatest : ModernOracleTestsBase + public class OracleSyncTestsCoreLatest : OracleSyncTestsBase { - public ModernOracleTestsCoreLatest(ConsoleDynamicMethodFixtureCoreLatest fixture, ITestOutputHelper output) : base(fixture, output) + public OracleSyncTestsCoreLatest(ConsoleDynamicMethodFixtureCoreLatest fixture, ITestOutputHelper output) : base(fixture, output) { } } diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/OracleTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/OracleTests.cs deleted file mode 100644 index f794e94417..0000000000 --- a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/OracleTests.cs +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2020 New Relic, Inc. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - - -using System; -using System.Collections.Generic; -using System.Linq; -using NewRelic.Agent.IntegrationTestHelpers; -using NewRelic.Agent.Tests.TestSerializationHelpers.Models; -using NewRelic.Agent.IntegrationTests.Shared; -using NewRelic.Testing.Assertions; -using Xunit; -using Xunit.Abstractions; - -namespace NewRelic.Agent.UnboundedIntegrationTests.Oracle -{ - [NetFrameworkTest] - public class OracleTests : NewRelicIntegrationTest - { - private readonly RemoteServiceFixtures.OracleBasicMvcFixture _fixture; - - public OracleTests(RemoteServiceFixtures.OracleBasicMvcFixture fixture, ITestOutputHelper output) : base(fixture) - { - _fixture = fixture; - _fixture.TestLogger = output; - - _fixture.AddActions - ( - setupConfiguration: () => - { - var configPath = fixture.DestinationNewRelicConfigFilePath; - var configModifier = new NewRelicConfigModifier(configPath); - configModifier.ConfigureFasterMetricsHarvestCycle(15); - configModifier.ConfigureFasterTransactionTracesHarvestCycle(15); - configModifier.ConfigureFasterSqlTracesHarvestCycle(15); - - configModifier.ForceTransactionTraces(); - - CommonUtils.ModifyOrCreateXmlAttributeInNewRelicConfig(configPath, new[] { "configuration", "transactionTracer" }, "explainThreshold", "1"); - - var instrumentationFilePath = $@"{fixture.DestinationNewRelicExtensionsDirectoryPath}\NewRelic.Providers.Wrapper.Sql.Instrumentation.xml"; - CommonUtils.SetAttributeOnTracerFactoryInNewRelicInstrumentation(instrumentationFilePath, "", "enabled", "true"); - }, - exerciseApplication: () => - { - _fixture.GetOracle(); - _fixture.AgentLog.WaitForLogLine(AgentLogBase.AgentConnectedLogLineRegex, TimeSpan.FromMinutes(1)); - _fixture.AgentLog.WaitForLogLine(AgentLogBase.SqlTraceDataLogLineRegex, TimeSpan.FromMinutes(1)); - } - ); - - _fixture.Initialize(); - } - - [Fact] - public void Test() - { - var expectedDatastoreCallCount = 4; // SELECT, INSERT, COUNT, and DELETE from GetOracle() above - - //This value is dictated by the query that is being run as part of this test. In this case, we're running a query that returns a single row. - //This results in two calls to read. Therefore the call count for the Iterate metric should be 2. - var expectedIterateCallCount = 2; - - var expectedMetrics = new List - { - new Assertions.ExpectedMetric { metricName = @"Datastore/all", callCount = expectedDatastoreCallCount }, - new Assertions.ExpectedMetric { metricName = @"Datastore/allWeb", callCount = expectedDatastoreCallCount }, - new Assertions.ExpectedMetric { metricName = @"Datastore/Oracle/all", callCount = expectedDatastoreCallCount }, - new Assertions.ExpectedMetric { metricName = @"Datastore/Oracle/allWeb", callCount = expectedDatastoreCallCount }, - new Assertions.ExpectedMetric { metricName = $@"Datastore/instance/Oracle/{OracleConfiguration.OracleServer}/{OracleConfiguration.OraclePort}", callCount = expectedDatastoreCallCount}, - new Assertions.ExpectedMetric { metricName = @"Datastore/operation/Oracle/select", callCount = 2 }, - new Assertions.ExpectedMetric { metricName = @"Datastore/statement/Oracle/user_tables/select", callCount = 1 }, - new Assertions.ExpectedMetric { metricName = @"Datastore/statement/Oracle/user_tables/select", callCount = 1, metricScope = "WebTransaction/MVC/OracleController/Oracle"}, - new Assertions.ExpectedMetric { metricName = $@"Datastore/statement/Oracle/{_fixture.TableName}/select", callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $@"Datastore/statement/Oracle/{_fixture.TableName}/select", callCount = 1, metricScope = "WebTransaction/MVC/OracleController/Oracle"}, - new Assertions.ExpectedMetric { metricName = @"Datastore/operation/Oracle/insert", callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $@"Datastore/statement/Oracle/{_fixture.TableName}/insert", callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $@"Datastore/statement/Oracle/{_fixture.TableName}/insert", callCount = 1, metricScope = "WebTransaction/MVC/OracleController/Oracle"}, - new Assertions.ExpectedMetric { metricName = @"Datastore/operation/Oracle/delete", callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $@"Datastore/statement/Oracle/{_fixture.TableName}/delete", callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $@"Datastore/statement/Oracle/{_fixture.TableName}/delete", callCount = 1, metricScope = "WebTransaction/MVC/OracleController/Oracle"}, - - new Assertions.ExpectedMetric { metricName = @"DotNet/DatabaseResult/Iterate" , callCount = expectedIterateCallCount }, - new Assertions.ExpectedMetric { metricName = @"DotNet/DatabaseResult/Iterate", callCount = expectedIterateCallCount, metricScope = "WebTransaction/MVC/OracleController/Oracle"} - }; - var unexpectedMetrics = new List - { - // The datastore operation happened inside a web transaction so there should be no allOther metrics - new Assertions.ExpectedMetric { metricName = @"Datastore/allOther" }, - new Assertions.ExpectedMetric { metricName = @"Datastore/Oracle/allOther"}, - - // The operation metric should not be scoped because the statement metric is scoped instead - new Assertions.ExpectedMetric { metricName = @"Datastore/operation/Oracle/select", metricScope = "WebTransaction/MVC/OracleController/Oracle" }, - new Assertions.ExpectedMetric { metricName = @"Datastore/operation/Oracle/insert", metricScope = "WebTransaction/MVC/OracleController/Oracle" }, - new Assertions.ExpectedMetric { metricName = @"Datastore/operation/Oracle/delete", metricScope = "WebTransaction/MVC/OracleController/Oracle" } - }; - var expectedTransactionTraceSegments = new List - { - "Datastore/statement/Oracle/user_tables/select" - }; - - var expectedTransactionEventIntrinsicAttributes = new List - { - "databaseDuration" - }; - var expectedSqlTraces = new List - { - new Assertions.ExpectedSqlTrace - { - TransactionName = "WebTransaction/MVC/OracleController/Oracle", - Sql = "SELECT DEGREE FROM user_tables WHERE ROWNUM <= ?", - DatastoreMetricName = "Datastore/statement/Oracle/user_tables/select", - HasExplainPlan = false - }, - new Assertions.ExpectedSqlTrace - { - TransactionName = "WebTransaction/MVC/OracleController/Oracle", - Sql = $"SELECT COUNT(*) FROM {_fixture.TableName}", - DatastoreMetricName = $"Datastore/statement/Oracle/{_fixture.TableName}/select", - - HasExplainPlan = false - }, - new Assertions.ExpectedSqlTrace - { - TransactionName = "WebTransaction/MVC/OracleController/Oracle", - Sql = $"INSERT INTO {_fixture.TableName} (HOTEL_ID, BOOKING_DATE) VALUES (?, SYSDATE)", - DatastoreMetricName = $"Datastore/statement/Oracle/{_fixture.TableName}/insert", - - HasExplainPlan = false - }, - new Assertions.ExpectedSqlTrace - { - TransactionName = "WebTransaction/MVC/OracleController/Oracle", - Sql = $"DELETE FROM {_fixture.TableName} WHERE HOTEL_ID = ?", - DatastoreMetricName = $"Datastore/statement/Oracle/{_fixture.TableName}/delete", - - HasExplainPlan = false - } - }; - - var metrics = _fixture.AgentLog.GetMetrics().ToList(); - var transactionSample = _fixture.AgentLog.TryGetTransactionSample("WebTransaction/MVC/OracleController/Oracle"); - var transactionEvent = _fixture.AgentLog.TryGetTransactionEvent("WebTransaction/MVC/OracleController/Oracle"); - var sqlTraces = _fixture.AgentLog.GetSqlTraces().ToList(); - - NrAssert.Multiple( - () => Assert.NotNull(transactionSample), - () => Assert.NotNull(transactionEvent) - ); - - NrAssert.Multiple - ( - () => Assertions.MetricsExist(expectedMetrics, metrics), - () => Assertions.MetricsDoNotExist(unexpectedMetrics, metrics), - () => Assertions.TransactionTraceSegmentsExist(expectedTransactionTraceSegments, transactionSample), - () => Assertions.TransactionEventHasAttributes(expectedTransactionEventIntrinsicAttributes, TransactionEventAttributeType.Intrinsic, transactionEvent), - () => Assertions.SqlTraceExists(expectedSqlTraces, sqlTraces) - ); - } - } -} From b21b93420a21009d10a245c335a0fd202a767301 Mon Sep 17 00:00:00 2001 From: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Thu, 29 Aug 2024 11:48:46 -0500 Subject: [PATCH 4/5] minor cleanup --- .../NetStandardLibraries/Oracle/OracleExerciser.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Oracle/OracleExerciser.cs b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Oracle/OracleExerciser.cs index b1bafb55cc..620574308f 100644 --- a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Oracle/OracleExerciser.cs +++ b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Oracle/OracleExerciser.cs @@ -24,9 +24,11 @@ public class OracleExerciser : IDisposable private const string InsertHotelOracleSql = "INSERT INTO {0} (HOTEL_ID, BOOKING_DATE) VALUES (1, SYSDATE)"; private const string DeleteHotelOracleSql = "DELETE FROM {0} WHERE HOTEL_ID = 1"; private const string CountHotelOracleSql = "SELECT COUNT(*) FROM {0}"; + private const string SelectDegreeFromUserTablesWhereRownum = "SELECT DEGREE FROM user_tables WHERE ROWNUM <= 1"; private string _tableName; + private string _storedProcedureName; [LibraryMethod] public void InitializeTable(string tableName) @@ -55,7 +57,7 @@ public void ExerciseSync() using var connection = new OracleConnection(connectionString); connection.Open(); - using (var command = new OracleCommand("SELECT DEGREE FROM user_tables WHERE ROWNUM <= 1", connection)) + using (var command = new OracleCommand(SelectDegreeFromUserTablesWhereRownum, connection)) using (var reader = command.ExecuteReader()) { while (reader.Read()) @@ -97,7 +99,7 @@ public async Task ExerciseAsync() using var connection = new OracleConnection(connectionString); await connection.OpenAsync(); - using (var command = new OracleCommand("SELECT DEGREE FROM user_tables WHERE ROWNUM <= 1", connection)) + using (var command = new OracleCommand(SelectDegreeFromUserTablesWhereRownum, connection)) using (var reader = await command.ExecuteReaderAsync()) { while (await reader.ReadAsync()) @@ -186,14 +188,13 @@ public void Dispose() _storedProcedureName = null; } - private readonly string createProcedureStatment = @"CREATE PROCEDURE {0} ({1}) IS BEGIN NULL; END {0};"; + private readonly string createProcedureStatement = @"CREATE PROCEDURE {0} ({1}) IS BEGIN NULL; END {0};"; private readonly string dropProcedureStatement = @"DROP PROCEDURE {0}"; - private string _storedProcedureName; private void CreateProcedure() { var parameters = string.Join(", ", DbParameterData.OracleParameters.Select(x => $"{x.ParameterName} IN {x.DbTypeName}")); - var statement = string.Format(createProcedureStatment, _storedProcedureName, parameters); + var statement = string.Format(createProcedureStatement, _storedProcedureName, parameters); using (var connection = new OracleConnection(OracleConfiguration.OracleConnectionString)) using (var command = new OracleCommand(statement, connection)) { From f14b180d297a8efcf15aa9358798e54fb5463400 Mon Sep 17 00:00:00 2001 From: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Thu, 29 Aug 2024 11:49:31 -0500 Subject: [PATCH 5/5] Another tweak --- .../NetStandardLibraries/Oracle/OracleExerciser.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Oracle/OracleExerciser.cs b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Oracle/OracleExerciser.cs index 620574308f..39044a5ae2 100644 --- a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Oracle/OracleExerciser.cs +++ b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Oracle/OracleExerciser.cs @@ -24,7 +24,7 @@ public class OracleExerciser : IDisposable private const string InsertHotelOracleSql = "INSERT INTO {0} (HOTEL_ID, BOOKING_DATE) VALUES (1, SYSDATE)"; private const string DeleteHotelOracleSql = "DELETE FROM {0} WHERE HOTEL_ID = 1"; private const string CountHotelOracleSql = "SELECT COUNT(*) FROM {0}"; - private const string SelectDegreeFromUserTablesWhereRownum = "SELECT DEGREE FROM user_tables WHERE ROWNUM <= 1"; + private const string SelectFromUserTablesOracleSql = "SELECT DEGREE FROM user_tables WHERE ROWNUM <= 1"; private string _tableName; @@ -57,7 +57,7 @@ public void ExerciseSync() using var connection = new OracleConnection(connectionString); connection.Open(); - using (var command = new OracleCommand(SelectDegreeFromUserTablesWhereRownum, connection)) + using (var command = new OracleCommand(SelectFromUserTablesOracleSql, connection)) using (var reader = command.ExecuteReader()) { while (reader.Read()) @@ -99,7 +99,7 @@ public async Task ExerciseAsync() using var connection = new OracleConnection(connectionString); await connection.OpenAsync(); - using (var command = new OracleCommand(SelectDegreeFromUserTablesWhereRownum, connection)) + using (var command = new OracleCommand(SelectFromUserTablesOracleSql, connection)) using (var reader = await command.ExecuteReaderAsync()) { while (await reader.ReadAsync())