Skip to content

Commit

Permalink
Rework after 3.0 release
Browse files Browse the repository at this point in the history
  • Loading branch information
Oleksandr Poliakov committed Oct 29, 2024
1 parent 476b180 commit 7da9712
Show file tree
Hide file tree
Showing 15 changed files with 409 additions and 380 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,67 +15,56 @@

using System;
using System.Collections.Generic;
using System.Threading;
using MongoDB.Driver.Linq;
using MongoDB.Driver.Tests;

namespace MongoDB.Driver.TestHelpers
{
public abstract class TemporaryCollectionFixture<TDocument> : IDisposable
public abstract class CollectionFixture<TDocument> : DatabaseFixture
{
private readonly TemporaryDatabaseFixture _temporaryDatabaseFixture;
private readonly string _collectionName;
private readonly Lazy<bool> _collectionInitializer;
private bool _collectionInitialized;

protected TemporaryCollectionFixture(string collectionName = null)
protected CollectionFixture(string collectionName = null)
{
_temporaryDatabaseFixture = new TemporaryDatabaseFixture();
_collectionInitializer = new Lazy<bool>(() =>
{
InitializeCollection();
return true;
}, LazyThreadSafetyMode.ExecutionAndPublication);
_collectionName = collectionName ?? GetCollectionName();
if (string.IsNullOrEmpty(_collectionName))
{
throw new ArgumentNullException(nameof(collectionName) , "Cannot resolve the collection name. Try to specify the parameter explicitly");
}
}

public void Dispose()
{
_temporaryDatabaseFixture.Dispose();
}

public string CollectionName => _collectionName;

public IMongoClient GetClient(LinqProvider provider)
=> _temporaryDatabaseFixture.GetClient(provider);
public virtual bool ResetOnEachGet => false;

public IMongoCollection<TDocument> GetCollection(LinqProvider provider = LinqProvider.V3)
public override IMongoCollection<T> GetCollection<T>(Action<MongoClientSettings> configure = null, string collectionName = null)
{
EnsureCollectionInitialized();
return GetDatabase(provider).GetCollection<TDocument>(CollectionName);
}
if (!string.IsNullOrEmpty(collectionName))
{
throw new NotSupportedException("CollectionFixture does not support explicit collection name.");
}

public IMongoDatabase GetDatabase(LinqProvider provider = LinqProvider.V3)
=> _temporaryDatabaseFixture.GetDatabase(provider);
if (ResetOnEachGet || !_collectionInitialized)
{
InitializeCollection();
}

var db = GetDatabase(configure);
return db.GetCollection<T>(CollectionName);
}

protected abstract IEnumerable<TDocument> GetInitialData();

protected virtual void InitializeCollection()
private void InitializeCollection()
{
var initialData = GetInitialData();
if (initialData != null)
{
var collection = _temporaryDatabaseFixture.GetCollection<TDocument>(CollectionName);
var collection = base.GetCollection<TDocument>(null, CollectionName);
collection.InsertMany(initialData);
}
}

private void EnsureCollectionInitialized()
{
_ = _collectionInitializer.Value;
_collectionInitialized = true;
}

private string GetCollectionName()
Expand Down
63 changes: 63 additions & 0 deletions tests/MongoDB.Driver.TestHelpers/DatabaseFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/* 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 DatabaseFixture: IDisposable
{
private static readonly string __timeStamp = DateTime.Now.ToString("yyyyMMdd-HHmmss");

private readonly string _databaseName = $"CsharpDriver-{__timeStamp}";
private readonly HashSet<string> _usedCollections = new();

public virtual void Dispose()
{
var database = GetDatabase();
foreach (var collection in _usedCollections)
{
database.DropCollection(collection);
}
}

public virtual IMongoClient GetMongoClient(Action<MongoClientSettings> configure = null)
{
var clientSettings = DriverTestConfiguration.GetClientSettings();
configure?.Invoke(clientSettings);
return new MongoClient(clientSettings);
}

public virtual IMongoDatabase GetDatabase(Action<MongoClientSettings> configure = null)
=> GetMongoClient(configure).GetDatabase(_databaseName);

public virtual IMongoCollection<T> GetCollection<T>(Action<MongoClientSettings> configure = null, string collectionName = null)
{
if (string.IsNullOrEmpty(collectionName))
{
var stack = new System.Diagnostics.StackTrace();
var frame = stack.GetFrame(1); // skip 1 frame to get the calling method info
var method = frame.GetMethod();
collectionName = $"{method.DeclaringType.Name}.{method.Name}";
}

var db = GetDatabase(configure);
db.DropCollection(collectionName);
_usedCollections.Add(collectionName);
return db.GetCollection<T>(collectionName);
}
}
}
30 changes: 29 additions & 1 deletion tests/MongoDB.Driver.TestHelpers/IntegrationTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* limitations under the License.
*/

using System;
using MongoDB.Driver.Core.TestHelpers.Logging;
using MongoDB.TestHelpers.XunitExtensions;
using Xunit;
Expand All @@ -22,7 +23,7 @@ namespace MongoDB.Driver.Tests
{
[IntegrationTest]
public abstract class IntegrationTest<TFixture> : LoggableTestClass, IClassFixture<TFixture>
where TFixture : class
where TFixture : DatabaseFixture
{
protected IntegrationTest(ITestOutputHelper testOutputHelper, TFixture fixture)
: base(testOutputHelper)
Expand All @@ -31,5 +32,32 @@ protected IntegrationTest(ITestOutputHelper testOutputHelper, TFixture fixture)
}

protected TFixture Fixture { get; }

public IMongoClient GetMongoClient(Action<MongoClientSettings> configure = null)
{
return Fixture.GetMongoClient(settings =>
{
settings.LoggingSettings = LoggingSettings;
configure?.Invoke(settings);
});
}

public IMongoDatabase GetDatabase(Action<MongoClientSettings> configure = null)
{
return Fixture.GetDatabase(settings =>
{
settings.LoggingSettings = LoggingSettings;
configure?.Invoke(settings);
});
}

public IMongoCollection<T> GetCollection<T>(Action<MongoClientSettings> configure = null, string collectionName = null)
{
return Fixture.GetCollection<T>(settings =>
{
settings.LoggingSettings = LoggingSettings;
configure?.Invoke(settings);
}, collectionName);
}
}
}
106 changes: 49 additions & 57 deletions tests/MongoDB.Driver.TestHelpers/LinqIntegrationTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,14 @@
using FluentAssertions;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Driver.Linq;
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 : class
where TFixture : DatabaseFixture
{
public LinqIntegrationTest(ITestOutputHelper testOutputHelper, TFixture fixture)
: base(testOutputHelper, fixture)
Expand All @@ -43,19 +42,21 @@ protected void AssertStages(IEnumerable<BsonDocument> stages, IEnumerable<string
stages.Should().Equal(expectedStages.Select(json => BsonDocument.Parse(json)));
}

protected static List<BsonDocument> Translate<TDocument, TResult>(IMongoCollection<TDocument> collection, IAggregateFluent<TResult> aggregate)
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 linqProvider = collection.Database.Client.Settings.LinqProvider;
return Translate(pipelineDefinition, documentSerializer, linqProvider);
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)
{
var linqProvider = collection.Database.Client.Settings.LinqProvider;
return Translate<TDocument, TResult>(queryable, linqProvider);
return Translate<TDocument, TResult>(queryable);
}

