Skip to content

Commit 6bb7218

Browse files
committed
Run Categories Separately
1 parent 15b7beb commit 6bb7218

25 files changed

Lines changed: 326 additions & 89 deletions

eng/devices/catalyst.cake

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -85,32 +85,54 @@ void ExecuteBuild(string project, string binDir, string config, string rid, stri
8585
});
8686
}
8787

88+
8889
void ExecuteTests(string project, string device, string resultsDir, string config, string tfm, string rid, string toolPath)
8990
{
9091
CleanResults(resultsDir);
9192

9293
var testApp = GetTestApplications(project, device, config, tfm, rid).FirstOrDefault();
93-
9494
Information($"Testing App: {testApp}");
95-
var settings = new DotNetToolSettings
96-
{
97-
DiagnosticOutput = true,
98-
ToolPath = toolPath,
99-
ArgumentCustomization = args => args.Append($"run xharness apple test --app=\"{testApp}\" --targets=\"{device}\" --output-directory=\"{resultsDir}\" --verbosity=\"Debug\" ")
100-
};
10195

102-
bool testsFailed = true;
103-
try
104-
{
105-
DotNetTool("tool", settings);
106-
testsFailed = false;
107-
}
108-
finally
96+
Exception exception = null;
97+
98+
bool testRunFailed = false;
99+
100+
foreach (var category in GetTestCategoriesToRunSeparately(projectPath))
109101
{
110-
HandleTestResults(resultsDir, testsFailed, true);
102+
Information($"Running tests for category: {category}");
103+
var settings = new DotNetToolSettings
104+
{
105+
DiagnosticOutput = true,
106+
ToolPath = toolPath,
107+
ArgumentCustomization = args =>
108+
args.Append($"run xharness apple test --app=\"{testApp}\" --targets=\"{device}\" --output-directory=\"{resultsDir}\" " +
109+
//" --verbosity=\"Debug\" " +
110+
$"--set-env=\"TestFilter={category}\" ")
111+
};
112+
113+
bool testsFailed = true;
114+
try
115+
{
116+
DotNetTool("tool", settings);
117+
testsFailed = false;
118+
}
119+
catch (Exception ex)
120+
{
121+
exception = ex;
122+
}
123+
finally
124+
{
125+
HandleTestResults(resultsDir, testsFailed, true, "-" + category.Split('=').Skip(1).FirstOrDefault());
126+
}
127+
128+
testRunFailed = testRunFailed || testsFailed;
111129
}
112130

113131
Information("Testing completed.");
132+
if (exception is not null)
133+
{
134+
throw exception;
135+
}
114136
}
115137

116138
void ExecuteUITests(string project, string app, string device, string resultsDir, string binDir, string config, string tfm, string rid, string toolPath)

eng/devices/devices-shared.cake

Lines changed: 94 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,72 @@ void CleanResults(string resultsDir)
133133
}
134134
}
135135

