-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathAsyncBlockExpressionRuntimeOptionsTests.cs
More file actions
306 lines (261 loc) · 10 KB
/
AsyncBlockExpressionRuntimeOptionsTests.cs
File metadata and controls
306 lines (261 loc) · 10 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using Hyperbee.Expressions.Tests.TestSupport;
using static System.Linq.Expressions.Expression;
using static Hyperbee.Expressions.ExpressionExtensions;
namespace Hyperbee.Expressions.Tests;
[TestClass]
public class AsyncBlockExpressionRuntimeOptionsTests
{
[TestMethod]
[DataRow( CompilerType.Fast )]
[DataRow( CompilerType.System )]
[DataRow( CompilerType.Interpret )]
public async Task BlockAsync_ShouldUseDefaultProvider_WhenNoOptionsProvided( CompilerType compiler )
{
// Arrange
var block = BlockAsync(
Await( AsyncHelper.Completer(
Constant( CompleterType.Immediate ),
Constant( 42 )
) )
);
var lambda = Lambda<Func<Task<int>>>( block );
var compiledLambda = lambda.Compile( compiler );
// Act
var result = await compiledLambda();
// Assert
Assert.AreEqual( 42, result );
}
[TestMethod]
[DataRow( CompilerType.Fast )]
[DataRow( CompilerType.System )]
[DataRow( CompilerType.Interpret )]
public async Task BlockAsync_ShouldUseCollectibleProvider_WhenProvidedInOptions( CompilerType compiler )
{
// Arrange
var options = new ExpressionRuntimeOptions
{
ModuleBuilderProvider = new CollectibleModuleBuilderProvider()
};
var block = BlockAsync(
new[]
{
Await( AsyncHelper.Completer(
Constant( CompleterType.Immediate ),
Constant( 100 )
) )
},
options
);
var lambda = Lambda<Func<Task<int>>>( block );
var compiledLambda = lambda.Compile( compiler );
// Act
var result = await compiledLambda();
// Assert
Assert.AreEqual( 100, result );
}
[TestMethod]
[DataRow( CompilerType.Fast )]
[DataRow( CompilerType.System )]
[DataRow( CompilerType.Interpret )]
public async Task BlockAsync_ShouldUseCustomProvider_WhenProvidedInOptions( CompilerType compiler )
{
// Arrange
var customProvider = new CustomTestModuleBuilderProvider();
var options = new ExpressionRuntimeOptions { ModuleBuilderProvider = customProvider };
var block = BlockAsync(
new[]
{
Await( AsyncHelper.Completer(
Constant( CompleterType.Immediate ),
Constant( 200 )
) )
},
options
);
var lambda = Lambda<Func<Task<int>>>( block );
var compiledLambda = lambda.Compile( compiler );
// Act
var result = await compiledLambda();
// Assert
Assert.AreEqual( 200, result );
Assert.IsTrue( customProvider.WasCalled );
}
[TestMethod]
[DataRow( CompilerType.Fast )]
[DataRow( CompilerType.System )]
[DataRow( CompilerType.Interpret )]
public async Task BlockAsync_WithVariables_ShouldUseCustomProvider( CompilerType compiler )
{
// Arrange
var options = new ExpressionRuntimeOptions
{
ModuleBuilderProvider = new CollectibleModuleBuilderProvider()
};
var result1 = Variable( typeof( int ), "result1" );
var result2 = Variable( typeof( int ), "result2" );
var block = BlockAsync(
new[] { result1, result2 },
new Expression[]
{
Assign( result1, Await( AsyncHelper.Completer(
Constant( CompleterType.Immediate ),
Constant( 10 )
) ) ),
Assign( result2, Await( AsyncHelper.Completer(
Constant( CompleterType.Immediate ),
Constant( 20 )
) ) ),
Add( result1, result2 )
},
options
);
var lambda = Lambda<Func<Task<int>>>( block );
var compiledLambda = lambda.Compile( compiler );
// Act
var result = await compiledLambda();
// Assert
Assert.AreEqual( 30, result );
}
[TestMethod]
[DataRow( CompilerType.Fast )]
[DataRow( CompilerType.System )]
[DataRow( CompilerType.Interpret )]
public async Task BlockAsync_MultipleCalls_ShouldUseSameModuleBuilder( CompilerType compiler )
{
// Arrange
var trackingProvider = new TrackingModuleBuilderProvider();
var options = new ExpressionRuntimeOptions { ModuleBuilderProvider = trackingProvider };
var block1 = BlockAsync(
new[] { Await( AsyncHelper.Completer( Constant( CompleterType.Immediate ), Constant( 1 ) ) ) },
options
);
var block2 = BlockAsync(
new[] { Await( AsyncHelper.Completer( Constant( CompleterType.Immediate ), Constant( 2 ) ) ) },
options
);
var lambda1 = Lambda<Func<Task<int>>>( block1 );
var lambda2 = Lambda<Func<Task<int>>>( block2 );
var compiledLambda1 = lambda1.Compile( compiler );
var compiledLambda2 = lambda2.Compile( compiler );
// Act
var result1 = await compiledLambda1();
var result2 = await compiledLambda2();
// Assert
Assert.AreEqual( 1, result1 );
Assert.AreEqual( 2, result2 );
// Provider should be called at least once per block (might be called during Reduce)
Assert.IsTrue( trackingProvider.AsyncCallCount >= 2,
$"Expected at least 2 calls, but got {trackingProvider.AsyncCallCount}" );
// Verify the same ModuleBuilder instance is reused
Assert.AreSame( trackingProvider.GetModuleBuilder( ModuleKind.Async ),
trackingProvider.GetModuleBuilder( ModuleKind.Async ) );
}
[TestMethod]
[DataRow( CompilerType.Fast )]
[DataRow( CompilerType.System )]
[DataRow( CompilerType.Interpret )]
public async Task BlockAsync_SourceHandler_ShouldCaptureStateMachineSource( CompilerType compiler )
{
// Arrange
string capturedSource = null;
var options = new ExpressionRuntimeOptions
{
SourceHandler = source => capturedSource = source
};
var block = BlockAsync(
new[]
{
Await( AsyncHelper.Completer(
Constant( CompleterType.Immediate ),
Constant( 42 )
) )
},
options
);
var lambda = Lambda<Func<Task<int>>>( block );
var compiledLambda = lambda.Compile( compiler );
// Act
var result = await compiledLambda();
// Assert
Assert.AreEqual( 42, result );
Assert.IsNotNull( capturedSource, "SourceHandler should have been called" );
Assert.IsTrue( capturedSource.Length > 0, "Captured source should not be empty" );
Assert.IsTrue( capturedSource.Contains( "__state<>" ),
"Captured source should contain state machine state field" );
Assert.IsTrue( capturedSource.Contains( "__builder<>" ),
"Captured source should contain state machine builder field" );
}
[TestMethod]
public async Task BlockAsync_SourceHandler_ShouldNotBeCalled_WhenNotProvided()
{
// Arrange - no SourceHandler set
var block = BlockAsync(
Await( AsyncHelper.Completer(
Constant( CompleterType.Immediate ),
Constant( 42 )
) )
);
var lambda = Lambda<Func<Task<int>>>( block );
var compiledLambda = lambda.Compile( CompilerType.Fast );
// Act - should complete without error
var result = await compiledLambda();
// Assert
Assert.AreEqual( 42, result );
}
// Helper: Custom test provider that tracks usage
private class CustomTestModuleBuilderProvider : IModuleBuilderProvider
{
private readonly Lazy<ModuleBuilder> _sharedModule = new( () =>
{
var assemblyName = new AssemblyName( "CustomTestAssembly" );
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly( assemblyName, AssemblyBuilderAccess.Run );
return assemblyBuilder.DefineDynamicModule( "CustomTestModule" );
} );
public bool WasCalled { get; private set; }
public ModuleBuilder GetModuleBuilder( ModuleKind kind )
{
WasCalled = true;
return _sharedModule.Value;
}
}
// Helper: Tracking provider that counts calls
private class TrackingModuleBuilderProvider : IModuleBuilderProvider
{
private readonly Lazy<ModuleBuilder> _asyncModule = new( () =>
{
var assemblyName = new AssemblyName( "TrackingAsyncAssembly" );
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly( assemblyName, AssemblyBuilderAccess.Run );
return assemblyBuilder.DefineDynamicModule( "TrackingAsyncModule" );
} );
private readonly Lazy<ModuleBuilder> _enumerableModule = new( () =>
{
var assemblyName = new AssemblyName( "TrackingEnumerableAssembly" );
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly( assemblyName, AssemblyBuilderAccess.Run );
return assemblyBuilder.DefineDynamicModule( "TrackingEnumerableModule" );
} );
public int AsyncCallCount { get; private set; }
public int EnumerableCallCount { get; private set; }
public ModuleBuilder GetModuleBuilder( ModuleKind kind )
{
return kind switch
{
ModuleKind.Async => GetAsyncModule(),
ModuleKind.Enumerable => GetEnumerableModule(),
_ => throw new ArgumentOutOfRangeException( nameof( kind ) )
};
}
private ModuleBuilder GetAsyncModule()
{
AsyncCallCount++;
return _asyncModule.Value;
}
private ModuleBuilder GetEnumerableModule()
{
EnumerableCallCount++;
return _enumerableModule.Value;
}
}
}