-
-
Notifications
You must be signed in to change notification settings - Fork 240
Fix: Add Fallback in ExpressionPromoter to Handle Cache Cleanup in ConstantExpressionHelper #905
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
StefH
merged 11 commits into
zzzprojects:master
from
RenanCarlosPereira:fix_race_condition
May 25, 2025
Merged
Changes from 10 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
e8dd351
include ParseRealLiteral tests
RenanCarlosPereira a27a91f
Change ParseRealLiteral to remove literals before parsing
RenanCarlosPereira 61c656d
Merge master
RenanCarlosPereira d28c748
NumberParserTests to split the tests NumberParser_Parse[Type]Literal
RenanCarlosPereira 2cf71df
Merge branch 'zzzprojects:master' into master
RenanCarlosPereira a2098ae
Merge branch 'zzzprojects:master' into master
RenanCarlosPereira b12b1ae
Fixing race condition
2b752bf
fix enum tests
b168256
Update to set instance via reflection
RenanCarlosPereira 2e9cb1a
Update ExpressionPromoterTests.cs
RenanCarlosPereira f9356e9
using IDynamicLinqCustomTypeProvider
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
148 changes: 112 additions & 36 deletions
148
test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionPromoterTests.cs
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 |
|---|---|---|
| @@ -1,56 +1,132 @@ | ||
| using Moq; | ||
| using FluentAssertions; | ||
| using Moq; | ||
| using System.Collections.Generic; | ||
| using System.Linq.Dynamic.Core.CustomTypeProviders; | ||
| using System.Linq.Dynamic.Core.Parser; | ||
| using System.Linq.Expressions; | ||
| using System.Reflection; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Xunit; | ||
|
|
||
| namespace System.Linq.Dynamic.Core.Tests.Parser; | ||
|
|
||
| public class ExpressionPromoterTests | ||
| namespace System.Linq.Dynamic.Core.Tests.Parser | ||
| { | ||
| public class SampleDto | ||
| public class ExpressionPromoterTests | ||
| { | ||
| public Guid Data { get; set; } | ||
| } | ||
| public class SampleDto | ||
| { | ||
| public Guid data { get; set; } | ||
| } | ||
|
|
||
| private readonly Mock<IExpressionPromoter> _expressionPromoterMock; | ||
| private readonly Mock<IDynamicLinqCustomTypeProvider> _dynamicLinkCustomTypeProviderMock; | ||
| private readonly Mock<IExpressionPromoter> _expressionPromoterMock; | ||
| private readonly Mock<IDynamicLinkCustomTypeProvider> _dynamicLinkCustomTypeProviderMock; | ||
|
||
|
|
||
| public ExpressionPromoterTests() | ||
| { | ||
| _dynamicLinkCustomTypeProviderMock = new Mock<IDynamicLinqCustomTypeProvider>(); | ||
| _dynamicLinkCustomTypeProviderMock.Setup(d => d.GetCustomTypes()).Returns(new HashSet<Type>()); | ||
| _dynamicLinkCustomTypeProviderMock.Setup(d => d.ResolveType(It.IsAny<string>())).Returns(typeof(SampleDto)); | ||
| public ExpressionPromoterTests() | ||
| { | ||
| _dynamicLinkCustomTypeProviderMock = new Mock<IDynamicLinkCustomTypeProvider>(); | ||
| _dynamicLinkCustomTypeProviderMock.Setup(d => d.GetCustomTypes()).Returns(new HashSet<Type>()); | ||
| _dynamicLinkCustomTypeProviderMock.Setup(d => d.ResolveType(It.IsAny<string>())).Returns(typeof(SampleDto)); | ||
|
|
||
| _expressionPromoterMock = new Mock<IExpressionPromoter>(); | ||
| _expressionPromoterMock.Setup(e => e.Promote(It.IsAny<Expression>(), It.IsAny<Type>(), It.IsAny<bool>(), It.IsAny<bool>())).Returns(Expression.Constant(Guid.NewGuid())); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void DynamicExpressionParser_ParseLambda_WithCustomExpressionPromoter() | ||
| { | ||
| // Assign | ||
| var parsingConfig = new ParsingConfig() | ||
| { | ||
| AllowNewToEvaluateAnyType = true, | ||
| CustomTypeProvider = _dynamicLinkCustomTypeProviderMock.Object, | ||
| ExpressionPromoter = _expressionPromoterMock.Object | ||
| }; | ||
|
|
||
| // Act | ||
| string query = $"new {typeof(SampleDto).FullName}(@0 as data)"; | ||
| LambdaExpression expression = DynamicExpressionParser.ParseLambda(parsingConfig, null, query, new object[] { Guid.NewGuid().ToString() }); | ||
| Delegate del = expression.Compile(); | ||
| SampleDto result = (SampleDto)del.DynamicInvoke(); | ||
|
|
||
| // Assert | ||
| Assert.NotNull(result); | ||
|
|
||
| // Verify | ||
| _dynamicLinkCustomTypeProviderMock.Verify(d => d.GetCustomTypes(), Times.Once); | ||
| _dynamicLinkCustomTypeProviderMock.Verify(d => d.ResolveType($"{typeof(SampleDto).FullName}"), Times.Once); | ||
|
|
||
| _expressionPromoterMock.Verify(e => e.Promote(It.IsAny<ConstantExpression>(), typeof(Guid), true, true), Times.Once); | ||
| } | ||
|
|
||
| _expressionPromoterMock = new Mock<IExpressionPromoter>(); | ||
| _expressionPromoterMock.Setup(e => e.Promote(It.IsAny<Expression>(), It.IsAny<Type>(), It.IsAny<bool>(), It.IsAny<bool>())).Returns(Expression.Constant(Guid.NewGuid())); | ||
| [Fact] | ||
| public async Task Promote_Should_Succeed_Even_When_LiteralsCache_Is_Cleaned() | ||
| { | ||
| // Arrange | ||
| var parsingConfig = new ParsingConfig() | ||
| { | ||
| ConstantExpressionCacheConfig = new Core.Util.Cache.CacheConfig | ||
| { | ||
| CleanupFrequency = TimeSpan.FromMilliseconds(500), // Run cleanup more often | ||
| TimeToLive = TimeSpan.FromMilliseconds(500), // Shorten TTL to force expiration | ||
| ReturnExpiredItems = false | ||
| } | ||
| }; | ||
|
|
||
| // because the field is static only one process is setting the field, | ||
| // we need a way to set up because the instance is private we are not able to overwrite the configuration. | ||
| ConstantExpressionHelperReflection.Initiate(parsingConfig); | ||
|
|
||
| var constantExpressionHelper = ConstantExpressionHelperFactory.GetInstance(parsingConfig); | ||
| var expressionPromoter = new ExpressionPromoter(parsingConfig); | ||
|
|
||
| double value = 0.40; | ||
| string text = "0.40"; | ||
| Type targetType = typeof(decimal); | ||
|
|
||
| // Step 1: Add constant to cache | ||
| var literalExpression = constantExpressionHelper.CreateLiteral(value, text); | ||
| Assert.NotNull(literalExpression); // Ensure it was added | ||
|
|
||
| // Step 2: Manually trigger cleanup | ||
| var cts = new CancellationTokenSource(500); | ||
| await Task.Run(async () => | ||
| { | ||
| while (!cts.IsCancellationRequested) | ||
| { | ||
| constantExpressionHelper.TryGetText(literalExpression, out _); | ||
| await Task.Delay(50); // Give some time for cleanup to be triggered | ||
| } | ||
| }); | ||
|
|
||
| // Ensure some cleanup cycles have passed | ||
| await Task.Delay(500); // Allow cache cleanup to happen | ||
|
|
||
| // Step 3: Attempt to promote the expression after cleanup | ||
| var promotedExpression = expressionPromoter.Promote(literalExpression, targetType, exact: false, true); | ||
|
|
||
| // Assert: Promotion should still work even if the cache was cleaned | ||
| promotedExpression.Should().NotBeNull(); // Ensure `Promote()` still returns a valid expression | ||
| } | ||
| } | ||
|
|
||
| [Fact] | ||
| public void DynamicExpressionParser_ParseLambda_WithCustomExpressionPromoter() | ||
| public static class ConstantExpressionHelperReflection | ||
| { | ||
| // Assign | ||
| var parsingConfig = new ParsingConfig() | ||
| private static readonly Type _constantExpressionHelperFactoryType; | ||
|
|
||
| static ConstantExpressionHelperReflection() | ||
| { | ||
| AllowNewToEvaluateAnyType = true, | ||
| CustomTypeProvider = _dynamicLinkCustomTypeProviderMock.Object, | ||
| ExpressionPromoter = _expressionPromoterMock.Object | ||
| }; | ||
| var assembly = Assembly.GetAssembly(typeof(DynamicClass))!; | ||
|
|
||
| // Act | ||
| string query = $"new {typeof(SampleDto).FullName}(@0 as Data)"; | ||
| LambdaExpression expression = DynamicExpressionParser.ParseLambda(parsingConfig, null, query, Guid.NewGuid().ToString()); | ||
| Delegate del = expression.Compile(); | ||
| SampleDto result = (SampleDto)del.DynamicInvoke(); | ||
| _constantExpressionHelperFactoryType = assembly.GetType("System.Linq.Dynamic.Core.Parser.ConstantExpressionHelperFactory")!; | ||
| } | ||
|
|
||
| // Assert | ||
| Assert.NotNull(result); | ||
| public static void Initiate(ParsingConfig parsingConfig) | ||
| { | ||
| var instance = new ConstantExpressionHelper(parsingConfig); | ||
|
|
||
| // Verify | ||
| _dynamicLinkCustomTypeProviderMock.Verify(d => d.GetCustomTypes(), Times.Once); | ||
| _dynamicLinkCustomTypeProviderMock.Verify(d => d.ResolveType($"{typeof(SampleDto).FullName}"), Times.Once); | ||
| var field = _constantExpressionHelperFactoryType.GetField("_instance", BindingFlags.NonPublic | BindingFlags.Static); | ||
|
|
||
| _expressionPromoterMock.Verify(e => e.Promote(It.IsAny<ConstantExpression>(), typeof(Guid), true, true), Times.Once); | ||
| field?.SetValue(field, instance); | ||
| } | ||
| } | ||
| } | ||
| } | ||
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.
Uh oh!
There was an error while loading. Please reload this page.