136-
void HandleTestResults(string resultsDir, bool testsFailed, bool needsNameFix)
136+
List<string> GetTestCategoriesToRunSeparately(string projectPath)
137+
{
138+
139+
if (!string.IsNullOrEmpty(testFilter))
140+
{
141+
return new List<string> { testFilter };
142+
}
143+
144+
if (!projectPath.EndsWith("Controls.DeviceTests.csproj") && !projectPath.EndsWith("Core.DeviceTests.csproj"))
145+
{
146+
return new List<string>
147+
{
148+
""
149+
};
150+
}
151+
152+
var file = Context.GetCallerInfo().SourceFilePath;
153+
var directoryPath = file.GetDirectory().FullPath;
154+
Information($"Directory: {directoryPath}");
155+
Information(directoryPath);
156+
157+
// Search for files that match the pattern
158+
FilePathCollection dllFilePath = null;
159+
160+
if (projectPath.EndsWith("Controls.DeviceTests.csproj"))
161+
dllFilePath = GetFiles($"{directoryPath}/../../**/Microsoft.Maui.Controls.DeviceTests.dll").ToList();
162+
163+
if (projectPath.EndsWith("Core.DeviceTests.csproj"))
164+
dllFilePath = GetFiles($"{directoryPath}/../../**/Microsoft.Maui.Core.DeviceTests.dll").ToList();
165+
166+
System.Reflection.Assembly loadedAssembly = null;
167+
168+
foreach (var filePath in dllFilePath)
169+
{
170+
try
171+
{
172+
loadedAssembly = System.Reflection.Assembly.LoadFrom(filePath.FullPath);
173+
Information($"Loaded assembly from {filePath}: {loadedAssembly.FullName}");
174+
break; // Exit the loop if the assembly is loaded successfully
175+
}
176+
catch (Exception ex)
177+
{
178+
Warning($"Failed to load assembly from {filePath}: {ex.Message}");
179+
}
180+
}
181+
182+
if (loadedAssembly == null)
183+
{
184+
throw new Exception("No test assembly found.");
185+
}
186+
var testCategoryType = loadedAssembly.GetType("Microsoft.Maui.DeviceTests.TestCategory");
187+
188+
var values = new List<string>();
189+
190+
foreach (var field in testCategoryType.GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static))
191+
{
192+
if (field.FieldType == typeof(string))
193+
{
194+
values.Add($"Category={(string)field.GetValue(null)}");
195+
}
196+
}
197+
198+
return values.ToList();
199+
}
200+
201+
void HandleTestResults(string resultsDir, bool testsFailed, bool needsNameFix, string suffix = null)
137202
{
138203
Information($"Handling test results: {resultsDir}");
139204

@@ -145,10 +210,24 @@ void HandleTestResults(string resultsDir, bool testsFailed, bool needsNameFix)
145210
{
146211
throw new Exception("No test results found.");
147212
}
213+
148214
if (FileExists(resultsFile))
149215
{
150-
Information($"Test results found on {resultsDir}");
151-
CopyFile(resultsFile, resultsFile.GetDirectory().CombineWithFilePath("TestResults.xml"));
216+
Information($"Test results found on {resultsFile}");
217+
MoveFile(resultsFile, resultsFile.GetDirectory().CombineWithFilePath($"TestResults{suffix}.xml"));
218+
var logFiles = GetFiles($"{resultsDir}/*.log");
219+
220+
foreach (var logFile in logFiles)
221+
{
222+
Information($"Log file found: {logFile.GetFilename().ToString()}");
223+
if (logFile.GetFilename().ToString().StartsWith("TestResults"))
224+
{
225+
// These are log files that have already been renamed
226+
continue;
227+
}
228+
229+
MoveFile(logFile, resultsFile.GetDirectory().CombineWithFilePath($"TestResults{suffix}-{logFile.GetFilename()}"));
230+
}
152231
}
153232
}
154233

@@ -158,12 +237,21 @@ void HandleTestResults(string resultsDir, bool testsFailed, bool needsNameFix)
158237
EnsureDirectoryExists(failurePath);
159238
// The tasks will retry the tests and overwrite the failed results each retry
160239
// we want to retain the failed results for diagnostic purposes
161-
CopyFiles($"{resultsDir}/*.*", failurePath);
240+
241+
var searchQuery = "*.*";
242+
243+
if (!string.IsNullOrWhiteSpace(suffix))
244+
{
245+
searchQuery = $"*{suffix}*.*";
246+
}
247+
248+
// Only copy files from this suffix set of failures
249+
CopyFiles($"{resultsDir}/{searchQuery}", failurePath);
162250

163251
// We don't want these to upload
164-
MoveFile($"{failurePath}/TestResults.xml", $"{failurePath}/Results.xml");
252+
MoveFile($"{failurePath}/TestResults{suffix}.xml", $"{failurePath}/Results{suffix}.xml");
165253
}
166-
FailRunOnOnlyInconclusiveTests($"{resultsDir}/TestResults.xml");
254+
FailRunOnOnlyInconclusiveTests($"{resultsDir}/TestResults{suffix}.xml");
167255
}
168256

169257
DirectoryPath DetermineBinlogDirectory(string projectPath, string binlogArg)

