Skip to content

Commit

Permalink
Refactor QueryBugsTest to use a non-provider specific way to configur…
Browse files Browse the repository at this point in the history
…e the contexts

Part of #17588
  • Loading branch information
AndriySvyryd committed Jan 11, 2021
1 parent 5c74e0d commit 03b3e20
Show file tree
Hide file tree
Showing 12 changed files with 4,600 additions and 5,235 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.TestModels.TransportationModel;
using Xunit.Abstractions;

Expand All @@ -14,12 +15,13 @@ protected TPTTableSplittingTestBase(ITestOutputHelper testOutputHelper)
{
}

public override void Can_use_optional_dependents_with_shared_concurrency_tokens()
public override Task Can_use_optional_dependents_with_shared_concurrency_tokens()
{
// TODO: Issue #22060
return Task.CompletedTask;
}

protected override string DatabaseName { get; } = "TPTTableSplittingTest";
protected override string StoreName { get; } = "TPTTableSplittingTest";

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
Expand Down
581 changes: 270 additions & 311 deletions test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions test/EFCore.Specification.Tests/ComplianceTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public virtual void All_test_bases_must_be_implemented()
var nonImplementedBases
= (from baseType in GetBaseTestClasses()
where !IgnoredTestBases.Contains(baseType)
&& baseType != typeof(NonSharedModelTestBase)
&& !concreteTests.Any(c => Implements(c, baseType))
select baseType.FullName)
.ToList();
Expand Down
184 changes: 184 additions & 0 deletions test/EFCore.Specification.Tests/NonSharedModelTestBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Xunit;

// ReSharper disable VirtualMemberCallInConstructor
namespace Microsoft.EntityFrameworkCore
{
public abstract class NonSharedModelTestBase : IDisposable, IAsyncLifetime
{
protected abstract string StoreName { get; }
protected abstract ITestStoreFactory TestStoreFactory { get; }

private ServiceProvider _serviceProvider;
protected IServiceProvider ServiceProvider
=> _serviceProvider ?? throw new InvalidOperationException(
$"You must call `await {nameof(InitializeAsync)}(\"DatabaseName\");` at the beggining of the test.");

private TestStore _testStore;
protected TestStore TestStore
=> _testStore ?? throw new InvalidOperationException(
$"You must call `await {nameof(InitializeAsync)}(\"DatabaseName\");` at the beggining of the test.");

private ListLoggerFactory _listLoggerFactory;
protected ListLoggerFactory ListLoggerFactory
=> _listLoggerFactory ??= (ListLoggerFactory)ServiceProvider.GetRequiredService<ILoggerFactory>();

public virtual Task InitializeAsync() => Task.CompletedTask;

protected virtual ContextFactory<TContext> Initialize<TContext>(
Action<ModelBuilder> onModelCreating = null,
Action<DbContextOptionsBuilder> onConfiguring = null,
Action<IServiceCollection> addServices = null,
Action<TContext> seed = null,
Func<string, bool> shouldLogCategory = null,
Func<TestStore> createTestStore = null,
bool usePooling = true)
where TContext : DbContext
{
var contextFactory = Initialize<TContext>(
onModelCreating, onConfiguring, addServices, shouldLogCategory, createTestStore, usePooling);

TestStore.Initialize(_serviceProvider, contextFactory.CreateContext, seed == null ? null : c => seed((TContext)c));

ListLoggerFactory.Clear();

return contextFactory;
}

protected virtual Task<ContextFactory<TContext>> InitializeAsync<TContext>(
Action<ModelBuilder> onModelCreating = null,
Action<DbContextOptionsBuilder> onConfiguring = null,
Action<IServiceCollection> addServices = null,
Action<TContext> seed = null,
Func<string, bool> shouldLogCategory = null,
Func<TestStore> createTestStore = null,
bool usePooling = true)
where TContext : DbContext
{
var contextFactory = Initialize<TContext>(
onModelCreating, onConfiguring, addServices, shouldLogCategory, createTestStore, usePooling);

TestStore.Initialize(_serviceProvider, contextFactory.CreateContext, seed == null ? null : c => seed((TContext)c));

ListLoggerFactory.Clear();

return Task.FromResult(contextFactory);
}

private ContextFactory<TContext> Initialize<TContext>(
Action<ModelBuilder> onModelCreating,
Action<DbContextOptionsBuilder> onConfiguring,
Action<IServiceCollection> addServices,
Func<string, bool> shouldLogCategory,
Func<TestStore> createTestStore,
bool usePooling)
where TContext : DbContext
{
_testStore = createTestStore?.Invoke() ?? CreateTestStore();

shouldLogCategory ??= _ => false;
var services = TestStoreFactory.AddProviderServices(new ServiceCollection())
.AddSingleton<ILoggerFactory>(TestStoreFactory.CreateListLoggerFactory(shouldLogCategory));

if (onModelCreating != null)
{
services = services.AddSingleton(TestModelSource.GetFactory(onModelCreating));
}

if (addServices != null)
{
addServices(services);
}

services = usePooling
? services.AddDbContextPool(typeof(TContext), (s, b) => ConfigureOptions(s, b, onConfiguring))
: services.AddDbContext(
typeof(TContext),
(s, b) => ConfigureOptions(s, b, onConfiguring),
ServiceLifetime.Transient,
ServiceLifetime.Singleton);

_serviceProvider = services.BuildServiceProvider(validateScopes: true);

var contextFactory = new ContextFactory<TContext>(_serviceProvider, usePooling, _testStore);
return contextFactory;
}

private DbContextOptionsBuilder ConfigureOptions(
IServiceProvider serviceProvider,
DbContextOptionsBuilder optionsBuilder,
Action<DbContextOptionsBuilder> onConfiguring)
{
optionsBuilder = AddOptions(TestStore.AddProviderOptions(optionsBuilder))
.UseInternalServiceProvider(serviceProvider);
onConfiguring?.Invoke(optionsBuilder);
return optionsBuilder;
}

protected virtual DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder)
=> builder
.EnableSensitiveDataLogging()
.ConfigureWarnings(
b => b.Default(WarningBehavior.Throw)
.Log(CoreEventId.SensitiveDataLoggingEnabledWarning)
.Log(CoreEventId.PossibleUnintendedReferenceComparisonWarning));

protected virtual TestStore CreateTestStore()
=> TestStoreFactory.Create(StoreName);

// Called after DisposeAsync
public virtual void Dispose()
{
}

public virtual async Task DisposeAsync()
{
if (_testStore != null)
{
await _testStore.DisposeAsync();
_testStore = null;
}

_serviceProvider?.Dispose();
_serviceProvider = null;

_listLoggerFactory = null;
}

protected class ContextFactory<TContext>
where TContext : DbContext
{
public ContextFactory(IServiceProvider serviceProvider, bool usePooling, TestStore testStore)
{
ServiceProvider = serviceProvider;
UsePooling = usePooling;
if (usePooling)
{
ContextPool ??= (IDbContextPool)ServiceProvider
.GetRequiredService(typeof(IDbContextPool<>).MakeGenericType(typeof(TContext)));
}

TestStore = testStore;
}

public IServiceProvider ServiceProvider { get; }
protected virtual bool UsePooling { get; }
private IDbContextPool ContextPool { get; }
public TestStore TestStore { get; }

public virtual TContext CreateContext()
=> UsePooling
? (TContext)new DbContextLease(ContextPool, standalone: true).Context
: (TContext)ServiceProvider.GetRequiredService(typeof(TContext));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1683,6 +1683,20 @@ where c.CustomerID.StartsWith("F")
entryCount: 26);
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Repro9735(bool async)
{
return AssertQuery(
async,
ss => ss.Set<Order>()
.Include(b => b.OrderDetails)
.OrderBy(b => b.Customer.CustomerID != null)
.ThenBy(b => b.Customer != null ? b.Customer.CustomerID : string.Empty)
.Take(2),
entryCount: 6);
}

protected virtual void ClearLog()
{
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Xunit;

Expand Down
2 changes: 1 addition & 1 deletion test/EFCore.Specification.Tests/TestUtilities/TestStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public virtual TestStore Initialize(
}
else
{
Initialize(createContext, seed, clean);
GetTestStoreIndex(serviceProvider).CreateNonShared(GetType().Name + Name, () => Initialize(createContext, seed, clean));
}

return this;
Expand Down
27 changes: 27 additions & 0 deletions test/EFCore.Specification.Tests/TestUtilities/TestStoreIndex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;

namespace Microsoft.EntityFrameworkCore.TestUtilities
{
Expand Down Expand Up @@ -39,5 +40,31 @@ public virtual void CreateShared(string name, Action initializeDatabase)
}
}
}

