Skip to content

Commit 6bca5cc

Browse files
authored
Fix exit code detection on Apple devices and iOS 15+ Simulators (#847)
1 parent 98d268d commit 6bca5cc

File tree

6 files changed

+193
-119
lines changed

6 files changed

+193
-119
lines changed

src/Microsoft.DotNet.XHarness.Apple/ExitCodeDetector.cs

Lines changed: 54 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
using System;
66
using System.IO;
7-
using System.Linq;
87
using System.Text.RegularExpressions;
98
using Microsoft.DotNet.XHarness.Common.Logging;
109
using Microsoft.DotNet.XHarness.iOS.Shared;
@@ -25,58 +24,85 @@ public interface IMacCatalystExitCodeDetector : IExitCodeDetector
2524

2625
public abstract class ExitCodeDetector : IExitCodeDetector
2726
{
28-
public int? DetectExitCode(AppBundleInformation appBundleInfo, IReadableLog systemLog)
27+
// This tag is logged by the dotnet/runtime Apple app wrapper
28+
// https://github.com/dotnet/runtime/blob/a883caa0803778084167b978281c34db8e753246/src/tasks/AppleAppBuilder/Templates/runtime.m#L30
29+
protected const string DotnetAppExitTag = "DOTNET.APP_EXIT_CODE:";
30+
31+
// This line is logged by MacOS
32+
protected const string AbnormalExitMessage = "Service exited with abnormal code";
33+
34+
public int? DetectExitCode(AppBundleInformation appBundleInfo, IReadableLog log)
2935
{
3036
StreamReader reader;
3137

3238
try
3339
{
34-
reader = systemLog.GetReader();
40+
reader = log.GetReader();
3541
}
3642
catch (FileNotFoundException e)
3743
{
38-
throw new Exception("Failed to detect application's exit code. The system log was empty / not found at " + e.FileName);
44+
throw new Exception("Failed to detect application's exit code. The log file was empty / not found at " + e.FileName);
3945
}
4046

4147
using (reader)
42-
while (!reader.EndOfStream)
48+
while (!reader.EndOfStream)
49+
{
50+
if (reader.ReadLine() is string line
51+
&& IsSignalLine(appBundleInfo, line) is Match match && match.Success
52+
&& int.TryParse(match.Groups["exitCode"].Value, out var exitCode))
4353
{
44-
var line = reader.ReadLine();
54+
return exitCode;
55+
}
56+
}
4557

46-
if (!string.IsNullOrEmpty(line) && IsSignalLine(appBundleInfo, line))
47-
{
48-
var match = ExitCodeRegex.Match(line);
58+
return null;
59+
}
4960

50-
if (match.Success && int.TryParse(match.Captures.First().Value, out var exitCode))
51-
{
52-
return exitCode;
53-
}
54-
}
55-
}
61+
protected virtual Match? IsSignalLine(AppBundleInformation appBundleInfo, string logLine)
62+
{
63+
if (IsAbnormalExitLine(appBundleInfo, logLine) || IsStdoutExitLine(appBundleInfo, logLine))
64+
{
65+
return EoLExitCodeRegex.Match(logLine);
66+
}
5667

5768
return null;
5869
}
5970

60-
protected abstract bool IsSignalLine(AppBundleInformation appBundleInfo, string logLine);
71+
protected Regex EoLExitCodeRegex { get; } = new Regex(@" (?<exitCode>-?[0-9]+)$", RegexOptions.Compiled);
72+
73+
// Example line coming from app's stdout log stream
74+
// 2022-03-18 12:48:53.336 I Microsoft.Extensions.Configuration.CommandLine.Tests[12477:10069] DOTNET.APP_EXIT_CODE: 0
75+
private static bool IsStdoutExitLine(AppBundleInformation appBundleInfo, string logLine) =>
76+
logLine.Contains(DotnetAppExitTag) && logLine.Contains(appBundleInfo.BundleExecutable ?? appBundleInfo.BundleIdentifier);
6177

62-
protected virtual Regex ExitCodeRegex { get; } = new Regex(" (\\-?[0-9]+)$", RegexOptions.Compiled);
78+
// Example line
79+
// Feb 18 06:40:16 Admins-Mac-Mini com.apple.xpc.launchd[1] (net.dot.System.Buffers.Tests.15140[59229]): Service exited with abnormal code: 74
80+
private static bool IsAbnormalExitLine(AppBundleInformation appBundleInfo, string logLine) =>
81+
logLine.Contains(AbnormalExitMessage) && (logLine.Contains(appBundleInfo.AppName) || logLine.Contains(appBundleInfo.BundleIdentifier));
6382
}
6483

