Skip to content

Commit c2bec81

Browse files
authored
CSHARP-5338: Improve integration test performance by test fixtures (#1332)
1 parent 66df06f commit c2bec81

17 files changed

+686
-240
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
using MongoDB.Driver.Core.TestHelpers.XunitExtensions;
18+
using MongoDB.TestHelpers.XunitExtensions;
19+
using Xunit;
20+
21+
namespace MongoDB.Driver.Tests
22+
{
23+
[IntegrationTest]
24+
public abstract class IntegrationTest<TFixture> : IClassFixture<TFixture>
25+
where TFixture : MongoDatabaseFixture
26+
{
27+
private readonly TFixture _fixture;
28+
29+
protected IntegrationTest(TFixture fixture, Action<RequireServer> requireServerCheck = null)
30+
{
31+
_fixture = fixture;
32+
requireServerCheck?.Invoke(RequireServer.Check());
33+
_fixture.BeforeTestCase();
34+
}
35+
36+
public TFixture Fixture => _fixture;
37+
}
38+
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.Collections.Generic;
18+
using System.Linq;
19+
using FluentAssertions;
20+
using MongoDB.Bson;
21+
using MongoDB.Bson.Serialization;
22+
using MongoDB.Driver.Core.TestHelpers.XunitExtensions;
23+
using MongoDB.Driver.Linq.Linq3Implementation;
24+
using MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToExecutableQueryTranslators;
25+
using Xunit.Abstractions;
26+
27+
namespace MongoDB.Driver.Tests
28+
{
29+
public abstract class LinqIntegrationTest<TFixture> : IntegrationTest<TFixture>
30+
where TFixture : MongoDatabaseFixture
31+
{
32+
public LinqIntegrationTest(TFixture fixture, Action<RequireServer> requireServerCheck = null)
33+
: base(fixture, requireServerCheck)
34+
{
35+
}
36+
37+
protected void AssertStages(IEnumerable<BsonDocument> stages, params string[] expectedStages)
38+
{
39+
AssertStages(stages, (IEnumerable<string>)expectedStages);
40+
}
41+
42+
protected void AssertStages(IEnumerable<BsonDocument> stages, IEnumerable<string> expectedStages)
43+
{
44+
stages.Should().Equal(expectedStages.Select(json => BsonDocument.Parse(json)));
45+
}
46+
47+
protected static List<BsonDocument> Translate<TDocument, TResult>(IMongoCollection<TDocument> collection, IAggregateFluent<TResult> aggregate) =>
48+
Translate(collection, aggregate, out _);
49+
50+
protected static List<BsonDocument> Translate<TDocument, TResult>(IMongoCollection<TDocument> collection, IAggregateFluent<TResult> aggregate, out IBsonSerializer<TResult> outputSerializer)
51+
{
52+
var pipelineDefinition = ((AggregateFluent<TDocument, TResult>)aggregate).Pipeline;
53+
var documentSerializer = collection.DocumentSerializer;
54+
var translationOptions = aggregate.Options?.TranslationOptions.AddMissingOptionsFrom(collection.Database.Client.Settings.TranslationOptions);
55+
return Translate(pipelineDefinition, documentSerializer, translationOptions, out outputSerializer);
56+
}
57+
58+
// in this overload the collection argument is used only to infer the TDocument type
59+
protected List<BsonDocument> Translate<TDocument, TResult>(IMongoCollection<TDocument> collection, IQueryable<TResult> queryable)
60+
{
61+
return Translate<TDocument, TResult>(queryable);
62+
}
63+
64+
// in this overload the collection argument is used only to infer the TDocument type
65+
protected List<BsonDocument> Translate<TDocument, TResult>(IMongoCollection<TDocument> collection, IQueryable<TResult> queryable, out IBsonSerializer<TResult> outputSerializer)
66+
{
67+
return Translate<TDocument, TResult>(queryable, out outputSerializer);
68+
}
69+
70+
protected static List<BsonDocument> Translate<TResult>(IMongoDatabase database, IAggregateFluent<TResult> aggregate)
71+
{
72+
var pipelineDefinition = ((AggregateFluent<NoPipelineInput, TResult>)aggregate).Pipeline;
73+
var translationOptions = aggregate.Options?.TranslationOptions.AddMissingOptionsFrom(database.Client.Settings.TranslationOptions);
74+
return Translate(pipelineDefinition, NoPipelineInputSerializer.Instance, translationOptions);
75+
}
76+
77+
// in this overload the database argument is used only to infer the NoPipelineInput type
78+
protected List<BsonDocument> Translate<TResult>(IMongoDatabase database, IQueryable<TResult> queryable)
79+
{
80+
return Translate<NoPipelineInput, TResult>(queryable);
81+
}
82+
83+
protected List<BsonDocument> Translate<TDocument, TResult>(IQueryable<TResult> queryable)
84+
{
85+
return Translate<TDocument, TResult>(queryable, out _);
86+
}
87+
88+
protected List<BsonDocument> Translate<TDocument, TResult>(IQueryable<TResult> queryable, out IBsonSerializer<TResult> outputSerializer)
89+
{
90+
var provider = (MongoQueryProvider<TDocument>)queryable.Provider;
91+
var translationOptions = provider.GetTranslationOptions();
92+
var executableQuery = ExpressionToExecutableQueryTranslator.Translate<TDocument, TResult>(provider, queryable.Expression, translationOptions);
93+
var stages = executableQuery.Pipeline.Ast.Stages;
94+
outputSerializer = (IBsonSerializer<TResult>)executableQuery.Pipeline.OutputSerializer;
95+
return stages.Select(s => s.Render().AsBsonDocument).ToList();
96+
}
97+
98+
protected static List<BsonDocument> Translate<TDocument, TResult>(
99+
PipelineDefinition<TDocument, TResult> pipelineDefinition,
100+
IBsonSerializer<TDocument> documentSerializer,
101+
ExpressionTranslationOptions translationOptions) =>
102+
Translate(pipelineDefinition, documentSerializer, translationOptions, out _);
103+
104+
protected static List<BsonDocument> Translate<TDocument, TResult>(
105+
PipelineDefinition<TDocument, TResult> pipelineDefinition,
106+
IBsonSerializer<TDocument> documentSerializer,
107+
ExpressionTranslationOptions translationOptions,
108+
out IBsonSerializer<TResult> outputSerializer)
109+
{
110+
var serializerRegistry = BsonSerializer.SerializerRegistry;
111+
documentSerializer ??= serializerRegistry.GetSerializer<TDocument>();
112+
var renderedPipeline = pipelineDefinition.Render(new(documentSerializer, serializerRegistry, translationOptions: translationOptions));
113+
outputSerializer = renderedPipeline.OutputSerializer;
114+
return renderedPipeline.Documents.ToList();
115+
}
116+
117+
protected BsonDocument Translate<TDocument>(IMongoCollection<TDocument> collection, FilterDefinition<TDocument> filterDefinition)
118+
{
119+
var documentSerializer = collection.DocumentSerializer;
120+
var serializerRegistry = BsonSerializer.SerializerRegistry;
121+
var translationOptions = collection.Database.Client.Settings.TranslationOptions;
122+
return filterDefinition.Render(new(documentSerializer, serializerRegistry, translationOptions: translationOptions));
123+
}
124+
125+
protected BsonDocument TranslateFilter<TDocument>(IMongoCollection<TDocument> collection, FilterDefinition<TDocument> filter)
126+
{
127+
var documentSerializer = collection.DocumentSerializer;
128+
var serializerRegistry = BsonSerializer.SerializerRegistry;
129+
var translationOptions = collection.Database.Client.Settings.TranslationOptions;
130+
return filter.Render(new(documentSerializer, serializerRegistry, translationOptions: translationOptions));
131+
}
132+
133+
protected BsonDocument TranslateFindFilter<TDocument, TProjection>(IMongoCollection<TDocument> collection, IFindFluent<TDocument, TProjection> find)
134+
{
135+
var filterDefinition = ((FindFluent<TDocument, TProjection>)find).Filter;
136+
var translationOptions = collection.Database.Client.Settings.TranslationOptions;
137+
return filterDefinition.Render(new(collection.DocumentSerializer, BsonSerializer.SerializerRegistry, translationOptions: translationOptions));
138+
}
139+
140+
protected BsonDocument TranslateFindProjection<TDocument, TProjection>(
141+
IMongoCollection<TDocument> collection,
142+
IFindFluent<TDocument, TProjection> find) =>
143+
TranslateFindProjection(collection, find, out _);
144+
145+
protected BsonDocument TranslateFindProjection<TDocument, TProjection>(
146+
IMongoCollection<TDocument> collection,
147+
IFindFluent<TDocument, TProjection> find,
148+
out IBsonSerializer<TProjection> projectionSerializer)
149+
{
150+
var projection = ((FindFluent<TDocument, TProjection>)find).Options.Projection;
151+
var translationOptions = find.Options?.TranslationOptions.AddMissingOptionsFrom(collection.Database.Client.Settings.TranslationOptions);
152+
return TranslateFindProjection(collection, projection, translationOptions, out projectionSerializer);
153+
}
154+
155+
protected BsonDocument TranslateFindProjection<TDocument, TProjection>(
156+
IMongoCollection<TDocument> collection,
157+
ProjectionDefinition<TDocument, TProjection> projection,
158+
ExpressionTranslationOptions translationOptions) =>
159+
TranslateFindProjection(collection, projection, translationOptions, out _);
160+
161+
protected BsonDocument TranslateFindProjection<TDocument, TProjection>(
162+
IMongoCollection<TDocument> collection,
163+
ProjectionDefinition<TDocument, TProjection> projection,
164+
ExpressionTranslationOptions translationOptions,
165+
out IBsonSerializer<TProjection> projectionSerializer)
166+
{
167+
var documentSerializer = collection.DocumentSerializer;
168+
var serializerRegistry = BsonSerializer.SerializerRegistry;
169+
var renderedProjection = projection.Render(new(documentSerializer, serializerRegistry, translationOptions: translationOptions, renderForFind: true));
170+
projectionSerializer = renderedProjection.ProjectionSerializer;
171+
return renderedProjection.Document;
172+
}
173+
}
174+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.Collections.Generic;
18+
using MongoDB.Driver.Tests;
19+
20+
namespace MongoDB.Driver.TestHelpers
21+
{
22+
public abstract class MongoCollectionFixture<TDocument> : MongoDatabaseFixture
23+
{
24+
private readonly Lazy<IMongoCollection<TDocument>> _collection;
25+
private bool _dataInitialized;
26+
27+
protected MongoCollectionFixture()
28+
{
29+
_collection = new Lazy<IMongoCollection<TDocument>>(CreateCollection);
30+
}
31+
32+
public IMongoCollection<TDocument> Collection => _collection.Value;
33+
34+
protected abstract IEnumerable<TDocument> InitialData { get; }
35+
36+
public virtual bool InitializeDataBeforeEachTestCase => false;
37+
38+
protected override void InitializeTestCase()
39+
{
40+
if (InitializeDataBeforeEachTestCase || !_dataInitialized)
41+
{
42+
Collection.Database.DropCollection(Collection.CollectionNamespace.CollectionName);
43+
44+
if (InitialData == null)
45+
{
46+
Collection.Database.CreateCollection(Collection.CollectionNamespace.CollectionName);
47+
}
48+
else
49+
{
50+
Collection.InsertMany(InitialData);
51+
}
52+
53+
_dataInitialized = true;
54+
}
55+
}
56+
57+
protected virtual string GetCollectionName()
58+
{
59+
var currentType = GetType();
60+
var result = currentType.DeclaringType?.Name;
61+
62+
if (string.IsNullOrEmpty(result))
63+
{
64+
throw new InvalidOperationException("Cannot resolve the collection name. Try to override GetCollectionName for custom collection name resolution.");
65+
}
66+
67+
return result;
68+
}
69+
70+
private IMongoCollection<TDocument> CreateCollection()
71+
{
72+
return CreateCollection<TDocument>(GetCollectionName());
73+
}
74+
}
75+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.Collections.Generic;
18+
19+
namespace MongoDB.Driver.Tests
20+
{
21+
public class MongoDatabaseFixture : IDisposable
22+
{
23+
private static readonly string __timeStamp = DateTime.Now.ToString("MMddHHmm");
24+
25+
private readonly Lazy<IMongoClient> _client;
26+
private readonly Lazy<IMongoDatabase> _database;
27+
private readonly string _databaseName = $"CSTests{__timeStamp}";
28+
private bool _fixtureInialized;
29+
private readonly HashSet<string> _usedCollections = new();
30+
31+
public MongoDatabaseFixture()
32+
{
33+
_client = new Lazy<IMongoClient>(CreateClient);
34+
_database = new Lazy<IMongoDatabase>(CreateDatabase);
35+
}
36+
37+
public IMongoClient Client => _client.Value;
38+
public IMongoDatabase Database => _database.Value;
39+
40+
public virtual void Dispose()
41+
{
42+
var database = _database.IsValueCreated ? _database.Value : null;
43+
if (database != null)
44+
{
45+
foreach (var collection in _usedCollections)
46+
{
47+
database.DropCollection(collection);
48+
}
49+
}
50+
}
51+
52+
protected IMongoCollection<TDocument> CreateCollection<TDocument>(string collectionName)
53+
{
54+
if (string.IsNullOrEmpty(collectionName))
55+
{
56+
throw new ArgumentException($"{nameof(collectionName)} should be non-empty string.", nameof(collectionName));
57+
}
58+
59+
Database.DropCollection(collectionName);
60+
_usedCollections.Add(collectionName);
61+
62+
return Database.GetCollection<TDocument>(collectionName);
63+
}
64+
65+
protected virtual IMongoClient CreateClient()
66+
=> DriverTestConfiguration.Client;
67+
68+
protected virtual IMongoDatabase CreateDatabase()
69+
{
70+
return Client.GetDatabase(_databaseName);
71+
}
72+
73+
internal void BeforeTestCase()
74+
{
75+
if (!_fixtureInialized)
76+
{
77+
InitializeFixture();
78+
_fixtureInialized = true;
79+
}
80+
81+
InitializeTestCase();
82+
}
83+
84+
protected virtual void InitializeFixture()
85+
{}
86+
87+
protected virtual void InitializeTestCase()
88+
{}
89+
}
90+
}

0 commit comments

Comments
 (0)