-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
CSHARP-5338: Improve integration test performance by test fixtures #1332
Changes from 10 commits
6d24ea2
45ed4ef
bfb4692
87d4983
6ee6c6b
a9b5f3c
5142f1e
e07ca74
15266fd
508842d
437c687
8b433e4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
/* Copyright 2010-present MongoDB Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
using System; | ||
using MongoDB.Driver.Core.TestHelpers.XunitExtensions; | ||
using MongoDB.TestHelpers.XunitExtensions; | ||
using Xunit; | ||
|
||
namespace MongoDB.Driver.Tests | ||
{ | ||
[IntegrationTest] | ||
public abstract class IntegrationTest<TFixture> : IClassFixture<TFixture> | ||
where TFixture : MongoDatabaseFixture | ||
{ | ||
private readonly TFixture _fixture; | ||
BorisDog marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
protected IntegrationTest(TFixture fixture, Action<RequireServer> requireServerCheck = null) | ||
{ | ||
_fixture = fixture; | ||
requireServerCheck?.Invoke(RequireServer.Check()); | ||
_fixture.BeforeTestCase(); | ||
} | ||
|
||
public TFixture Fixture => _fixture; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
/* Copyright 2010-present MongoDB Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using FluentAssertions; | ||
using MongoDB.Bson; | ||
using MongoDB.Bson.Serialization; | ||
using MongoDB.Driver.Core.TestHelpers.XunitExtensions; | ||
using MongoDB.Driver.Linq.Linq3Implementation; | ||
using MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToExecutableQueryTranslators; | ||
using Xunit.Abstractions; | ||
|
||
namespace MongoDB.Driver.Tests | ||
{ | ||
public abstract class LinqIntegrationTest<TFixture> : IntegrationTest<TFixture> | ||
where TFixture : MongoDatabaseFixture | ||
{ | ||
public LinqIntegrationTest(TFixture fixture, Action<RequireServer> requireServerCheck = null) | ||
: base(fixture, requireServerCheck) | ||
{ | ||
} | ||
|
||
protected void AssertStages(IEnumerable<BsonDocument> stages, params string[] expectedStages) | ||
{ | ||
AssertStages(stages, (IEnumerable<string>)expectedStages); | ||
} | ||
|
||
protected void AssertStages(IEnumerable<BsonDocument> stages, IEnumerable<string> expectedStages) | ||
{ | ||
stages.Should().Equal(expectedStages.Select(json => BsonDocument.Parse(json))); | ||
} | ||
|
||
protected static List<BsonDocument> Translate<TDocument, TResult>(IMongoCollection<TDocument> collection, IAggregateFluent<TResult> aggregate) => | ||
Translate(collection, aggregate, out _); | ||
|
||
protected static List<BsonDocument> Translate<TDocument, TResult>(IMongoCollection<TDocument> collection, IAggregateFluent<TResult> aggregate, out IBsonSerializer<TResult> outputSerializer) | ||
{ | ||
var pipelineDefinition = ((AggregateFluent<TDocument, TResult>)aggregate).Pipeline; | ||
var documentSerializer = collection.DocumentSerializer; | ||
var translationOptions = aggregate.Options?.TranslationOptions.AddMissingOptionsFrom(collection.Database.Client.Settings.TranslationOptions); | ||
return Translate(pipelineDefinition, documentSerializer, translationOptions, out outputSerializer); | ||
} | ||
|
||
// in this overload the collection argument is used only to infer the TDocument type | ||
protected List<BsonDocument> Translate<TDocument, TResult>(IMongoCollection<TDocument> collection, IQueryable<TResult> queryable) | ||
{ | ||
return Translate<TDocument, TResult>(queryable); | ||
} | ||
|
||
// in this overload the collection argument is used only to infer the TDocument type | ||
protected List<BsonDocument> Translate<TDocument, TResult>(IMongoCollection<TDocument> collection, IQueryable<TResult> queryable, out IBsonSerializer<TResult> outputSerializer) | ||
{ | ||
return Translate<TDocument, TResult>(queryable, out outputSerializer); | ||
} | ||
|
||
protected static List<BsonDocument> Translate<TResult>(IMongoDatabase database, IAggregateFluent<TResult> aggregate) | ||
{ | ||
var pipelineDefinition = ((AggregateFluent<NoPipelineInput, TResult>)aggregate).Pipeline; | ||
var translationOptions = aggregate.Options?.TranslationOptions.AddMissingOptionsFrom(database.Client.Settings.TranslationOptions); | ||
return Translate(pipelineDefinition, NoPipelineInputSerializer.Instance, translationOptions); | ||
} | ||
|
||
// in this overload the database argument is used only to infer the NoPipelineInput type | ||
protected List<BsonDocument> Translate<TResult>(IMongoDatabase database, IQueryable<TResult> queryable) | ||
{ | ||
return Translate<NoPipelineInput, TResult>(queryable); | ||
} | ||
|
||
protected List<BsonDocument> Translate<TDocument, TResult>(IQueryable<TResult> queryable) | ||
{ | ||
return Translate<TDocument, TResult>(queryable, out _); | ||
} | ||
|
||
protected List<BsonDocument> Translate<TDocument, TResult>(IQueryable<TResult> queryable, out IBsonSerializer<TResult> outputSerializer) | ||
{ | ||
var provider = (MongoQueryProvider<TDocument>)queryable.Provider; | ||
var translationOptions = provider.GetTranslationOptions(); | ||
var executableQuery = ExpressionToExecutableQueryTranslator.Translate<TDocument, TResult>(provider, queryable.Expression, translationOptions); | ||
var stages = executableQuery.Pipeline.Ast.Stages; | ||
outputSerializer = (IBsonSerializer<TResult>)executableQuery.Pipeline.OutputSerializer; | ||
return stages.Select(s => s.Render().AsBsonDocument).ToList(); | ||
} | ||
|
||
protected static List<BsonDocument> Translate<TDocument, TResult>( | ||
PipelineDefinition<TDocument, TResult> pipelineDefinition, | ||
IBsonSerializer<TDocument> documentSerializer, | ||
ExpressionTranslationOptions translationOptions) => | ||
Translate(pipelineDefinition, documentSerializer, translationOptions, out _); | ||
|
||
protected static List<BsonDocument> Translate<TDocument, TResult>( | ||
PipelineDefinition<TDocument, TResult> pipelineDefinition, | ||
IBsonSerializer<TDocument> documentSerializer, | ||
ExpressionTranslationOptions translationOptions, | ||
out IBsonSerializer<TResult> outputSerializer) | ||
{ | ||
var serializerRegistry = BsonSerializer.SerializerRegistry; | ||
documentSerializer ??= serializerRegistry.GetSerializer<TDocument>(); | ||
var renderedPipeline = pipelineDefinition.Render(new(documentSerializer, serializerRegistry, translationOptions: translationOptions)); | ||
outputSerializer = renderedPipeline.OutputSerializer; | ||
return renderedPipeline.Documents.ToList(); | ||
} | ||
|
||
protected BsonDocument Translate<TDocument>(IMongoCollection<TDocument> collection, FilterDefinition<TDocument> filterDefinition) | ||
{ | ||
var documentSerializer = collection.DocumentSerializer; | ||
var serializerRegistry = BsonSerializer.SerializerRegistry; | ||
var translationOptions = collection.Database.Client.Settings.TranslationOptions; | ||
return filterDefinition.Render(new(documentSerializer, serializerRegistry, translationOptions: translationOptions)); | ||
} | ||
|
||
protected BsonDocument TranslateFilter<TDocument>(IMongoCollection<TDocument> collection, FilterDefinition<TDocument> filter) | ||
{ | ||
var documentSerializer = collection.DocumentSerializer; | ||
var serializerRegistry = BsonSerializer.SerializerRegistry; | ||
var translationOptions = collection.Database.Client.Settings.TranslationOptions; | ||
return filter.Render(new(documentSerializer, serializerRegistry, translationOptions: translationOptions)); | ||
} | ||
|
||
protected BsonDocument TranslateFindFilter<TDocument, TProjection>(IMongoCollection<TDocument> collection, IFindFluent<TDocument, TProjection> find) | ||
{ | ||
var filterDefinition = ((FindFluent<TDocument, TProjection>)find).Filter; | ||
var translationOptions = collection.Database.Client.Settings.TranslationOptions; | ||
return filterDefinition.Render(new(collection.DocumentSerializer, BsonSerializer.SerializerRegistry, translationOptions: translationOptions)); | ||
} | ||
|
||
protected BsonDocument TranslateFindProjection<TDocument, TProjection>( | ||
IMongoCollection<TDocument> collection, | ||
IFindFluent<TDocument, TProjection> find) => | ||
TranslateFindProjection(collection, find, out _); | ||
|
||
protected BsonDocument TranslateFindProjection<TDocument, TProjection>( | ||
IMongoCollection<TDocument> collection, | ||
IFindFluent<TDocument, TProjection> find, | ||
out IBsonSerializer<TProjection> projectionSerializer) | ||
{ | ||
var projection = ((FindFluent<TDocument, TProjection>)find).Options.Projection; | ||
var translationOptions = find.Options?.TranslationOptions.AddMissingOptionsFrom(collection.Database.Client.Settings.TranslationOptions); | ||
return TranslateFindProjection(collection, projection, translationOptions, out projectionSerializer); | ||
} | ||
|
||
protected BsonDocument TranslateFindProjection<TDocument, TProjection>( | ||
IMongoCollection<TDocument> collection, | ||
ProjectionDefinition<TDocument, TProjection> projection, | ||
ExpressionTranslationOptions translationOptions) => | ||
TranslateFindProjection(collection, projection, translationOptions, out _); | ||
|
||
protected BsonDocument TranslateFindProjection<TDocument, TProjection>( | ||
IMongoCollection<TDocument> collection, | ||
ProjectionDefinition<TDocument, TProjection> projection, | ||
ExpressionTranslationOptions translationOptions, | ||
out IBsonSerializer<TProjection> projectionSerializer) | ||
{ | ||
var documentSerializer = collection.DocumentSerializer; | ||
var serializerRegistry = BsonSerializer.SerializerRegistry; | ||
var renderedProjection = projection.Render(new(documentSerializer, serializerRegistry, translationOptions: translationOptions, renderForFind: true)); | ||
projectionSerializer = renderedProjection.ProjectionSerializer; | ||
return renderedProjection.Document; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
/* Copyright 2010-present MongoDB Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using MongoDB.Driver.Tests; | ||
|
||
namespace MongoDB.Driver.TestHelpers | ||
{ | ||
public abstract class MongoCollectionFixture<TDocument> : MongoDatabaseFixture | ||
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add:
|
||
private readonly Lazy<IMongoCollection<TDocument>> _collection; | ||
private bool _dataInitialized; | ||
|
||
protected MongoCollectionFixture() | ||
{ | ||
_collection = new Lazy<IMongoCollection<TDocument>>(CreateCollection); | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add:
|
||
public IMongoCollection<TDocument> Collection => _collection.Value; | ||
|
||
protected abstract IEnumerable<TDocument> InitialData { get; } | ||
|
||
public virtual bool InitializeDataBeforeEachTestCase => false; | ||
|
||
protected override void InitializeTestCase() | ||
{ | ||
if (InitializeDataBeforeEachTestCase || !_dataInitialized) | ||
{ | ||
Collection.Database.DropCollection(Collection.CollectionNamespace.CollectionName); | ||
|
||
if (InitialData == null) | ||
{ | ||
Collection.Database.CreateCollection(Collection.CollectionNamespace.CollectionName); | ||
} | ||
else | ||
{ | ||
Collection.InsertMany(InitialData); | ||
} | ||
|
||
_dataInitialized = true; | ||
} | ||
} | ||
|
||
protected virtual string GetCollectionName() | ||
{ | ||
var currentType = GetType(); | ||
var result = currentType.DeclaringType?.Name; | ||
|
||
if (string.IsNullOrEmpty(result)) | ||
{ | ||
throw new InvalidOperationException("Cannot resolve the collection name. Try to override GetCollectionName for custom collection name resolution."); | ||
} | ||
|
||
return result; | ||
} | ||
|
||
private IMongoCollection<TDocument> CreateCollection() | ||
{ | ||
return CreateCollection<TDocument>(GetCollectionName()); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
/* Copyright 2010-present MongoDB Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
|
||
namespace MongoDB.Driver.Tests | ||
{ | ||
public class MongoDatabaseFixture : IDisposable | ||
{ | ||
private static readonly string __timeStamp = DateTime.Now.ToString("MMddHHmm"); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add:
|
||
private readonly Lazy<IMongoClient> _client; | ||
private readonly Lazy<IMongoDatabase> _database; | ||
private readonly string _databaseName = $"CSTests{__timeStamp}"; | ||
private bool _fixtureInialized; | ||
private readonly HashSet<string> _usedCollections = new(); | ||
|
||
public MongoDatabaseFixture() | ||
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add:
|
||
_client = new Lazy<IMongoClient>(CreateClient); | ||
_database = new Lazy<IMongoDatabase>(CreateDatabase); | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add:
|
||
public IMongoClient Client => _client.Value; | ||
public IMongoDatabase Database => _database.Value; | ||
|
||
public virtual void Dispose() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider refactoring to:
|
||
{ | ||
var database = _database.IsValueCreated ? _database.Value : null; | ||
if (database != null) | ||
{ | ||
foreach (var collection in _usedCollections) | ||
{ | ||
database.DropCollection(collection); | ||
} | ||
} | ||
} | ||
|
||
protected IMongoCollection<TDocument> CreateCollection<TDocument>(string collectionName = null) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
{ | ||
if (string.IsNullOrEmpty(collectionName)) | ||
{ | ||
var stack = new System.Diagnostics.StackTrace(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code is not reached. Throw an exception instead. |
||
var frame = stack.GetFrame(1); // skip 1 frame to get the calling method info | ||
var method = frame.GetMethod(); | ||
collectionName = $"{method.DeclaringType.Name}_{method.Name}"; | ||
} | ||
|
||
Database.DropCollection(collectionName); | ||
_usedCollections.Add(collectionName); | ||
|
||
return Database.GetCollection<TDocument>(collectionName); | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add value factory methods for
|
||
protected virtual IMongoClient CreateClient() | ||
=> DriverTestConfiguration.Client; | ||
|
||
protected virtual IMongoDatabase CreateDatabase() | ||
{ | ||
return Client.GetDatabase(_databaseName); | ||
} | ||
|
||
internal void BeforeTestCase() | ||
{ | ||
if (!_fixtureInialized) | ||
{ | ||
InitializeFixture(); | ||
_fixtureInialized = true; | ||
} | ||
|
||
InitializeTestCase(); | ||
} | ||
|
||
protected virtual void InitializeFixture() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably have to add some inline documentation here, but the idea was: |
||
{} | ||
|
||
protected virtual void InitializeTestCase() | ||
{} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably worth inheriting for
LoggableTestClass
, to have logging and timeouts for all.Ideally all test would derive from
LoggableTestClass
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IntegrationTest
is now inherited fromLoggableTestClass
. Let's discuss offline if we need some extra functionality around.