6584
public class iOSExitCodeDetector : ExitCodeDetector, IiOSExitCodeDetector
6685
{
67-
// Example line
68-
// Nov 18 04:31:44 ML-MacVM com.apple.CoreSimulator.SimDevice.2E1EE736-5672-4220-89B5-B7C77DB6AF18[55655] (UIKitApplication:net.dot.HelloiOS[9a0b][rb-legacy][57331]): Service exited with abnormal code: 200
69-
protected override bool IsSignalLine(AppBundleInformation appBundleInfo, string logLine) =>
70-
logLine.Contains("UIKitApplication:") &&
71-
logLine.Contains("Service exited with abnormal code") &&
72-
(logLine.Contains(appBundleInfo.AppName) || logLine.Contains(appBundleInfo.BundleIdentifier));
86+
// Example line coming from the mlaunch log
87+
// [07:02:21.6637600] Application 'net.dot.iOS.Simulator.PInvoke.Test' terminated (with exit code '42' and/or crashing signal ').
88+
private Regex DeviceExitCodeRegex { get; } = new Regex(@"terminated \(with exit code '(?<exitCode>-?[0-9]+)' and/or crashing signal", RegexOptions.Compiled);
89+
90+
protected override Match? IsSignalLine(AppBundleInformation appBundleInfo, string logLine)
91+
{
92+
if (base.IsSignalLine(appBundleInfo, logLine) is Match match && match.Success)
93+
{
94+
return match;
95+
}
96+
97+
if (logLine.Contains(appBundleInfo.BundleIdentifier))
98+
{
99+
return DeviceExitCodeRegex.Match(logLine);
100+
}
101+
102+
return null;
103+
}
73104
}
74105

75106
public class MacCatalystExitCodeDetector : ExitCodeDetector, IMacCatalystExitCodeDetector
76107
{
77-
// Example line
78-
// Feb 18 06:40:16 Admins-Mac-Mini com.apple.xpc.launchd[1] (net.dot.System.Buffers.Tests.15140[59229]): Service exited with abnormal code: 74
79-
protected override bool IsSignalLine(AppBundleInformation appBundleInfo, string logLine) =>
80-
logLine.Contains("Service exited with abnormal code") &&
81-
(logLine.Contains(appBundleInfo.AppName) || logLine.Contains(appBundleInfo.BundleIdentifier));
82108
}

src/Microsoft.DotNet.XHarness.Apple/Orchestration/RunOrchestrator.cs

Lines changed: 44 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
1-
// Licensed to the .NET Foundation under one or more agreements.
2-
// The .NET Foundation licenses this file to you under the MIT license.
3-
// See the LICENSE file in the project root for more information.
4-
5-
using System;
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
66
using System.Collections.Generic;
7-
using System.Linq;
8-
using System.Threading;
9-
using System.Threading.Tasks;
10-
using Microsoft.DotNet.XHarness.Common;
11-
using Microsoft.DotNet.XHarness.Common.CLI;
12-
using Microsoft.DotNet.XHarness.Common.Execution;
13-
using Microsoft.DotNet.XHarness.Common.Logging;
14-
using Microsoft.DotNet.XHarness.iOS.Shared;
15-
using Microsoft.DotNet.XHarness.iOS.Shared.Hardware;
16-
using Microsoft.DotNet.XHarness.iOS.Shared.Logging;
17-
using Microsoft.DotNet.XHarness.iOS.Shared.Utilities;
18-
7+
using System.Linq;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
using Microsoft.DotNet.XHarness.Common;
11+
using Microsoft.DotNet.XHarness.Common.CLI;
12+
using Microsoft.DotNet.XHarness.Common.Execution;
13+
using Microsoft.DotNet.XHarness.Common.Logging;
14+
using Microsoft.DotNet.XHarness.iOS.Shared;
15+
using Microsoft.DotNet.XHarness.iOS.Shared.Hardware;
16+
using Microsoft.DotNet.XHarness.iOS.Shared.Logging;
17+
using Microsoft.DotNet.XHarness.iOS.Shared.Utilities;
18+
1919
namespace Microsoft.DotNet.XHarness.Apple;
2020

