Skip to content

Commit 81cdc84

Browse files
RenanCarlosPereiraRenan Pereira
andauthored
Fix: Add Fallback in ExpressionPromoter to Handle Cache Cleanup in ConstantExpressionHelper (#905)
* include ParseRealLiteral tests * Change ParseRealLiteral to remove literals before parsing * Merge master * NumberParserTests to split the tests NumberParser_Parse[Type]Literal * Fixing race condition * fix enum tests * Update to set instance via reflection * Update ExpressionPromoterTests.cs * using IDynamicLinqCustomTypeProvider --------- Co-authored-by: Renan Pereira <rpereira@glasslewis.com>
1 parent 4cc5d17 commit 81cdc84

File tree

2 files changed

+119
-38
lines changed

2 files changed

+119
-38
lines changed

src/System.Linq.Dynamic.Core/Parser/ExpressionPromoter.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,12 @@ public ExpressionPromoter(ParsingConfig config)
5050
}
5151
else
5252
{
53-
if (_constantExpressionHelper.TryGetText(ce, out var text))
53+
if (!_constantExpressionHelper.TryGetText(ce, out var text))
54+
{
55+
text = ce.Value?.ToString();
56+
}
57+
58+
if (text != null)
5459
{
5560
Type target = TypeHelper.GetNonNullableType(type);
5661
object? value = null;
@@ -67,7 +72,7 @@ public ExpressionPromoter(ParsingConfig config)
6772
// Make sure an enum value stays an enum value
6873
if (target.IsEnum)
6974
{
70-
value = Enum.ToObject(target, value!);
75+
TypeHelper.TryParseEnum(text, target, out value);
7176
}
7277
break;
7378

Lines changed: 112 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,132 @@
1-
using Moq;
1+
using FluentAssertions;
2+
using Moq;
23
using System.Collections.Generic;
34
using System.Linq.Dynamic.Core.CustomTypeProviders;
45
using System.Linq.Dynamic.Core.Parser;
56
using System.Linq.Expressions;
7+
using System.Reflection;
8+
using System.Threading;
9+
using System.Threading.Tasks;
610
using Xunit;
711

8-
namespace System.Linq.Dynamic.Core.Tests.Parser;
9-
10-
public class ExpressionPromoterTests
12+
namespace System.Linq.Dynamic.Core.Tests.Parser
1113
{
12-
public class SampleDto
14+
public class ExpressionPromoterTests
1315
{
14-
public Guid Data { get; set; }
15-
}
16+
public class SampleDto
17+
{
18+
public Guid data { get; set; }
19+
}
1620

17-
private readonly Mock<IExpressionPromoter> _expressionPromoterMock;
18-
private readonly Mock<IDynamicLinqCustomTypeProvider> _dynamicLinkCustomTypeProviderMock;
21+
private readonly Mock<IExpressionPromoter> _expressionPromoterMock;
22+
private readonly Mock<IDynamicLinqCustomTypeProvider> _dynamicLinqCustomTypeProviderMock;
1923

20-
public ExpressionPromoterTests()
21-
{
22-
_dynamicLinkCustomTypeProviderMock = new Mock<IDynamicLinqCustomTypeProvider>();
23-
_dynamicLinkCustomTypeProviderMock.Setup(d => d.GetCustomTypes()).Returns(new HashSet<Type>());
24-
_dynamicLinkCustomTypeProviderMock.Setup(d => d.ResolveType(It.IsAny<string>())).Returns(typeof(SampleDto));
24+
public ExpressionPromoterTests()
25+
{
26+
_dynamicLinqCustomTypeProviderMock = new Mock<IDynamicLinqCustomTypeProvider>();
27+
_dynamicLinqCustomTypeProviderMock.Setup(d => d.GetCustomTypes()).Returns(new HashSet<Type>());
28+
_dynamicLinqCustomTypeProviderMock.Setup(d => d.ResolveType(It.IsAny<string>())).Returns(typeof(SampleDto));
29+
30+
_expressionPromoterMock = new Mock<IExpressionPromoter>();
31+
_expressionPromoterMock.Setup(e => e.Promote(It.IsAny<Expression>(), It.IsAny<Type>(), It.IsAny<bool>(), It.IsAny<bool>())).Returns(Expression.Constant(Guid.NewGuid()));
32+
}
33+
34+
[Fact]
35+
public void DynamicExpressionParser_ParseLambda_WithCustomExpressionPromoter()
36+
{
37+
// Assign
38+
var parsingConfig = new ParsingConfig()
39+
{
40+
AllowNewToEvaluateAnyType = true,
41+
CustomTypeProvider = _dynamicLinqCustomTypeProviderMock.Object,
42+
ExpressionPromoter = _expressionPromoterMock.Object
43+
};
44+
45+
// Act
46+
string query = $"new {typeof(SampleDto).FullName}(@0 as data)";
47+
LambdaExpression expression = DynamicExpressionParser.ParseLambda(parsingConfig, null, query, new object[] { Guid.NewGuid().ToString() });
48+
Delegate del = expression.Compile();
49+
SampleDto result = (SampleDto)del.DynamicInvoke();
50+
51+
// Assert
52+
Assert.NotNull(result);
53+
54+
// Verify
55+
_dynamicLinqCustomTypeProviderMock.Verify(d => d.GetCustomTypes(), Times.Once);
56+
_dynamicLinqCustomTypeProviderMock.Verify(d => d.ResolveType($"{typeof(SampleDto).FullName}"), Times.Once);
57+
58+
_expressionPromoterMock.Verify(e => e.Promote(It.IsAny<ConstantExpression>(), typeof(Guid), true, true), Times.Once);
59+
}
2560

26-
_expressionPromoterMock = new Mock<IExpressionPromoter>();
27-
_expressionPromoterMock.Setup(e => e.Promote(It.IsAny<Expression>(), It.IsAny<Type>(), It.IsAny<bool>(), It.IsAny<bool>())).Returns(Expression.Constant(Guid.NewGuid()));
61+
[Fact]
62+
public async Task Promote_Should_Succeed_Even_When_LiteralsCache_Is_Cleaned()
63+
{
64+
// Arrange
65+
var parsingConfig = new ParsingConfig()
66+
{
67+
ConstantExpressionCacheConfig = new Core.Util.Cache.CacheConfig
68+
{
69+
CleanupFrequency = TimeSpan.FromMilliseconds(500), // Run cleanup more often
70+
TimeToLive = TimeSpan.FromMilliseconds(500), // Shorten TTL to force expiration
71+
ReturnExpiredItems = false
72+
}
73+
};
74+
75+
// because the field is static only one process is setting the field,
76+
// we need a way to set up because the instance is private we are not able to overwrite the configuration.
77+
ConstantExpressionHelperReflection.Initiate(parsingConfig);
78+
79+
var constantExpressionHelper = ConstantExpressionHelperFactory.GetInstance(parsingConfig);
80+
var expressionPromoter = new ExpressionPromoter(parsingConfig);
81+
82+
double value = 0.40;
83+
string text = "0.40";
84+
Type targetType = typeof(decimal);
85+
86+
// Step 1: Add constant to cache
87+
var literalExpression = constantExpressionHelper.CreateLiteral(value, text);
88+
Assert.NotNull(literalExpression); // Ensure it was added
89+
90+
// Step 2: Manually trigger cleanup
91+
var cts = new CancellationTokenSource(500);
92+
await Task.Run(async () =>
93+
{
94+
while (!cts.IsCancellationRequested)
95+
{
96+
constantExpressionHelper.TryGetText(literalExpression, out _);
97+
await Task.Delay(50); // Give some time for cleanup to be triggered
98+
}
99+
});
100+
101+
// Ensure some cleanup cycles have passed
102+
await Task.Delay(500); // Allow cache cleanup to happen
103+
104+
// Step 3: Attempt to promote the expression after cleanup
105+
var promotedExpression = expressionPromoter.Promote(literalExpression, targetType, exact: false, true);
106+
107+
// Assert: Promotion should still work even if the cache was cleaned
108+
promotedExpression.Should().NotBeNull(); // Ensure `Promote()` still returns a valid expression
109+
}
28110
}
29111

30-
[Fact]
31-
public void DynamicExpressionParser_ParseLambda_WithCustomExpressionPromoter()
112+
public static class ConstantExpressionHelperReflection
32113
{
33-
// Assign
34-
var parsingConfig = new ParsingConfig()
114+
private static readonly Type _constantExpressionHelperFactoryType;
115+
116+
static ConstantExpressionHelperReflection()
35117
{
36-
AllowNewToEvaluateAnyType = true,
37-
CustomTypeProvider = _dynamicLinkCustomTypeProviderMock.Object,
38-
ExpressionPromoter = _expressionPromoterMock.Object
39-
};
118+
var assembly = Assembly.GetAssembly(typeof(DynamicClass))!;
40119

41-
// Act
42-
string query = $"new {typeof(SampleDto).FullName}(@0 as Data)";
43-
LambdaExpression expression = DynamicExpressionParser.ParseLambda(parsingConfig, null, query, Guid.NewGuid().ToString());
44-
Delegate del = expression.Compile();
45-
SampleDto result = (SampleDto)del.DynamicInvoke();
120+
_constantExpressionHelperFactoryType = assembly.GetType("System.Linq.Dynamic.Core.Parser.ConstantExpressionHelperFactory")!;
121+
}
46122

47-
// Assert
48-
Assert.NotNull(result);
123+
public static void Initiate(ParsingConfig parsingConfig)
124+
{
125+
var instance = new ConstantExpressionHelper(parsingConfig);
49126

50-
// Verify
51-
_dynamicLinkCustomTypeProviderMock.Verify(d => d.GetCustomTypes(), Times.Once);
52-
_dynamicLinkCustomTypeProviderMock.Verify(d => d.ResolveType($"{typeof(SampleDto).FullName}"), Times.Once);
127+
var field = _constantExpressionHelperFactoryType.GetField("_instance", BindingFlags.NonPublic | BindingFlags.Static);
53128

54-
_expressionPromoterMock.Verify(e => e.Promote(It.IsAny<ConstantExpression>(), typeof(Guid), true, true), Times.Once);
129+
field?.SetValue(field, instance);
130+
}
55131
}
56-
}
132+
}

0 commit comments

Comments
 (0)