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..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,14 +70,30 @@ SPDX-License-Identifier: Apache-2.0 - - + + + + + + + + + + + + + + + + + + @@ -196,6 +212,32 @@ SPDX-License-Identifier: Apache-2.0 + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -329,6 +371,19 @@ SPDX-License-Identifier: Apache-2.0 + + + + + + + + + + + + + @@ -359,9 +414,10 @@ 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..13c4870720 100644 --- a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/MultiFunctionApplicationHelpers.csproj +++ b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/MultiFunctionApplicationHelpers.csproj @@ -80,6 +80,13 @@ + + + + + + + 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..39044a5ae2 --- /dev/null +++ b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/Oracle/OracleExerciser.cs @@ -0,0 +1,220 @@ +// 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 const string SelectFromUserTablesOracleSql = "SELECT DEGREE FROM user_tables WHERE ROWNUM <= 1"; + + + private string _tableName; + private string _storedProcedureName; + + [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(SelectFromUserTablesOracleSql, 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(SelectFromUserTablesOracleSql, 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 createProcedureStatement = @"CREATE PROCEDURE {0} ({1}) IS BEGIN NULL; END {0};"; + private readonly string dropProcedureStatement = @"DROP PROCEDURE {0}"; + + private void CreateProcedure() + { + var parameters = string.Join(", ", DbParameterData.OracleParameters.Select(x => $"{x.ParameterName} IN {x.DbTypeName}")); + var statement = string.Format(createProcedureStatement, _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/OracleSyncTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/OracleSyncTests.cs new file mode 100644 index 0000000000..d62d928623 --- /dev/null +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/Oracle/OracleSyncTests.cs @@ -0,0 +1,204 @@ +// 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 Xunit; +using Xunit.Abstractions; + +namespace NewRelic.Agent.UnboundedIntegrationTests.Oracle +{ + public abstract class OracleSyncTestsBase : NewRelicIntegrationTest + where TFixture : ConsoleDynamicMethodFixture + { + private readonly ConsoleDynamicMethodFixture _fixture; + private readonly string _tableName; + + protected OracleSyncTestsBase(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 ExerciseSync"); + + _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"); + CommonUtils.ModifyOrCreateXmlAttributeInNewRelicConfig(configPath, new[] { "configuration", "transactionTracer" }, "explainEnabled", "true"); + + 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.ShutdownLogLineRegex, 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.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 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/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 + { + "Datastore/statement/Oracle/user_tables/select" + }; + + var expectedTransactionEventIntrinsicAttributes = new List + { + "databaseDuration" + }; + var expectedSqlTraces = new List + { + new() + { + 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.OracleExerciser/ExerciseSync", + Sql = $"SELECT COUNT(*) FROM {_tableName}", + DatastoreMetricName = $"Datastore/statement/Oracle/{_tableName}/select", + + HasExplainPlan = false + }, + new() + { + TransactionName = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/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.OracleExerciser/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.OracleExerciser/ExerciseSync"); + var transactionEvent = _fixture.AgentLog.TryGetTransactionEvent("OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.Oracle.OracleExerciser/ExerciseSync"); + var sqlTraces = _fixture.AgentLog.GetSqlTraces().ToList(); + + Assert.Multiple( + () => Assert.NotNull(transactionSample), + () => Assert.NotNull(transactionEvent) + ); + + Assert.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}"; + } + } + + [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 OracleSyncTestsCoreLatest : OracleSyncTestsBase + { + 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) - ); - } - } -}