eng/devices/ios.cake

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -173,45 +173,62 @@ void ExecuteTests(string project, string device, string resultsDir, string confi
173173

174174
Information($"Testing App: {testApp}");
175175

176-
var settings = new DotNetToolSettings
176+
Exception exception = null;
177+
178+
foreach (var category in GetTestCategoriesToRunSeparately(projectPath))
177179
{
178-
ToolPath = toolPath,
179-
DiagnosticOutput = true,
180-
ArgumentCustomization = args =>
180+
Information($"Running tests for category: {category}");
181+
var settings = new DotNetToolSettings
181182
{
182-
args.Append("run xharness apple test " +
183-
$"--app=\"{testApp}\" " +
184-
$"--targets=\"{device}\" " +
185-
$"--output-directory=\"{resultsDir}\" " +
186-
$"--timeout=01:15:00 " +
187-
$"--launch-timeout=00:06:00 " +
188-
xcode_args +
189-
$"--verbosity=\"Debug\" ");
190-
191-
if (device.Contains("device"))
183+
ToolPath = toolPath,
184+
DiagnosticOutput = true,
185+
ArgumentCustomization = args =>
192186
{
193-
if (string.IsNullOrEmpty(DEVICE_UDID))
187+
args.Append("run xharness apple test " +
188+
$"--app=\"{testApp}\" " +
189+
$"--targets=\"{device}\" " +
190+
$"--output-directory=\"{resultsDir}\" " +
191+
$"--timeout=01:15:00 " +
192+
$"--launch-timeout=00:06:00 " +
193+
xcode_args +
194+
//$"--verbosity=\"Debug\" " +
195+
$"--set-env=\"TestFilter={category}\" ");
196+
197+
if (device.Contains("device"))
194198
{
195-
throw new Exception("No device was found to install the app on. See the Setup method for more details.");
199+
if (string.IsNullOrEmpty(DEVICE_UDID))
200+
{
201+
throw new Exception("No device was found to install the app on. See the Setup method for more details.");
202+
}
203+
args.Append($"--device=\"{DEVICE_UDID}\" ");
196204
}
197-
args.Append($"--device=\"{DEVICE_UDID}\" ");
205+
return args;
198206
}
199-
return args;
200-
}
201-
};
207+
};
202208

203-
bool testsFailed = true;
204-
try
205-
{
206-
DotNetTool("tool", settings);
207-
testsFailed = false;
208-
}
209-
finally
210-
{
211-
HandleTestResults(resultsDir, testsFailed, true);
209+
bool testsFailed = true;
210+
211+
try
212+
{
213+
DotNetTool("tool", settings);
214+
testsFailed = false;
215+
}
216+
catch (Exception ex)
217+
{
218+
exception = ex;
219+
}
220+
finally
221+
{
222+
HandleTestResults(resultsDir, testsFailed, true, "-" + category.Split('=').Skip(1).FirstOrDefault());
223+
}
212224
}
213225

214226
Information("Testing completed.");
227+
228+
if (exception is not null)
229+
{
230+
throw exception;
231+
}
215232
}
216233

217234
void ExecutePrepareUITests(string project, string app, string device, string resultsDir, string binDir, string config, string tfm, string rid, string ver, string toolPath)

eng/pipelines/common/device-tests-steps.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,14 +138,12 @@ steps:
138138
displayName: Execute Build
139139
workingDirectory: ${{ parameters.checkoutDirectory }}
140140
condition: and(succeeded(), ne('${{ parameters.buildType }}', 'testOnly'))
141-
retryCountOnTaskFailure: 1
142141

143142
# Then run the tests if this is a test-only or build-and-test job
144143
- script: dotnet cake eng/devices/${{ parameters.platform }}.cake --target="testOnly" --project="${{ parameters.path }}" --binlog="$(LogDirectory)" --configuration="${{ parameters.deviceTestConfiguration }}" --targetFrameworkVersion="${{ parameters.targetFrameworkVersion }}" ${{ iif(eq(parameters.device, ''), '', format('--device="{0}"', parameters.device)) }} ${{ iif(eq(parameters.apiversion, ''), '', format('--apiversion="{0}"', parameters.apiversion)) }} --create="${{ ne(parameters.buildType, 'buildOnly') }}" --packageid "${{ parameters.packageid }}" --results="$(TestResultsDirectory)" --workloads="${{ iif(eq(parameters.skipDotNet, 'true'), 'global', 'local') }}" --verbosity=diagnostic ${{ parameters.cakeArgs }}
145144
displayName: Execute Test Run
146145
workingDirectory: ${{ parameters.checkoutDirectory }}
147146
condition: and(succeeded(), ne('${{ parameters.buildType }}', 'buildOnly'))
148-
retryCountOnTaskFailure: 1
149147