2121
public interface IRunOrchestrator
@@ -36,11 +36,11 @@ Task<ExitCode> OrchestrateRun(
3636
CancellationToken cancellationToken);
3737
}
3838

39-
/// <summary>
40-
/// This orchestrator implements the `run` command flow.
41-
/// In this flow we spawn the application and do not expect TestRunner inside.
42-
/// We only try to detect the exit code after the app run is finished.
43-
/// </summary>
39+
/// <summary>
40+
/// This orchestrator implements the `run` command flow.
41+
/// In this flow we spawn the application and do not expect TestRunner inside.
42+
/// We only try to detect the exit code after the app run is finished.
43+
/// </summary>
4444
public class RunOrchestrator : BaseOrchestrator, IRunOrchestrator
4545
{
4646
private readonly IiOSExitCodeDetector _iOSExitCodeDetector;
@@ -268,31 +268,39 @@ private ExitCode ParseResult(
268268
return ExitCode.APP_LAUNCH_FAILURE;
269269
}
270270

271-
int? exitCode;
272-
273-
var systemLog = _logs.FirstOrDefault(log => log.Description == LogType.SystemLog.ToString());
274-
if (systemLog == null)
271+
var logs = _logs.Where(log => log.Description == LogType.SystemLog.ToString() || log.Description == LogType.ApplicationLog.ToString()).ToList();
272+
if (!logs.Any())
275273
{
276274
_logger.LogError("Application has finished but no system log found. Failed to determine the exit code!");
277275
return ExitCode.RETURN_CODE_NOT_SET;
278276
}
279277

280-
try
281-
{
282-
exitCode = exitCodeDetector.DetectExitCode(appBundleInfo, systemLog);
283-
}
284-
catch (Exception e)
278+
int? exitCode = null;
279+
foreach (var log in logs)
285280
{
286-
_logger.LogError($"Failed to determine the exit code:{Environment.NewLine}{e}");
287-
return ExitCode.RETURN_CODE_NOT_SET;
281+
try
282+
{
283+
exitCode = exitCodeDetector.DetectExitCode(appBundleInfo, log);
284+
285+
if (exitCode.HasValue)
286+
{
287+
_logger.LogDebug($"Detected exit code {exitCode.Value} from {log.FullPath}");
288+
break;
289+
}
290+
291+
_logger.LogDebug($"Failed to determine the exit code from {log.FullPath}");
292+
}
293+
catch (Exception e)
294+
{
295+
_logger.LogDebug($"Failed to determine the exit code from {log.FullPath}:{Environment.NewLine}{e.Message}");
296+
}
288297
}
289298

290299
if (exitCode is null)
291300
{
292301
if (expectedExitCode != 0)
293302
{
294-
_logger.LogError("Application has finished but XHarness failed to determine its exit code! " +
295-
"This is a known issue, please run the app again.");
303+
_logger.LogError("Application has finished but XHarness failed to determine its exit code!");
296304
return ExitCode.RETURN_CODE_NOT_SET;
297305
}
298306

@@ -332,4 +340,4 @@ private ExitCode ParseResult(
332340

333341
return ExitCode.SUCCESS;
334342
}
335-
}
343+
}

0 commit comments

Comments
 (0)