-
-
Notifications
You must be signed in to change notification settings - Fork 107
refactor: Consolidate initialisation logic #4006
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
Merged
Merged
Changes from 14 commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
024a2ca
feat: implement IAsyncDiscoveryInitializer and related classes for im…
thomhurst 7cdc0fc
feat: implement IAsyncDiscoveryInitializer and related classes for im…
thomhurst bebb3e2
feat: implement IAsyncDiscoveryInitializer and related classes for im…
thomhurst 0041ee5
feat: implement IAsyncDiscoveryInitializer and related classes for im…
thomhurst 8e97f35
feat: implement IAsyncDiscoveryInitializer and related classes for im…
thomhurst 07b2215
feat: implement IAsyncDiscoveryInitializer and related classes for im…
thomhurst 8589baa
feat: implement IAsyncDiscoveryInitializer and related classes for im…
thomhurst 5c78262
feat: implement IAsyncDiscoveryInitializer and related classes for im…
thomhurst 579cd75
feat: implement IAsyncDiscoveryInitializer and related classes for im…
thomhurst 8c661b3
feat: implement IAsyncDiscoveryInitializer and related classes for im…
thomhurst 8030e42
feat: implement IAsyncDiscoveryInitializer and related classes for im…
thomhurst 1646680
feat: enhance type handling and object tracking with custom primitive…
thomhurst 851e21c
feat: improve disposal callback registration logic to handle untracke…
thomhurst f89adb8
feat: change visibility of object graph related classes and interface…
thomhurst a1938e3
feat: implement IAsyncDiscoveryInitializer and related classes for im…
thomhurst 9efbd55
feat: skip PlaceholderInstance during data source class resolution fo…
thomhurst da86e46
fix: normalize line endings in exception message assertions for consi…
thomhurst ca3065f
fix: normalize line endings in exception messages for consistency acr…
thomhurst 2799dd7
fix: remove premature cache removal for shared data sources
thomhurst ff2e57e
feat: enable parallel initialization of tracked objects during test e…
thomhurst 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 hidden or 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
This file contains hidden or 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,43 @@ | ||
| namespace TUnit.Assertions.Conditions.Helpers; | ||
|
|
||
| /// <summary> | ||
| /// Helper methods for parsing and extracting information from assertion expressions. | ||
| /// Consolidates expression parsing logic to ensure consistent behavior across assertion classes. | ||
| /// </summary> | ||
| internal static class ExpressionHelper | ||
| { | ||
| /// <summary> | ||
| /// Extracts the source variable name from an assertion expression string. | ||
| /// </summary> | ||
| /// <param name="expression">The expression string, e.g., "Assert.That(variableName).IsEquivalentTo(...)"</param> | ||
| /// <returns>The variable name, or "value" if it cannot be extracted or is a lambda expression.</returns> | ||
| /// <example> | ||
| /// Input: "Assert.That(myObject).IsEquivalentTo(expected)" | ||
| /// Output: "myObject" | ||
| /// | ||
| /// Input: "Assert.That(async () => GetValue()).IsEquivalentTo(expected)" | ||
| /// Output: "value" | ||
| /// </example> | ||
| public static string ExtractSourceVariable(string expression) | ||
| { | ||
| // Extract variable name from "Assert.That(variableName)" or similar | ||
| var thatIndex = expression.IndexOf(".That(", StringComparison.Ordinal); | ||
| if (thatIndex >= 0) | ||
| { | ||
| var startIndex = thatIndex + 6; // Length of ".That(" | ||
| var endIndex = expression.IndexOf(')', startIndex); | ||
| if (endIndex > startIndex) | ||
| { | ||
| var variable = expression.Substring(startIndex, endIndex - startIndex); | ||
| // Handle lambda expressions like "async () => ..." by returning "value" | ||
| if (variable.Contains("=>") || variable.StartsWith("()", StringComparison.Ordinal)) | ||
| { | ||
| return "value"; | ||
| } | ||
| return variable; | ||
| } | ||
| } | ||
|
|
||
| return "value"; | ||
| } | ||
| } | ||
This file contains hidden or 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,63 @@ | ||
| using System.Diagnostics.CodeAnalysis; | ||
| using System.Reflection; | ||
|
|
||
| namespace TUnit.Assertions.Conditions.Helpers; | ||
|
|
||
| /// <summary> | ||
| /// Helper methods for reflection-based member access. | ||
| /// Consolidates reflection logic to ensure consistent behavior and reduce code duplication. | ||
| /// </summary> | ||
| internal static class ReflectionHelper | ||
| { | ||
| /// <summary> | ||
| /// Gets all public instance properties and fields to compare for structural equivalency. | ||
| /// </summary> | ||
| /// <param name="type">The type to get members from.</param> | ||
| /// <returns>A list of PropertyInfo and FieldInfo members.</returns> | ||
| public static List<MemberInfo> GetMembersToCompare( | ||
| [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] | ||
| Type type) | ||
| { | ||
| var members = new List<MemberInfo>(); | ||
| members.AddRange(type.GetProperties(BindingFlags.Public | BindingFlags.Instance)); | ||
| members.AddRange(type.GetFields(BindingFlags.Public | BindingFlags.Instance)); | ||
| return members; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets the value of a member (property or field) from an object. | ||
| /// </summary> | ||
| /// <param name="obj">The object to get the value from.</param> | ||
| /// <param name="member">The member (PropertyInfo or FieldInfo) to read.</param> | ||
| /// <returns>The value of the member.</returns> | ||
| /// <exception cref="InvalidOperationException">Thrown if the member is not a PropertyInfo or FieldInfo.</exception> | ||
| public static object? GetMemberValue(object obj, MemberInfo member) | ||
| { | ||
| return member switch | ||
| { | ||
| PropertyInfo prop => prop.GetValue(obj), | ||
| FieldInfo field => field.GetValue(obj), | ||
| _ => throw new InvalidOperationException($"Unknown member type: {member.GetType()}") | ||
| }; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets a member (property or field) by name from a type. | ||
| /// </summary> | ||
| /// <param name="type">The type to search.</param> | ||
| /// <param name="name">The member name to find.</param> | ||
| /// <returns>The MemberInfo if found; null otherwise.</returns> | ||
| public static MemberInfo? GetMemberInfo( | ||
| [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] | ||
| Type type, | ||
| string name) | ||
| { | ||
| var property = type.GetProperty(name, BindingFlags.Public | BindingFlags.Instance); | ||
| if (property != null) | ||
| { | ||
| return property; | ||
| } | ||
|
|
||
| return type.GetField(name, BindingFlags.Public | BindingFlags.Instance); | ||
| } | ||
| } |
This file contains hidden or 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
This file contains hidden or 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,84 @@ | ||
| using System.Collections.Concurrent; | ||
|
|
||
| namespace TUnit.Assertions.Conditions.Helpers; | ||
|
|
||
| /// <summary> | ||
| /// Helper methods for type checking and classification. | ||
| /// Consolidates type checking logic to ensure consistent behavior across assertion classes. | ||
| /// </summary> | ||
| internal static class TypeHelper | ||
| { | ||
| /// <summary> | ||
| /// Thread-safe registry of user-defined types that should be treated as primitives | ||
| /// (using value equality rather than structural comparison). | ||
| /// </summary> | ||
| private static readonly ConcurrentDictionary<Type, byte> CustomPrimitiveTypes = new(); | ||
|
|
||
| /// <summary> | ||
| /// Registers a type to be treated as a primitive for structural equivalency comparisons. | ||
| /// Once registered, instances of this type will use value equality (via Equals) rather | ||
| /// than having their properties compared individually. | ||
| /// </summary> | ||
| /// <typeparam name="T">The type to register as a primitive.</typeparam> | ||
| public static void RegisterAsPrimitive<T>() | ||
| { | ||
| CustomPrimitiveTypes.TryAdd(typeof(T), 0); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Registers a type to be treated as a primitive for structural equivalency comparisons. | ||
| /// </summary> | ||
| /// <param name="type">The type to register as a primitive.</param> | ||
| public static void RegisterAsPrimitive(Type type) | ||
| { | ||
| CustomPrimitiveTypes.TryAdd(type, 0); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Removes a previously registered custom primitive type. | ||
| /// </summary> | ||
| /// <typeparam name="T">The type to unregister.</typeparam> | ||
| /// <returns>True if the type was removed; false if it wasn't registered.</returns> | ||
| public static bool UnregisterPrimitive<T>() | ||
| { | ||
| return CustomPrimitiveTypes.TryRemove(typeof(T), out _); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Clears all registered custom primitive types. | ||
| /// Useful for test cleanup between tests. | ||
| /// </summary> | ||
| public static void ClearCustomPrimitives() | ||
| { | ||
| CustomPrimitiveTypes.Clear(); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Determines if a type is a primitive or well-known immutable type that should use | ||
| /// value equality rather than structural comparison. | ||
| /// </summary> | ||
| /// <param name="type">The type to check.</param> | ||
| /// <returns>True if the type should use value equality; false for structural comparison.</returns> | ||
| public static bool IsPrimitiveOrWellKnownType(Type type) | ||
| { | ||
| // Check user-defined primitives first (fast path for common case) | ||
| if (CustomPrimitiveTypes.ContainsKey(type)) | ||
| { | ||
| return true; | ||
| } | ||
|
|
||
| return type.IsPrimitive | ||
| || type.IsEnum | ||
| || type == typeof(string) | ||
| || type == typeof(decimal) | ||
| || type == typeof(DateTime) | ||
| || type == typeof(DateTimeOffset) | ||
| || type == typeof(TimeSpan) | ||
| || type == typeof(Guid) | ||
| #if NET6_0_OR_GREATER | ||
| || type == typeof(DateOnly) | ||
| || type == typeof(TimeOnly) | ||
| #endif | ||
| ; | ||
| } | ||
| } |
This file contains hidden or 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.
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.
Using
StringComparison.Ordinalis good practice. However, on line 33, the secondContainscall doesn't specify a comparison type. For consistency and to avoid culture-specific issues, consider usingContains("=>", StringComparison.Ordinal)if available in the target framework, or usingIndexOf("=>", StringComparison.Ordinal) >= 0.