-
-
Notifications
You must be signed in to change notification settings - Fork 121
Expand file tree
/
Copy pathTestArgumentRegistrationService.cs
More file actions
160 lines (141 loc) · 7.34 KB
/
TestArgumentRegistrationService.cs
File metadata and controls
160 lines (141 loc) · 7.34 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
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using TUnit.Core;
using TUnit.Core.Data;
using TUnit.Core.Enums;
using TUnit.Core.Interfaces;
using TUnit.Core.Interfaces.SourceGenerator;
using TUnit.Core.PropertyInjection;
using TUnit.Core.Tracking;
namespace TUnit.Engine.Services;
/// <summary>
/// Service that handles registration of test arguments (constructor args, method args) during test discovery.
/// Implements ITestRegisteredEventReceiver to register objects when tests are registered.
/// Renamed from TestArgumentTrackingService to clarify it's for the registration phase.
/// </summary>
internal sealed class TestArgumentRegistrationService : ITestRegisteredEventReceiver
{
private readonly ObjectRegistrationService _objectRegistrationService;
private readonly ObjectTracker _objectTracker;
public TestArgumentRegistrationService(ObjectRegistrationService objectRegistrationService, ObjectTracker objectTracker)
{
_objectRegistrationService = objectRegistrationService;
_objectTracker = objectTracker;
}
public int Order => int.MinValue; // Run first to ensure registration happens before other event receivers
/// <summary>
/// Called when a test is registered. This is the correct time to register constructor and method arguments
/// for proper reference counting and disposal tracking.
/// </summary>
public async ValueTask OnTestRegistered(TestRegisteredContext context)
{
var testContext = context.TestContext;
var classArguments = testContext.Metadata.TestDetails.TestClassArguments;
var methodArguments = testContext.Metadata.TestDetails.TestMethodArguments;
// Register class arguments (registration phase - property injection + tracking, NO IAsyncInitializer)
await _objectRegistrationService.RegisterArgumentsAsync(
classArguments,
testContext.StateBag.Items,
testContext.Metadata.TestDetails.MethodMetadata,
testContext.InternalEvents);
// Register method arguments (registration phase)
await _objectRegistrationService.RegisterArgumentsAsync(
methodArguments,
testContext.StateBag.Items,
testContext.Metadata.TestDetails.MethodMetadata,
testContext.InternalEvents);
// Register properties that will be injected into the test class
await RegisterPropertiesAsync(testContext);
_objectTracker.TrackObjects(testContext);
}
/// <summary>
/// Registers properties that will be injected into the test class instance.
/// This ensures proper reference counting for all property-injected instances during discovery.
/// Exceptions during data generation will be caught and associated with the test for reporting.
/// </summary>
private async ValueTask RegisterPropertiesAsync(TestContext testContext)
{
try
{
var classType = testContext.Metadata.TestDetails.ClassType;
// Get the property source for the class
var propertySource = PropertySourceRegistry.GetSource(classType);
if (propertySource?.ShouldInitialize != true)
{
// No properties to inject for this class
return;
}
// Get all properties that need injection
var propertyMetadata = propertySource.GetPropertyMetadata();
foreach (var metadata in propertyMetadata)
{
try
{
// Create the data source for this property
var dataSource = metadata.CreateDataSource();
// Create minimal DataGeneratorMetadata for property resolution during registration
var testBuilderContext = new TestBuilderContext
{
TestMetadata = testContext.Metadata.TestDetails.MethodMetadata,
DataSourceAttribute = dataSource,
Events = testContext.InternalEvents,
StateBag = testContext.StateBag.Items
};
var dataGenMetadata = new DataGeneratorMetadata
{
TestBuilderContext = new TestBuilderContextAccessor(testBuilderContext),
MembersToGenerate = [], // Properties don't use member generation
TestInformation = testContext.Metadata.TestDetails.MethodMetadata,
Type = DataGeneratorType.Property,
TestSessionId = TestSessionContext.Current?.Id ?? "registration",
TestClassInstance = null, // Not available during registration
ClassInstanceArguments = testContext.Metadata.TestDetails.TestClassArguments
};
// Get the data rows from the data source
var dataRows = dataSource.GetDataRowsAsync(dataGenMetadata);
// Get the first data row (properties get single values, not multiple)
await foreach (var dataRowFunc in dataRows)
{
var dataRow = await dataRowFunc();
if (dataRow is { Length: > 0 })
{
var data = dataRow[0];
if (data != null)
{
// Store for later injection
testContext.Metadata.TestDetails.TestClassInjectedPropertyArguments[metadata.PropertyName] = data;
// Register the ClassDataSource instance during registration phase
// This does: property injection + tracking (NO IAsyncInitializer - deferred to execution)
await _objectRegistrationService.RegisterObjectAsync(
data,
testContext.StateBag.Items,
testContext.Metadata.TestDetails.MethodMetadata,
testContext.InternalEvents);
}
}
break; // Only take the first result for property injection
}
}
catch (Exception ex)
{
// Capture the exception for this property and re-throw
// The test building process will handle marking it as failed
var exceptionMessage = $"Failed to generate data for property '{metadata.PropertyName}': {ex.Message}";
var propertyException = new InvalidOperationException(exceptionMessage, ex);
throw propertyException;
}
}
}
catch (Exception ex)
{
// Capture any top-level exceptions (e.g., getting property source) and re-throw
// The test building process will handle marking it as failed
var exceptionMessage = $"Failed to register properties for test '{testContext.Metadata.TestDetails.TestName}': {ex.Message}";
var registrationException = new InvalidOperationException(exceptionMessage, ex);
throw registrationException;
}
}
}