// in this overload the collection argument is used only to infer the TDocument type
Expand All @@ -67,8 +68,8 @@ protected List<BsonDocument> Translate<TDocument, TResult>(IMongoCollection<TDoc
protected static List<BsonDocument> Translate<TResult>(IMongoDatabase database, IAggregateFluent<TResult> aggregate)
{
var pipelineDefinition = ((AggregateFluent<NoPipelineInput, TResult>)aggregate).Pipeline;
var linqProvider = database.Client.Settings.LinqProvider;
return Translate(pipelineDefinition, NoPipelineInputSerializer.Instance, linqProvider);
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
Expand All @@ -77,103 +78,94 @@ protected List<BsonDocument> Translate<TResult>(IMongoDatabase database, IQuerya
return Translate<NoPipelineInput, TResult>(queryable);
}

protected List<BsonDocument> Translate<TDocument, TResult>(IQueryable<TResult> queryable, LinqProvider linqProvider = LinqProvider.V3)
protected List<BsonDocument> Translate<TDocument, TResult>(IQueryable<TResult> queryable)
{
return Translate<TDocument, TResult>(queryable, linqProvider, out _);
return Translate<TDocument, TResult>(queryable, out _);
}

protected List<BsonDocument> Translate<TDocument, TResult>(IQueryable<TResult> queryable, out IBsonSerializer<TResult> outputSerializer)
{
return Translate<TDocument, TResult>(queryable, LinqProvider.V3, out 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.Stages;
outputSerializer = (IBsonSerializer<TResult>)executableQuery.Pipeline.OutputSerializer;
return stages.Select(s => s.Render().AsBsonDocument).ToList();
}

protected List<BsonDocument> Translate<TDocument, TResult>(IQueryable<TResult> queryable, LinqProvider linqProvider, out IBsonSerializer<TResult> outputSerializer)
{
if (linqProvider == LinqProvider.V2)
{
var linq2QueryProvider = (MongoDB.Driver.Linq.Linq2Implementation.MongoQueryProviderImpl<TDocument>)queryable.Provider;
var executionModel = linq2QueryProvider.GetExecutionModel(queryable.Expression);
var executionModelType = executionModel.GetType();
var stagesPropertyInfo = executionModelType.GetProperty("Stages");
var stages = (IEnumerable<BsonDocument>)stagesPropertyInfo.GetValue(executionModel);
var outputSerializerPropertyInfo = executionModelType.GetProperty("OutputSerializer");
outputSerializer = (IBsonSerializer<TResult>)outputSerializerPropertyInfo.GetValue(executionModel);
return stages.ToList();
}
else
{
var linq3QueryProvider = (MongoQueryProvider<TDocument>)queryable.Provider;
var executableQuery = ExpressionToExecutableQueryTranslator.Translate<TDocument, TResult>(linq3QueryProvider, queryable.Expression);
var stages = executableQuery.Pipeline.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 = null,
LinqProvider linqProvider = LinqProvider.V3)
IBsonSerializer<TDocument> documentSerializer,
ExpressionTranslationOptions translationOptions,
out IBsonSerializer<TResult> outputSerializer)
{
var serializerRegistry = BsonSerializer.SerializerRegistry;
documentSerializer ??= serializerRegistry.GetSerializer<TDocument>();
var renderedPipeline = pipelineDefinition.Render(documentSerializer, serializerRegistry, linqProvider);
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 linqProvider = collection.Database.Client.Settings.LinqProvider;
return filterDefinition.Render(documentSerializer, serializerRegistry, linqProvider);
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 linqProvider = collection.Database.Client.Settings.LinqProvider;
return filter.Render(documentSerializer, serializerRegistry, linqProvider);
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 linqProvider = collection.Database.Client.Settings.LinqProvider;
return TranslateFindFilter(collection, find, linqProvider);
}

protected BsonDocument TranslateFindFilter<TDocument, TProjection>(IMongoCollection<TDocument> collection, IFindFluent<TDocument, TProjection> find, LinqProvider linqProvider)
{
var filterDefinition = ((FindFluent<TDocument, TProjection>)find).Filter;
return filterDefinition.Render(collection.DocumentSerializer, BsonSerializer.SerializerRegistry, linqProvider);
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)
{
var linqProvider = collection.Database.Client.Settings.LinqProvider;
return TranslateFindProjection(collection, find, linqProvider);
}
IFindFluent<TDocument, TProjection> find) =>
TranslateFindProjection(collection, find, out _);

protected BsonDocument TranslateFindProjection<TDocument, TProjection>(
IMongoCollection<TDocument> collection,
IFindFluent<TDocument, TProjection> find,
LinqProvider linqProvider)
out IBsonSerializer<TProjection> projectionSerializer)
{
var projection = ((FindFluent<TDocument, TProjection>)find).Options.Projection;
return TranslateFindProjection(collection, projection, linqProvider);
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,
LinqProvider linqProvider)
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.RenderForFind(documentSerializer, serializerRegistry, linqProvider);
var renderedProjection = projection.Render(new(documentSerializer, serializerRegistry, translationOptions: translationOptions, renderForFind: true));
projectionSerializer = renderedProjection.ProjectionSerializer;
return renderedProjection.Document;
}
}
Expand Down
Loading

0 comments on commit 7da9712

Please sign in to comment.