150148

151149
##################################################

src/Controls/tests/DeviceTests/ControlsHandlerTestBase.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ protected Task CreateHandlerAndAddToWindow<THandler>(IElement view, Action<THand
140140
protected Task CreateHandlerAndAddToWindow<THandler>(IElement view, Func<THandler, Task> action, IMauiContext mauiContext = null, TimeSpan? timeOut = null)
141141
where THandler : class, IElementHandler
142142
{
143+
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
143144
mauiContext ??= MauiContext;
144145

145146
if (System.Diagnostics.Debugger.IsAttached)
@@ -275,7 +276,11 @@ void OnBatchCommitted(object sender, Controls.Internals.EventArg<VisualElement>
275276
finally
276277
{
277278
_takeOverMainContentSempahore.Release();
278-
TestRunnerLogger.LogDebug($"Finished Running Test");
279+
stopwatch.Stop();
280+
TestRunnerLogger.LogDebug($"Finished Running Test: {stopwatch.Elapsed}");
281+
282+
if (stopwatch.ElapsedMilliseconds > 15000)
283+
TestRunnerLogger.LogError($"Test took longer than 15 seconds to complete.");
279284
}
280285
});
281286
}

src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.iOS.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ UIButton GetPlatformButton(ButtonHandler buttonHandler) =>
1717

1818
Task<string> GetPlatformText(ButtonHandler buttonHandler)
1919
{
20-
return InvokeOnMainThreadAsync(() => GetPlatformButton(buttonHandler).CurrentTitle);
20+
return InvokeOnMainThreadAsync(() => GetPlatformButton(buttonHandler).CurrentTitle!);
2121
}
2222

2323
UILineBreakMode GetPlatformLineBreakMode(ButtonHandler buttonHandler) =>

src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,6 @@ await InvokeOnMainThreadAsync(async () =>
326326
}
327327

328328
[Category(TestCategory.Editor)]
329-
[Category(TestCategory.TextInput)]
330329
[Collection(RunInNewWindowCollection)]
331330
public class EditorTextInputTests : TextInputTests<EditorHandler, Editor>
332331
{
@@ -340,4 +339,4 @@ protected override Task<string> GetPlatformText(EditorHandler handler) =>
340339
EditorTests.GetPlatformText(handler);
341340
}
342341
}
343-
}
342+
}

src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,6 @@ await InvokeOnMainThreadAsync(async () =>
239239
}
240240

241241
[Category(TestCategory.Entry)]
242-
[Category(TestCategory.TextInput)]
243242
[Collection(RunInNewWindowCollection)]
244243
public class EntryTextInputTests : TextInputTests<EntryHandler, Entry>
245244
{

src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.iOS.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ Task<bool> GetPlatformIsVisible(EntryHandler entryHandler)
6666
}
6767

6868
[Collection(ControlsHandlerTestBase.RunInNewWindowCollection)]
69+
[Category(TestCategory.Entry)]
6970
public class ScrollTests : ControlsHandlerTestBase
7071
{
7172
[Fact]
@@ -126,6 +127,7 @@ await contentViewHandler.PlatformView.AttachAndRun(async () =>
126127
}
127128

128129
[Collection(ControlsHandlerTestBase.RunInNewWindowCollection)]
130+
[Category(TestCategory.Entry)]
129131
public class NextKeyboardTests : ControlsHandlerTestBase
130132
{
131133
void SetupNextBuilder()

src/Controls/tests/DeviceTests/Elements/Page/PageTests.Android.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
namespace Microsoft.Maui.DeviceTests
1111
{
12+
[Category(TestCategory.Page)]
1213
public partial class PageTests : ControlsHandlerTestBase
1314
{
1415
//src/Compatibility/Core/tests/Android/EmbeddingTests.cs

0 commit comments

Comments
 (0)