Skip to content

Commit

Permalink
Optimize tests
Browse files Browse the repository at this point in the history
To not recreate each sproc before every test
  • Loading branch information
roji committed Aug 1, 2022
1 parent 5fbbd31 commit 8edfbcf
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 41 deletions.
4 changes: 2 additions & 2 deletions src/EFCore.Relational/Update/ResultSetMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace Microsoft.EntityFrameworkCore.Update;
public enum ResultSetMapping
{
/// <summary>
/// The command does not have any result set mapping.
/// The command does not have any results, neither as rows nor as output parameters.
/// </summary>
NoResults = 0,

Expand All @@ -43,7 +43,7 @@ public enum ResultSetMapping
/// position value, in order to look up the correct <see cref="ModificationCommand" /> and propagate the values. When this bit is
/// enabled, the current result row contains such a position value.
/// </summary>
IsPositionalResultMappingEnabled = 8,
IsPositionalResultMappingEnabled = 9,

/// <summary>
/// The command has output parameters.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ public StoredProcedureUpdateContext(DbContextOptions options)
public DbSet<WithOutputParameter> WithOutputParameter { get; set; }
public DbSet<WithResultColumn> WithResultColumn { get; set; }

public static void Seed(StoredProcedureUpdateContext context, bool useGeneratedKeys)
public static void Seed(StoredProcedureUpdateContext context)
{
context.WithOutputParameter.Add(new() { Name = "Foo" });
context.WithOutputParameter.Add(new() { Name = "Pre-seeded" });

context.SaveChanges();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
}

protected override void Seed(StoredProcedureUpdateContext context)
{
// TODO: This means we recreate all sprocs on each test run. Should introduce an extension point for one-time initialization
// which doesn't happen with reseeding
CreateStoredProcedures(context);

StoredProcedureUpdateContext.Seed(context, useGeneratedKeys: true);
}

protected abstract void CreateStoredProcedures(StoredProcedureUpdateContext context);
=> StoredProcedureUpdateContext.Seed(context);

// TODO: Look into this again
protected override void Clean(DbContext context)
=> StoredProcedureUpdateContext.Clean(CreateContext());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public virtual async Task Update(bool async)
{
await using var context = CreateContext();

var entity = (await context.WithOutputParameter.FindAsync(1))!;
var entity = await context.WithOutputParameter.Where(w => w.Name == "Pre-seeded").SingleAsync();
entity.Name = "Updated";

ClearLog();
Expand All @@ -84,7 +84,7 @@ public virtual async Task Update(bool async)

using (Fixture.TestSqlLoggerFactory.SuspendRecordingEvents())
{
Assert.Equal("Updated", context.WithOutputParameter.Single(b => b.Id == 1).Name);
Assert.Equal("Updated", (await context.WithOutputParameter.Where(w => w.Id == entity.Id).SingleAsync()).Name);
}
}

Expand All @@ -94,7 +94,7 @@ public virtual async Task Delete(bool async)
{
await using var context = CreateContext();

var entity = (await context.WithOutputParameter.FindAsync(1))!;
var entity = await context.WithOutputParameter.Where(w => w.Name == "Pre-seeded").SingleAsync();
context.WithOutputParameter.Remove(entity);

ClearLog();
Expand All @@ -103,7 +103,7 @@ public virtual async Task Delete(bool async)

using (Fixture.TestSqlLoggerFactory.SuspendRecordingEvents())
{
Assert.Equal(0, context.WithOutputParameter.Count(b => b.Id == 1));
Assert.Equal(0, await context.WithOutputParameter.CountAsync(b => b.Name == "Pre-seeded"));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ protected SqlServerAdventureWorksTestStoreFactory()
}

public override TestStore GetOrCreate(string storeName)
=> SqlServerTestStore.GetOrCreate(
=> SqlServerTestStore.GetOrCreateWithScriptPath(
"adventureworks",
Path.Combine("SqlAzure", "adventureworks.sql"));
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ protected SqlServerNorthwindTestStoreFactory()
}

public override TestStore GetOrCreate(string storeName)
=> SqlServerTestStore.GetOrCreate(storeName, "Northwind.sql");
=> SqlServerTestStore.GetOrCreateWithScriptPath(storeName, "Northwind.sql");
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ public static SqlServerTestStore GetOrCreate(string name)
public static SqlServerTestStore GetOrCreateInitialized(string name)
=> new SqlServerTestStore(name).InitializeSqlServer(null, (Func<DbContext>)null, null);

public static SqlServerTestStore GetOrCreate(string name, string scriptPath, bool? multipleActiveResultSets = null)
public static SqlServerTestStore GetOrCreateWithInitScript(string name, string initScript)
=> new(name, initScript: initScript);

public static SqlServerTestStore GetOrCreateWithScriptPath(string name, string scriptPath, bool? multipleActiveResultSets = null)
=> new(name, scriptPath: scriptPath, multipleActiveResultSets: multipleActiveResultSets);

public static SqlServerTestStore Create(string name, bool useFileName = false)
Expand All @@ -37,12 +40,14 @@ public static SqlServerTestStore CreateInitialized(string name, bool useFileName
.InitializeSqlServer(null, (Func<DbContext>)null, null);

private readonly string _fileName;
private readonly string _initScript;
private readonly string _scriptPath;

private SqlServerTestStore(
string name,
bool useFileName = false,
bool? multipleActiveResultSets = null,
string initScript = null,
string scriptPath = null,
bool shared = true)
: base(name, shared)
Expand All @@ -52,6 +57,11 @@ private SqlServerTestStore(
_fileName = Path.Combine(CurrentDirectory, name + ".mdf");
}

if (initScript != null)
{
_initScript = initScript;
}

if (scriptPath != null)
{
_scriptPath = Path.Combine(Path.GetDirectoryName(typeof(SqlServerTestStore).Assembly.Location), scriptPath);
Expand Down Expand Up @@ -79,12 +89,18 @@ protected override void Initialize(Func<DbContext> createContext, Action<DbConte
{
if (_scriptPath != null)
{
ExecuteScript(_scriptPath);
ExecuteScript(File.ReadAllText(_scriptPath));
}
else
{
using var context = createContext();
context.Database.EnsureCreatedResiliently();

if (_initScript != null)
{
ExecuteScript(_initScript);
}

seed?.Invoke(context);
}
}
Expand All @@ -102,8 +118,7 @@ private bool CreateDatabase(Action<DbContext> clean)
if (ExecuteScalar<int>(master, $"SELECT COUNT(*) FROM sys.databases WHERE name = N'{Name}'") > 0)
{
// Only reseed scripted databases during CI runs
if (_scriptPath != null
&& !TestEnvironment.IsCI)
if (_scriptPath != null && !TestEnvironment.IsCI)
{
return false;
}
Expand Down Expand Up @@ -134,9 +149,8 @@ private bool CreateDatabase(Action<DbContext> clean)
public override void Clean(DbContext context)
=> context.Database.EnsureClean();

public void ExecuteScript(string scriptPath)
public void ExecuteScript(string script)
{
var script = File.ReadAllText(scriptPath);
Execute(
Connection, command =>
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.EntityFrameworkCore.TestModels.StoredProcedureUpdateModel;

namespace Microsoft.EntityFrameworkCore.Update;

#nullable enable

public class StoredProcedureUpdateSqlServerTest
: StoredProcedureUpdateTestBase<StoredProcedureUpdateSqlServerTest.StoredProcedureUpdateSqlServerFixture>
{
Expand Down Expand Up @@ -79,30 +79,75 @@ public override async Task Delete(bool async)
public class StoredProcedureUpdateSqlServerFixture : StoredProcedureUpdateFixtureBase
{
protected override ITestStoreFactory TestStoreFactory
=> SqlServerTestStoreFactory.Instance;
=> StoredProcedureTestStoryFactory.Instance;

private string? _identityResetCommand;

public override void Reseed()
{
using var context = CreateContext();
Clean(context);
Seed(context);
}

protected override void CreateStoredProcedures(StoredProcedureUpdateContext context)
protected override void Clean(DbContext context)
{
// TODO: Roundtrip per procedure, for every single test. Make this more efficient.
base.Clean(context);

// Reset the IDENTITY values since we assert on them
context.Database.ExecuteSqlRaw(GetIdentityResetCommand());
}

private string GetIdentityResetCommand()
{
if (_identityResetCommand is not null)
{
return _identityResetCommand;
}

var context = CreateContext();
var builder = new StringBuilder();

context.Database.ExecuteSqlRaw(@"
var tablesWithIdentity = context.Model.GetEntityTypes()
.Where(e => e.GetProperties().Any(p => p.GetValueGenerationStrategy() == SqlServerValueGenerationStrategy.IdentityColumn))
.Select(e => e.GetTableName());

foreach (var table in tablesWithIdentity)
{
builder.AppendLine($"DBCC CHECKIDENT ('{table}', RESEED, 0);");
}

return _identityResetCommand = builder.ToString();
}

class StoredProcedureTestStoryFactory : SqlServerTestStoreFactory
{
public static new StoredProcedureTestStoryFactory Instance { get; } = new();

public override TestStore GetOrCreate(string storeName)
=> SqlServerTestStore.GetOrCreateWithInitScript(storeName, InitScript);

private const string InitScript = @"
CREATE OR ALTER PROCEDURE WithOutputParameter_Insert(@Name varchar(max), @Id int OUT)
AS BEGIN
INSERT INTO [WithOutputParameter] ([Name]) VALUES (@Name);
SET @Id = SCOPE_IDENTITY();
END;");
INSERT INTO [WithOutputParameter] ([Name]) VALUES (@Name);
SET @Id = SCOPE_IDENTITY();
END;
GO
context.Database.ExecuteSqlRaw(@"
CREATE OR ALTER PROCEDURE WithOutputParameter_Update(@Id int, @Name varchar(max))
AS UPDATE [WithOutputParameter] SET [Name] = @Name WHERE [Id] = @id;");
AS UPDATE [WithOutputParameter] SET [Name] = @Name WHERE [Id] = @id;
GO
context.Database.ExecuteSqlRaw(@"
CREATE OR ALTER PROCEDURE WithOutputParameter_Delete(@Id int)
AS DELETE FROM [WithOutputParameter] WHERE [Id] = @Id;");
AS DELETE FROM [WithOutputParameter] WHERE [Id] = @Id;
GO
context.Database.ExecuteSqlRaw(@"
CREATE OR ALTER PROCEDURE WithResultColumn_Insert(@Name varchar(max))
AS INSERT INTO [WithResultColumn] ([Name]) OUTPUT inserted.[Id] VALUES (@Name);");
AS INSERT INTO [WithResultColumn] ([Name]) OUTPUT inserted.[Id] VALUES (@Name);";
}
}
}

0 comments on commit 8edfbcf

Please sign in to comment.