-
Notifications
You must be signed in to change notification settings - Fork 338
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
Fill Readonly Collection Properties Behavior #1177
Merged
aivascu
merged 15 commits into
AutoFixture:master
from
charles-salmon:fill-readonly-collection-properties-behavior
Apr 14, 2021
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
b441ab3
#341 Add behavior which fills readonly collection properties, such th…
charles-salmon 348eef1
#341 Remove superfluous whitespace.
charles-salmon f46c4e2
#341 Mark `private` members of `FillReadonlyCollectionPropertiesComma…
charles-salmon d88ace8
#341 Specify culture in call to `Convert.ChangeType`.
charles-salmon bbc7d32
#341 Remove superfluous whitespace.
charles-salmon ed08948
Merge branch 'master' of https://github.com/AutoFixture/AutoFixture i…
charles-salmon 3076365
Rename `FillReadonlyCollectionPropertiesBehavior` -> `ReadonlyCollect…
charles-salmon 485e9ea
Shift `CreateMany` method into `SpecimenFactory`.
charles-salmon 4bea6ef
Introduce `EnumerableEnvy` class, exposing an extension method which …
charles-salmon 7d63b66
Expose member in `TypeEnvy` class which gets a default value, given a…
charles-salmon 30f566a
Introduce `InstanceMethodQuery` to query for the `Add` method on coll…
charles-salmon 8eee7c1
Resolve build errors due to whitespace issues, and documentation errors.
charles-salmon 6e18edc
Rename `CollectionPropertyQuery` to `GenericCollectionPropertyQuery`.
charles-salmon 78f036e
Test property queries by way of unit testing, not by way of integrati…
charles-salmon f9e05e6
Disable the `ReadonlyCollectionPropertiesBehavior` by default.
charles-salmon File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Reflection; | ||
|
||
namespace AutoFixture.Kernel | ||
{ | ||
/// <summary> | ||
/// A query which is used to compose multiple <see cref="IPropertyQuery"/>s, returning only those properties | ||
/// that are returned by all queries. | ||
/// </summary> | ||
public class AndPropertyQuery : IPropertyQuery | ||
{ | ||
/// <summary> | ||
/// Constructs an instance of <see cref="AndPropertyQuery"/>, that will select properties from specified types | ||
/// that are returned by <paramref name="queries"/>. | ||
/// </summary> | ||
/// <param name="queries">The queries that should be used to select properties.</param> | ||
public AndPropertyQuery(params IPropertyQuery[] queries) | ||
{ | ||
this.Queries = queries; | ||
} | ||
|
||
/// <summary> | ||
/// Gets the queries that are used to select properties from specified types. | ||
/// </summary> | ||
public IEnumerable<IPropertyQuery> Queries { get; } | ||
|
||
/// <summary> | ||
/// Selects properties from <paramref name="type"/> that are returned by <see cref="Queries"/>. | ||
/// </summary> | ||
/// <param name="type">The type which properties should be selected from.</param> | ||
/// <returns>Properties belonging to <paramref name="type"/> that meet <see cref="Queries"/>.</returns> | ||
public IEnumerable<PropertyInfo> SelectProperties(Type type) | ||
{ | ||
var properties = new HashSet<PropertyInfo>(this.Queries.First().SelectProperties(type)); | ||
|
||
foreach (var query in this.Queries.Skip(1)) | ||
{ | ||
properties.IntersectWith(query.SelectProperties(type)); | ||
} | ||
|
||
return properties; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Globalization; | ||
using System.Linq; | ||
|
||
namespace AutoFixture.Kernel | ||
{ | ||
internal static class EnumerableEnvy | ||
{ | ||
public static IEnumerable<object> ConvertObjectType(this IEnumerable<object> enumerable, Type type) | ||
{ | ||
return enumerable.Select(v => Convert.ChangeType(v, type, CultureInfo.CurrentCulture)); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Reflection; | ||
|
||
namespace AutoFixture.Kernel | ||
{ | ||
/// <summary> | ||
/// A query which returns generic collection properties from specified types. | ||
/// </summary> | ||
public class GenericCollectionPropertyQuery : IPropertyQuery | ||
{ | ||
/// <summary> | ||
/// Select those properties that are generic collections from <paramref name="type"/>. | ||
/// </summary> | ||
/// <param name="type">The type which generic collection properties should be selected from.</param> | ||
/// <returns>Properties belonging to <paramref name="type"/> that are collections.</returns> | ||
public IEnumerable<PropertyInfo> SelectProperties(Type type) | ||
{ | ||
return type.GetTypeInfo().GetProperties() | ||
.Where(p => | ||
p.PropertyType.Name == typeof(ICollection<>).Name || | ||
p.PropertyType.GetTypeInfo().GetInterface(typeof(ICollection<>).Name) != null); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Reflection; | ||
|
||
namespace AutoFixture.Kernel | ||
{ | ||
/// <summary> | ||
/// Defines a strategy for selecting properties from a type. | ||
/// </summary> | ||
public interface IPropertyQuery | ||
{ | ||
/// <summary> | ||
/// Selects the properties for the specified <paramref name="type"/>. | ||
/// </summary> | ||
/// <param name="type">The type.</param> | ||
/// <returns>Property information for properties belonging to <paramref name="type"/>.</returns> | ||
IEnumerable<PropertyInfo> SelectProperties(Type type); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Reflection; | ||
|
||
namespace AutoFixture.Kernel | ||
{ | ||
/// <summary> | ||
/// Selects a method from an instance. | ||
/// </summary> | ||
public class InstanceMethodQuery : IMethodQuery | ||
{ | ||
/// <summary> | ||
/// Constructs an instance of an <see cref="InstanceMethodQuery"/>, used to select a particular method | ||
/// from an instance. | ||
/// </summary> | ||
/// <param name="owner">The instance that should be selected from.</param> | ||
/// <param name="methodName">The name of the method that should be selected.</param> | ||
public InstanceMethodQuery(object owner, string methodName) | ||
{ | ||
this.Owner = owner ?? throw new ArgumentNullException(nameof(owner)); | ||
this.MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName)); | ||
} | ||
|
||
/// <summary> | ||
/// Gets the instance that should be selected from. | ||
/// </summary> | ||
public object Owner { get; } | ||
|
||
/// <summary> | ||
/// Gets the name of the method that should be selected. | ||
/// </summary> | ||
public string MethodName { get; } | ||
|
||
/// <summary> | ||
/// Selects <see cref="MethodName"/> from <see cref="Owner"/>. | ||
/// </summary> | ||
/// <param name="type">Discarded.</param> | ||
/// <returns>Returns an empty enumerable if <see cref="MethodName"/> does not belong to <see cref="Owner"/>; | ||
/// returns an enumerable containing a single <see cref="InstanceMethod"/> otherwise.</returns> | ||
public IEnumerable<IMethod> SelectMethods(Type type = default) | ||
{ | ||
var method = this.Owner.GetType().GetTypeInfo().GetMethod(this.MethodName); | ||
|
||
return method == null | ||
? new IMethod[0] | ||
: new IMethod[] { new InstanceMethod(method, this.Owner) }; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
using System; | ||
|
||
namespace AutoFixture.Kernel | ||
{ | ||
/// <summary> | ||
/// A specification which omits the <see cref="Fixture"/> and <see cref="IFixture"/> types. | ||
/// </summary> | ||
public class OmitFixtureSpecification : IRequestSpecification | ||
{ | ||
/// <summary> | ||
/// Evaluates whether or not the <paramref name="request"/> is for a fixture type. | ||
/// </summary> | ||
/// <param name="request">The specimen request.</param> | ||
/// <returns> | ||
/// <see langword="false"/> if the <paramref name="request"/> is for a fixture type; | ||
/// <see langword="false"/> otherwise. | ||
/// </returns> | ||
public bool IsSatisfiedBy(object request) | ||
{ | ||
return !(request is Type requestType && (requestType == typeof(Fixture) || requestType == typeof(IFixture))); | ||
} | ||
} | ||
} |
83 changes: 83 additions & 0 deletions
83
Src/AutoFixture/Kernel/ReadonlyCollectionPropertiesCommand.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Reflection; | ||
|
||
namespace AutoFixture.Kernel | ||
{ | ||
/// <summary> | ||
/// A command which invokes <see cref="ICollection{T}.Add"/> to fill all readonly properties in a specimen that | ||
/// implement <see cref="ICollection{T}"/>. | ||
/// </summary> | ||
public class ReadonlyCollectionPropertiesCommand : ISpecimenCommand | ||
{ | ||
/// <summary> | ||
/// Constructs an instance of <see cref="ReadonlyCollectionPropertiesCommand"/>, used to fill all readonly | ||
/// properties in a specimen that implement <see cref="ICollection{T}"/>. | ||
/// </summary> | ||
public ReadonlyCollectionPropertiesCommand() | ||
: this(ReadonlyCollectionPropertiesSpecification.DefaultPropertyQuery) | ||
{ | ||
} | ||
|
||
/// <summary> | ||
/// Constructs an instance of <see cref="ReadonlyCollectionPropertiesCommand"/>, used to fill all readonly | ||
/// properties in a specimen that implement <see cref="ICollection{T}"/>. | ||
/// </summary> | ||
/// <param name="propertyQuery">The query that will be applied to select readonly collection properties.</param> | ||
public ReadonlyCollectionPropertiesCommand(IPropertyQuery propertyQuery) | ||
{ | ||
this.PropertyQuery = propertyQuery; | ||
} | ||
|
||
/// <summary> | ||
/// Gets the query used to determine whether or not a specified type has readonly collection properties. | ||
/// </summary> | ||
public IPropertyQuery PropertyQuery { get; } | ||
|
||
/// <summary> | ||
/// Invokes <see cref="ICollection{T}.Add"/> to fill all readonly properties in a specimen that implement | ||
/// <see cref="ICollection{T}"/>. | ||
/// </summary> | ||
/// <param name="specimen"> | ||
/// The specimen on which readonly collection properties should be filled. | ||
/// </param> | ||
/// <param name="context"> | ||
/// An <see cref="ISpecimenContext"/> that is used to create the elements used to fill collections. | ||
/// </param> | ||
/// <exception cref="ArgumentNullException"> | ||
/// Thrown if <paramref name="specimen"/> or <paramref name="context"/> is <see langword="null"/>. | ||
/// </exception> | ||
public void Execute(object specimen, ISpecimenContext context) | ||
{ | ||
if (specimen == null) throw new ArgumentNullException(nameof(specimen)); | ||
if (context == null) throw new ArgumentNullException(nameof(context)); | ||
|
||
var specimenType = specimen.GetType(); | ||
foreach (var pi in this.PropertyQuery.SelectProperties(specimenType)) | ||
{ | ||
var addMethod = new InstanceMethodQuery(pi.GetValue(specimen), nameof(ICollection<object>.Add)) | ||
.SelectMethods() | ||
.SingleOrDefault(); | ||
if (addMethod == null) continue; | ||
|
||
var valuesToAdd = SpecimenFactory.CreateMany( | ||
context, | ||
addMethod.Parameters.Single().ParameterType); | ||
|
||
foreach (var valueToAdd in valuesToAdd) | ||
{ | ||
try | ||
{ | ||
addMethod.Invoke(new[] { valueToAdd }); | ||
} | ||
catch (TargetInvocationException e) | ||
{ | ||
if (e.InnerException?.GetType() == typeof(NotSupportedException)) break; | ||
throw; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
61 changes: 61 additions & 0 deletions
61
Src/AutoFixture/Kernel/ReadonlyCollectionPropertiesSpecification.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
|
||
namespace AutoFixture.Kernel | ||
{ | ||
/// <summary> | ||
/// A specification that evaluates whether or not a request is for a type containing readonly properties that | ||
/// implement <see cref="ICollection{T}"/>. | ||
/// </summary> | ||
public class ReadonlyCollectionPropertiesSpecification : IRequestSpecification | ||
{ | ||
/// <summary> | ||
/// The default query that will be applied to select readonly collection properties. | ||
/// </summary> | ||
public static readonly IPropertyQuery DefaultPropertyQuery = new AndPropertyQuery( | ||
new ReadonlyPropertyQuery(), | ||
new GenericCollectionPropertyQuery()); | ||
|
||
/// <summary> | ||
/// Constructs an instance of <see cref="ReadonlyCollectionPropertiesSpecification"/> with a default | ||
/// query applied for selection of readonly collection properties. | ||
/// </summary> | ||
public ReadonlyCollectionPropertiesSpecification() | ||
: this(DefaultPropertyQuery) | ||
{ | ||
} | ||
|
||
/// <summary> | ||
/// Constructs an instance of <see cref="ReadonlyCollectionPropertiesSpecification"/>, which will use the query | ||
/// supplied in <paramref name="propertyQuery"/> to determine whether or not a type contains readonly collection | ||
/// properties. | ||
/// </summary> | ||
/// <param name="propertyQuery">The query that will be applied to select readonly collection properties.</param> | ||
public ReadonlyCollectionPropertiesSpecification(IPropertyQuery propertyQuery) | ||
{ | ||
this.PropertyQuery = propertyQuery; | ||
} | ||
|
||
/// <summary> | ||
/// Gets the query used to determine whether or not a specified type has readonly collection properties. | ||
/// </summary> | ||
public IPropertyQuery PropertyQuery { get; } | ||
|
||
/// <summary> | ||
/// Evaluates whether or not the <paramref name="request"/> is for a type containing readonly properties that | ||
/// implement <see cref="ICollection{T}"/>. | ||
/// </summary> | ||
/// <param name="request"> | ||
/// The specimen request. | ||
/// </param> | ||
/// <returns> | ||
/// <see langword="true"/> if the <paramref name="request"/> is for a type containing readonly properties that | ||
/// implement <see cref="ICollection{T}"/>; <see langword="false"/> otherwise. | ||
/// </returns> | ||
public bool IsSatisfiedBy(object request) | ||
{ | ||
return request is Type requestType && this.PropertyQuery.SelectProperties(requestType).Any(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Reflection; | ||
|
||
namespace AutoFixture.Kernel | ||
{ | ||
/// <summary> | ||
/// A query which returns readonly properties from specified types. | ||
/// </summary> | ||
public class ReadonlyPropertyQuery : IPropertyQuery | ||
{ | ||
/// <summary> | ||
/// Select those properties that are readonly from <paramref name="type"/>. | ||
/// </summary> | ||
/// <param name="type">The type which readonly properties should be selected from.</param> | ||
/// <returns>Properties belonging to <paramref name="type"/> that are readonly.</returns> | ||
public IEnumerable<PropertyInfo> SelectProperties(Type type) | ||
{ | ||
return type.GetTypeInfo().GetProperties().Where(p => p.GetSetMethod() == null); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
I am getting AmbiguosMatchFound Exception here, the Collection that I am using is RepeatedField from protobuf, Not sure if this can happen anywhere else, but maybe worth checking in.
I have solved it like:
Not sure if ideal, but desperate time - require desperate measures :)