Skip to content

Commit 2777994

Browse files
authored
Include installation time in --launch-timeout and create exit code for installation timeout (#720)
The --launch-timeout only started running after we found the device and installed the app. Timing out installation operations where then not timing out properly. Also adds a new exit code to distinguish failing installation and timing out installation.
1 parent 6ce7efe commit 2777994

File tree

10 files changed

+268
-193
lines changed

10 files changed

+268
-193
lines changed

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ protected virtual async Task<ExitCode> InstallApp(
256256

257257
try
258258
{
259-
result = await appInstaller.InstallApp(appBundleInfo, target, device, cancellationToken: cancellationToken);
259+
result = await appInstaller.InstallApp(appBundleInfo, target, device, cancellationToken);
260260
}
261261
catch (Exception e)
262262
{
@@ -266,6 +266,12 @@ protected virtual async Task<ExitCode> InstallApp(
266266

267267
if (!result.Succeeded)
268268
{
269+
if (cancellationToken.IsCancellationRequested)
270+
{
271+
_logger.LogError($"Application installation timed out");
272+
return ExitCode.PACKAGE_INSTALLATION_TIMEOUT;
273+
}
274+
269275
// use the knowledge base class to decide if the error is known, if it is, let the user know
270276
// the failure reason
271277
if (_errorKnowledgeBase.IsKnownInstallIssue(_mainLog, out var errorMessage))

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,14 @@ public async Task<ExitCode> OrchestrateInstall(
7474
Func<AppBundleInformation, IDevice, IDevice?, Task<ExitCode>> executeApp = (appBundleInfo, device, companionDevice)
7575
=> Task.FromResult(ExitCode.SUCCESS); // no-op
7676

77-
return await OrchestrateRun(target, deviceName, includeWirelessDevices, resetSimulator, enableLldb, appBundleInfo, executeMacCatalystApp, executeApp, cancellationToken);
77+
var result = await OrchestrateRun(target, deviceName, includeWirelessDevices, resetSimulator, enableLldb, appBundleInfo, executeMacCatalystApp, executeApp, cancellationToken);
78+
79+
if (cancellationToken.IsCancellationRequested)
80+
{
81+
return ExitCode.PACKAGE_INSTALLATION_TIMEOUT;
82+
}
83+
84+
return result;
7885
}
7986

8087
protected override Task CleanUpSimulators(IDevice device, IDevice? companionDevice)

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

Lines changed: 57 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System;
6-
using System.Collections.Generic;
6+
using System.Collections.Generic;
77
using System.Linq;
88
using System.Threading;
99
using System.Threading.Tasks;
@@ -26,6 +26,7 @@ Task<ExitCode> OrchestrateRun(
2626
TestTargetOs target,
2727
string? deviceName,
2828
TimeSpan timeout,
29+
TimeSpan launchTimeout,
2930
int expectedExitCode,
3031
bool includeWirelessDevices,
3132
bool resetSimulator,
@@ -81,11 +82,12 @@ public RunOrchestrator(
8182
logCallback);
8283
}
8384

84-
public Task<ExitCode> OrchestrateRun(
85+
public async Task<ExitCode> OrchestrateRun(
8586
AppBundleInformation appBundleInformation,
8687
TestTargetOs target,
8788
string? deviceName,
8889
TimeSpan timeout,
90+
TimeSpan launchTimeout,
8991
int expectedExitCode,
9092
bool includeWirelessDevices,
9193
bool resetSimulator,
@@ -95,30 +97,56 @@ public Task<ExitCode> OrchestrateRun(
9597
IEnumerable<string> passthroughArguments,
9698
CancellationToken cancellationToken)
9799
{
98-
Func<AppBundleInformation, Task<ExitCode>> executeMacCatalystApp = (appBundleInfo) =>
99-
ExecuteMacCatalystApp(
100-
appBundleInfo,
101-
timeout,
102-
expectedExitCode,
103-
signalAppEnd,
104-
environmentalVariables,
105-
passthroughArguments,
106-
cancellationToken);
107-
108-
Func<AppBundleInformation, IDevice, IDevice?, Task<ExitCode>> executeApp = (appBundleInfo, device, companionDevice) =>
109-
ExecuteApp(
110-
appBundleInfo,
111-
target,
112-
device,
113-
companionDevice,
114-
timeout,
115-
expectedExitCode,
116-
signalAppEnd,
117-
environmentalVariables,
118-
passthroughArguments,
119-
cancellationToken);
120-
121-
return OrchestrateRun(
100+
// The --launch-timeout option must start counting now and not complete before we start running tests to succeed.
101+
// After then, this timeout must not interfere.
102+
// Tests start running inside of ExecuteApp() which means we have to time-constrain all operations happening inside
103+
// OrchestrateRun() that happen before we start the app execution.
104+
// We will achieve this by sending a special cancellation token to OrchestrateRun() and only cancel if it in case
105+
// we didn't manage to start the app run until then.
106+
using var launchTimeoutCancellation = new CancellationTokenSource();
107+
var appRunStarted = false;
108+
var task = Task.Delay(launchTimeout < timeout ? launchTimeout : timeout).ContinueWith(t =>
109+
{
110+
if (!appRunStarted)
111+
{
112+
launchTimeoutCancellation.Cancel();
113+
}
114+
});
115+
116+
using var launchTimeoutCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(
117+
launchTimeoutCancellation.Token,
118+
cancellationToken);
119+
120+
Func<AppBundleInformation, Task<ExitCode>> executeMacCatalystApp = (appBundleInfo) =>
121+
{
122+
appRunStarted = true;
123+
return ExecuteMacCatalystApp(
124+
appBundleInfo,
125+
timeout,
126+
expectedExitCode,
127+
signalAppEnd,
128+
environmentalVariables,
129+
passthroughArguments,
130+
cancellationToken);
131+
};
132+
133+
Func<AppBundleInformation, IDevice, IDevice?, Task<ExitCode>> executeApp = (appBundleInfo, device, companionDevice) =>
134+
{
135+
appRunStarted = true;
136+
return ExecuteApp(
137+
appBundleInfo,
138+
target,
139+
device,
140+
companionDevice,
141+
timeout,
142+
expectedExitCode,
143+
signalAppEnd,
144+
environmentalVariables,
145+
passthroughArguments,
146+
cancellationToken);
147+
};
148+
149+
return await OrchestrateRun(
122150
target,
123151
deviceName,
124152
includeWirelessDevices,
@@ -127,7 +155,7 @@ public Task<ExitCode> OrchestrateRun(
127155
appBundleInformation,
128156
executeMacCatalystApp,
129157
executeApp,
130-
cancellationToken);
158+
launchTimeoutCancellationToken.Token);
131159
}
132160

133161
private async Task<ExitCode> ExecuteApp(
@@ -148,7 +176,7 @@ private async Task<ExitCode> ExecuteApp(
148176
_logger.LogWarning("The --signal-app-end option is used for device tests and has no effect on simulators");
149177
}
150178

151-
_logger.LogInformation($"Starting application '{appBundleInfo.AppName}' on '{device.Name}'");
179+
_logger.LogInformation($"Starting application '{appBundleInfo.AppName}' on '{device.Name}'");
152180

153181
ProcessExecutionResult result = await _appRunner.RunApp(
154182
appBundleInfo,
@@ -179,7 +207,7 @@ private async Task<ExitCode> ExecuteMacCatalystApp(
179207
IEnumerable<string> passthroughArguments,
180208
CancellationToken cancellationToken)
181209
{
182-
_logger.LogInformation($"Starting '{appBundleInfo.AppName}' on MacCatalyst");
210+
_logger.LogInformation($"Starting '{appBundleInfo.AppName}' on MacCatalyst");
183211

184212
ProcessExecutionResult result = await _appRunner.RunMacCatalystApp(
185213
appBundleInfo,

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

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

55
using System;
66
using System.Collections.Generic;
7+
using System.Diagnostics;
78
using System.Linq;
89
using System.Threading;
910
using System.Threading.Tasks;
@@ -70,7 +71,7 @@ public TestOrchestrator(
7071
_errorKnowledgeBase = errorKnowledgeBase ?? throw new ArgumentNullException(nameof(errorKnowledgeBase));
7172
}
7273

73-
public Task<ExitCode> OrchestrateTest(
74+
public async Task<ExitCode> OrchestrateTest(
7475
AppBundleInformation appBundleInformation,
7576
TestTargetOs target,
7677
string? deviceName,
@@ -87,39 +88,68 @@ public Task<ExitCode> OrchestrateTest(
8788
IReadOnlyCollection<(string, string)> environmentalVariables,
8889
IEnumerable<string> passthroughArguments,
8990
CancellationToken cancellationToken)
90-
{
91-
Func<AppBundleInformation, Task<ExitCode>> executeMacCatalystApp = (appBundleInfo) =>
92-
ExecuteMacCatalystApp(
93-
appBundleInfo,
94-
timeout,
95-
launchTimeout,
96-
communicationChannel,
97-
xmlResultJargon,
98-
singleMethodFilters,
99-
classMethodFilters,
100-
environmentalVariables,
101-
passthroughArguments,
102-
signalAppEnd,
103-
cancellationToken);
91+
{
92+
// The --launch-timeout option must start counting now and not complete before we start running tests to succeed.
93+
// After then, this timeout must not interfere.
94+
// Tests start running inside of ExecuteApp() which means we have to time-constrain all operations happening inside
95+
// OrchestrateRun() that happen before we start the app execution.
96+
// We will achieve this by sending a special cancellation token to OrchestrateRun() and only cancel if it in case
97+
// we didn't manage to start the app run until then.
98+
using var launchTimeoutCancellation = new CancellationTokenSource();
99+
var appRunStarted = false;
100+
var task = Task.Delay(launchTimeout < timeout ? launchTimeout : timeout).ContinueWith(t =>
101+
{
102+
if (!appRunStarted)
103+
{
104+
launchTimeoutCancellation.Cancel();
105+
}
106+
});
107+
108+
// We also want to shrink the launch timeout by whatever elapsed before we get to ExecuteApp
109+
var watch = Stopwatch.StartNew();
110+
111+
using var launchTimeoutCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(
112+
launchTimeoutCancellation.Token,
113+
cancellationToken);
114+
115+
Func<AppBundleInformation, Task<ExitCode>> executeMacCatalystApp = (appBundleInfo) =>
116+
{
117+
appRunStarted = true;
118+
return ExecuteMacCatalystApp(
119+
appBundleInfo,
120+
timeout,
121+
launchTimeout - watch.Elapsed,
122+
communicationChannel,
123+
xmlResultJargon,
124+
singleMethodFilters,
125+
classMethodFilters,
126+
environmentalVariables,
127+
passthroughArguments,
128+
signalAppEnd,
129+
cancellationToken);
130+
};
104131

105-
Func<AppBundleInformation, IDevice, IDevice?, Task<ExitCode>> executeApp = (appBundleInfo, device, companionDevice) =>
106-
ExecuteApp(
107-
appBundleInfo,
108-
target,
109-
device,
110-
companionDevice,
111-
timeout,
112-
launchTimeout,
113-
communicationChannel,
114-
xmlResultJargon,
115-
singleMethodFilters,
116-
classMethodFilters,
117-
environmentalVariables,
118-
passthroughArguments,
119-
signalAppEnd,
120-
cancellationToken);
132+
Func<AppBundleInformation, IDevice, IDevice?, Task<ExitCode>> executeApp = (appBundleInfo, device, companionDevice) =>
133+
{
134+
appRunStarted = true;
135+
return ExecuteApp(
136+
appBundleInfo,
137+
target,
138+
device,
139+
companionDevice,
140+
timeout,
141+
launchTimeout - watch.Elapsed,
142+
communicationChannel,
143+
xmlResultJargon,
144+
singleMethodFilters,
145+
classMethodFilters,
146+
environmentalVariables,
147+
passthroughArguments,
148+
signalAppEnd,
149+
cancellationToken);
150+
};
121151

122-
return OrchestrateRun(
152+
return await OrchestrateRun(
123153
target,
124154
deviceName,
125155
includeWirelessDevices,
@@ -128,7 +158,7 @@ public Task<ExitCode> OrchestrateTest(
128158
appBundleInformation,
129159
executeMacCatalystApp,
130160
executeApp,
131-
cancellationToken);
161+
launchTimeoutCancellationToken.Token);
132162
}
133163

134164
private async Task<ExitCode> ExecuteApp(
Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
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;
6-
7-
namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Android
8-
{
9-
/// <summary>
10-
/// Time to wait for boot completion.
11-
/// </summary>
12-
internal class LaunchTimeoutArgument : TimeSpanArgument
13-
{
14-
public LaunchTimeoutArgument(TimeSpan defaultValue)
15-
: base("launch-timeout=|lt=", "Time span in the form of \"00:00:00\" or number of seconds to wait for the device to boot to complete", defaultValue)
16-
{
17-
}
18-
}
19-
}
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;
6+
7+
namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Android
8+
{
9+
/// <summary>
10+
/// Time to wait for boot completion.
11+
/// </summary>
12+
internal class LaunchTimeoutArgument : TimeSpanArgument
13+
{
14+
public LaunchTimeoutArgument(TimeSpan defaultValue)
15+
: base("launch-timeout=|lt=", $"Time span in the form of \"00:00:00\" or number of seconds to wait for the device to boot to complete. Default is {defaultValue}", defaultValue)
16+
{
17+
}
18+
}
19+
}

0 commit comments

Comments
 (0)