public virtual void CreateNonShared(string name, Action initializeDatabase)
{
var creationLock = _creationLocks.GetOrAdd(name, new object());

if (Monitor.TryEnter(creationLock))
{
try
{
initializeDatabase?.Invoke();
}
finally
{
Monitor.Exit(creationLock);
if (!_creationLocks.TryRemove(name, out _))
{
throw new InvalidOperationException($"An attempt was made to initialize a non-shared store {name} from two different threads.");
}
}
}
else
{
_creationLocks.TryRemove(name, out _);
throw new InvalidOperationException($"An attempt was made to initialize a non-shared store {name} from two different threads.");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1333,6 +1333,36 @@ WHERE [c].[CustomerID] LIKE N'F%'
ORDER BY [c].[CustomerID], [t0].[OrderID], [o0].[OrderID], [o0].[ProductID]");
}

public override async Task Repro9735(bool async)
{
await base.Repro9735(async);

AssertSql(
@"@__p_0='2'
SELECT [t].[OrderID], [t].[CustomerID], [t].[EmployeeID], [t].[OrderDate], [t].[CustomerID0], [o0].[OrderID], [o0].[ProductID], [o0].[Discount], [o0].[Quantity], [o0].[UnitPrice]
FROM (
SELECT TOP(@__p_0) [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [c].[CustomerID] AS [CustomerID0], CASE
WHEN [c].[CustomerID] IS NOT NULL THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END AS [c], CASE
WHEN [c].[CustomerID] IS NOT NULL THEN [c].[CustomerID]
ELSE N''
END AS [c0]
FROM [Orders] AS [o]
LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID]
ORDER BY CASE
WHEN [c].[CustomerID] IS NOT NULL THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END, CASE
WHEN [c].[CustomerID] IS NOT NULL THEN [c].[CustomerID]
ELSE N''
END
) AS [t]
LEFT JOIN [Order Details] AS [o0] ON [t].[OrderID] = [o0].[OrderID]
ORDER BY [t].[c], [t].[c0], [t].[OrderID], [t].[CustomerID0], [o0].[OrderID], [o0].[ProductID]");
}

private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);

Expand Down
Loading

0 comments on commit 03b3e20

Please sign in to comment.