Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor QueryBugsTest to use a non-provider specific way to configure the contexts #23837

Merged
merged 1 commit into from
Jan 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
smitpatel marked this conversation as resolved.
Show resolved Hide resolved

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>(
smitpatel marked this conversation as resolved.
Show resolved Hide resolved
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