Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions scripts/run-with-timeout/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# run-with-timeout

This script runs an external process with a few extra features:

* The process will be killed if it takes longer than the specified timeout.
* The process will be killed if it doesn't launch within 10 seconds. This is
implemented by passing the environment variable `LAUNCH_SENTINEL_FILE` (with
a path to a file) to the process, which has 10 seconds to create said file.
* If the launch timed out, the process will be relaunched, up to a maximum of
10 attempts.

The intented use case for this tool is to run tests, but with a timeout if the
tests hang. Also re-launch automatically if the test run doesn't successfully
start (this seems to happen randomly for Mac Catalyst sometimes).

Syntax:

```shell
run-with-timeout timeoutInSeconds command cmdarg1 cmdarg2 ...
```
2 changes: 2 additions & 0 deletions scripts/run-with-timeout/fragment.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
include $(TOP)/scripts/template.mk
$(eval $(call TemplateScript,RUN_WITH_TIMEOUT,run-with-timeout))
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#!/usr/bin/env /Library/Frameworks/Mono.framework/Commands/csharp -s

// arguments are: <platform> <outputPath>

using System.Diagnostics;
Expand All @@ -8,45 +6,9 @@
using System.Threading;
using System.Xml;

static class NativeMethods {
[DllImport ("__Internal", SetLastError = true)]
static extern int kill (int pid, int signal);

public static void Abort (this Process process)
{
var exitTimeout = TimeSpan.FromSeconds (60);
var pid = process.Id;
Console.WriteLine ($"kill ({pid}, 6);");
var rv = kill (pid, 6 /* SIGABRT - this triggers a crash report */);
if (rv != 0) {
// This might randomly happen, because there's a race condition here: we waited for the process to exit,
// the timeout occurred so we decided to kill the process, and *then* the process exited, before we got
// around to kill it. In that case, the kill call would fail.
Console.WriteLine ($"Failed to execute 'kill -6 {pid}'. errno = {Marshal.GetLastWin32Error ()} - process already exited?");
return;
}
var watch = Stopwatch.StartNew ();
while (watch.Elapsed < exitTimeout) {
Console.WriteLine ($"kill ({pid}, 0);");
rv = kill (pid, 0); // check if pid is still alive (valid)
if (rv != 0) {
// Nope it's not, so it must have terminated.
return;
}
Thread.Sleep (50);
}

// Send SIGKILL - time to finish it off.
Console.WriteLine ($"kill ({pid}, 9);");
kill (pid, 9);
}
}

var args = Args;
if (args.Length <= 1) {
Console.WriteLine ($"Need two arguments (the timeout + the command to launch), got {args.Length} argument(s)");
Environment.Exit (1);
return;
return 1;
}

var launchTimeout = TimeSpan.FromSeconds (10); // must launch within a few seconds.
Expand Down Expand Up @@ -109,4 +71,38 @@ public static void Abort (this Process process)
break;
}

Environment.Exit (exitCode);
return exitCode;

static class NativeMethods {
[DllImport ("__Internal", SetLastError = true)]
static extern int kill (int pid, int signal);

public static void Abort (this Process process)
{
var exitTimeout = TimeSpan.FromSeconds (60);
var pid = process.Id;
Console.WriteLine ($"kill ({pid}, 6);");
var rv = kill (pid, 6 /* SIGABRT - this triggers a crash report */);
if (rv != 0) {
// This might randomly happen, because there's a race condition here: we waited for the process to exit,
// the timeout occurred so we decided to kill the process, and *then* the process exited, before we got
// around to kill it. In that case, the kill call would fail.
Console.WriteLine ($"Failed to execute 'kill -6 {pid}'. errno = {Marshal.GetLastWin32Error ()} - process already exited?");
return;
}
var watch = Stopwatch.StartNew ();
while (watch.Elapsed < exitTimeout) {
Console.WriteLine ($"kill ({pid}, 0);");
rv = kill (pid, 0); // check if pid is still alive (valid)
if (rv != 0) {
// Nope it's not, so it must have terminated.
return;
}
Thread.Sleep (50);
}

// Send SIGKILL - time to finish it off.
Console.WriteLine ($"kill ({pid}, 9);");
kill (pid, 9);
}
}
5 changes: 5 additions & 0 deletions scripts/run-with-timeout/run-with-timeout.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net$(BundledNETCoreAppTargetFrameworkVersion)</TargetFramework>
</PropertyGroup>
</Project>
6 changes: 6 additions & 0 deletions tests/package-mac-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,11 @@ $CP -p ../mk/subdirs.mk "$DIR/mk"
$CP -p ../mk/rules.mk "$DIR/mk"
$CP -p ../mk/quiet.mk "$DIR/mk"
$CP -p ../mk/mono.mk "$DIR/mk"
$CP -cp ../Directory.Build.props "$DIR/"
mkdir -p "$DIR/scripts/run-with-timeout"
$CP -cp ../scripts/Directory.Build.props "$DIR/scripts/"
$CP -cp ../scripts/*.mk "$DIR/scripts/"
$CP -cp ../scripts/run-with-timeout/*.cs* "$DIR/scripts/run-with-timeout/"
$CP -cp ../scripts/run-with-timeout/*.mk "$DIR/scripts/run-with-timeout/"

cd mac-test-package && 7z a ../mac-test-package.7z ./*
40 changes: 22 additions & 18 deletions tests/packaged-macos-tests.mk
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ TOP=..

include $(TOP)/Make.config

export DOTNET=$(shell which dotnet)

ifeq ($(shell arch),"arm64")
IS_ARM64=1
IS_APPLE_SILICON=1
Expand All @@ -14,10 +16,12 @@ endif
CONFIG?=Debug
LAUNCH_ARGUMENTS=--autostart --autoexit

include $(TOP)/scripts/run-with-timeout/fragment.mk

# Time test runs out after 5 minutes (300 seconds)
RUN_WITH_TIMEOUT=./run-with-timeout.csharp 300
LAUNCH_WITH_TIMEOUT=$(RUN_WITH_TIMEOUT_EXEC) 300
# Some tests need a bit more time... (introspection, monotouch-test)
RUN_WITH_TIMEOUT_LONGER=./run-with-timeout.csharp 600
LAUNCH_WITH_TIMEOUT_LONGER=$(RUN_WITH_TIMEOUT_EXEC) 600

### .NET dependency projects

Expand Down Expand Up @@ -48,18 +52,18 @@ define DotNetNormalTest
build-mac-dotnet-x64-$(1): .stamp-dotnet-dependency-macOS
$$(Q) $$(MAKE) -C "$(1)/dotnet/macOS" build BUILD_ARGUMENTS=/p:RuntimeIdentifier=osx-x64

exec-mac-dotnet-x64-$(1):
exec-mac-dotnet-x64-$(1): $(RUN_WITH_TIMEOUT)
@echo "ℹ️ Executing the '$(1)' test for macOS/.NET (x64) ℹ️"
$$(Q) $(RUN_WITH_TIMEOUT$(3)) "./$(1)/dotnet/macOS/bin/$(CONFIG)/$(DOTNET_TFM)-macos/osx-x64/$(2).app/Contents/MacOS/$(2)"
$$(Q) $(LAUNCH_WITH_TIMEOUT$(3)) "./$(1)/dotnet/macOS/bin/$(CONFIG)/$(DOTNET_TFM)-macos/osx-x64/$(2).app/Contents/MacOS/$(2)"

# macOS/.NET/arm64
build-mac-dotnet-arm64-$(1): .stamp-dotnet-dependency-macOS
$$(Q) $$(MAKE) -C "$(1)/dotnet/macOS" build BUILD_ARGUMENTS=/p:RuntimeIdentifier=osx-arm64

exec-mac-dotnet-arm64-$(1):
exec-mac-dotnet-arm64-$(1): $(RUN_WITH_TIMEOUT)
ifeq ($(IS_APPLE_SILICON),1)
@echo "ℹ️ Executing the '$(1)' test for macOS/.NET (arm64) ℹ️"
$$(Q) $(RUN_WITH_TIMEOUT$(3)) "./$(1)/dotnet/macOS/bin/$(CONFIG)/$(DOTNET_TFM)-macos/osx-arm64/$(2).app/Contents/MacOS/$(2)"
$$(Q) $(LAUNCH_WITH_TIMEOUT$(3)) "./$(1)/dotnet/macOS/bin/$(CONFIG)/$(DOTNET_TFM)-macos/osx-arm64/$(2).app/Contents/MacOS/$(2)"
else
@echo "⚠️ Not executing the '$(1)' test for macOS/.NET (arm64) - not executing on Apple Silicon ⚠️"
endif
Expand All @@ -68,18 +72,18 @@ endif
build-maccatalyst-dotnet-x64-$(1): .stamp-dotnet-dependency-MacCatalyst
$$(Q_BUILD) $$(MAKE) -C "$(1)/dotnet/MacCatalyst" build BUILD_ARGUMENTS=/p:RuntimeIdentifier=maccatalyst-x64

exec-maccatalyst-dotnet-x64-$(1):
exec-maccatalyst-dotnet-x64-$(1): $(RUN_WITH_TIMEOUT)
@echo "ℹ️ Executing the '$(1)' test for Mac Catalyst/.NET (x64) ℹ️"
$$(Q) $(RUN_WITH_TIMEOUT$(3)) "./$(1)/dotnet/MacCatalyst/bin/$(CONFIG)/$(DOTNET_TFM)-maccatalyst/maccatalyst-x64/$(2).app/Contents/MacOS/$(2)" $(LAUNCH_ARGUMENTS)
$$(Q) $(LAUNCH_WITH_TIMEOUT$(3)) "./$(1)/dotnet/MacCatalyst/bin/$(CONFIG)/$(DOTNET_TFM)-maccatalyst/maccatalyst-x64/$(2).app/Contents/MacOS/$(2)" $(LAUNCH_ARGUMENTS)

# MacCatalyst/.NET/arm64
build-maccatalyst-dotnet-arm64-$(1):.stamp-dotnet-dependency-MacCatalyst
$$(Q) $$(MAKE) -C "$(1)/dotnet/MacCatalyst" build BUILD_ARGUMENTS=/p:RuntimeIdentifier=maccatalyst-arm64

exec-maccatalyst-dotnet-arm64-$(1):
exec-maccatalyst-dotnet-arm64-$(1): $(RUN_WITH_TIMEOUT)
ifeq ($(IS_APPLE_SILICON),1)
@echo "ℹ️ Executing the '$(1)' test for Mac Catalyst/.NET (arm64) ℹ️"
$$(Q) $(RUN_WITH_TIMEOUT$(3)) "./$(1)/dotnet/MacCatalyst/bin/$(CONFIG)/$(DOTNET_TFM)-maccatalyst/maccatalyst-arm64/$(2).app/Contents/MacOS/$(2)" $(LAUNCH_ARGUMENTS)
$$(Q) $(LAUNCH_WITH_TIMEOUT$(3)) "./$(1)/dotnet/MacCatalyst/bin/$(CONFIG)/$(DOTNET_TFM)-maccatalyst/maccatalyst-arm64/$(2).app/Contents/MacOS/$(2)" $(LAUNCH_ARGUMENTS)
else
@echo "⚠️ Not executing the '$(1)' test for Mac Catalyst/.NET (arm64) - not executing on Apple Silicon ⚠️"
endif
Expand Down Expand Up @@ -119,18 +123,18 @@ define DotNetLinkerTest
build-mac-dotnet-x64-$(1): .stamp-dotnet-dependency-macOS
$$(Q_BUILD) $$(MAKE) -C "linker/ios/$(2)/dotnet/macOS" build BUILD_ARGUMENTS=/p:RuntimeIdentifier=osx-x64

exec-mac-dotnet-x64-$(1):
exec-mac-dotnet-x64-$(1): $(RUN_WITH_TIMEOUT)
@echo "ℹ️ Executing the '$(2)' test for macOS/.NET (x64) ℹ️"
$$(Q) $(RUN_WITH_TIMEOUT) "./linker/ios/$(2)/dotnet/macOS/bin/$(CONFIG)/$(DOTNET_TFM)-macos/osx-x64/$(2).app/Contents/MacOS/$(2)"
$$(Q) $(LAUNCH_WITH_TIMEOUT) "./linker/ios/$(2)/dotnet/macOS/bin/$(CONFIG)/$(DOTNET_TFM)-macos/osx-x64/$(2).app/Contents/MacOS/$(2)"

# macOS/.NET/arm64
build-mac-dotnet-arm64-$(1): .stamp-dotnet-dependency-macOS
$$(Q) $$(MAKE) -C "linker/ios/$(2)/dotnet/macOS" build BUILD_ARGUMENTS=/p:RuntimeIdentifier=osx-arm64

exec-mac-dotnet-arm64-$(1):
exec-mac-dotnet-arm64-$(1): $(RUN_WITH_TIMEOUT)
ifeq ($(IS_APPLE_SILICON),1)
@echo "ℹ️ Executing the '$(2)' test for macOS/.NET (arm64) ℹ️"
$$(Q) $(RUN_WITH_TIMEOUT) "./linker/ios/$(2)/dotnet/macOS/bin/$(CONFIG)/$(DOTNET_TFM)-macos/osx-arm64/$(2).app/Contents/MacOS/$(2)"
$$(Q) $(LAUNCH_WITH_TIMEOUT) "./linker/ios/$(2)/dotnet/macOS/bin/$(CONFIG)/$(DOTNET_TFM)-macos/osx-arm64/$(2).app/Contents/MacOS/$(2)"
else
@echo "⚠️ Not executing the '$(2)' test for macOS/.NET (arm64) - not executing on Apple Silicon ⚠️"
endif
Expand All @@ -139,18 +143,18 @@ endif
build-maccatalyst-dotnet-x64-$(1): .stamp-dotnet-dependency-MacCatalyst
$$(Q_BUILD) $$(MAKE) -C "linker/ios/$(2)/dotnet/MacCatalyst" build BUILD_ARGUMENTS=/p:RuntimeIdentifier=maccatalyst-x64

exec-maccatalyst-dotnet-x64-$(1):
exec-maccatalyst-dotnet-x64-$(1): $(RUN_WITH_TIMEOUT)
@echo "ℹ️ Executing the '$(2)' test for Mac Catalyst/.NET (x64) ℹ️"
$$(Q) $(RUN_WITH_TIMEOUT) "./linker/ios/$(2)/dotnet/MacCatalyst/bin/$(CONFIG)/$(DOTNET_TFM)-maccatalyst/maccatalyst-x64/$(2).app/Contents/MacOS/$(2)" $(LAUNCH_ARGUMENTS)
$$(Q) $(LAUNCH_WITH_TIMEOUT) "./linker/ios/$(2)/dotnet/MacCatalyst/bin/$(CONFIG)/$(DOTNET_TFM)-maccatalyst/maccatalyst-x64/$(2).app/Contents/MacOS/$(2)" $(LAUNCH_ARGUMENTS)

# MacCatalyst/.NET/arm64
build-maccatalyst-dotnet-arm64-$(1): .stamp-dotnet-dependency-MacCatalyst
$$(Q) $$(MAKE) -C "linker/ios/$(2)/dotnet/MacCatalyst" build BUILD_ARGUMENTS=/p:RuntimeIdentifier=maccatalyst-arm64

exec-maccatalyst-dotnet-arm64-$(1):
exec-maccatalyst-dotnet-arm64-$(1): $(RUN_WITH_TIMEOUT)
ifeq ($(IS_APPLE_SILICON),1)
@echo "ℹ️ Executing the '$(2)' test for Mac Catalyst/.NET (arm64) ℹ️"
$$(Q) $(RUN_WITH_TIMEOUT) "./linker/ios/$(2)/dotnet/MacCatalyst/bin/$(CONFIG)/$(DOTNET_TFM)-maccatalyst/maccatalyst-arm64/$(2).app/Contents/MacOS/$(2)" $(LAUNCH_ARGUMENTS)
$$(Q) $(LAUNCH_WITH_TIMEOUT) "./linker/ios/$(2)/dotnet/MacCatalyst/bin/$(CONFIG)/$(DOTNET_TFM)-maccatalyst/maccatalyst-arm64/$(2).app/Contents/MacOS/$(2)" $(LAUNCH_ARGUMENTS)
else
@echo "⚠️ Not executing the '$(2)' test for Mac Catalyst/.NET (arm64) - not executing on Apple Silicon ⚠️"
endif
Expand Down
4 changes: 4 additions & 0 deletions tools/devops/automation/templates/mac/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,10 @@ steps:
displayName: Install dependencies.
timeoutInMinutes: 60

- task: UseDotNet@2
inputs:
version: 8.x

- pwsh: >-
$(System.DefaultWorkingDirectory)/xamarin-macios/tools/devops/automation/scripts/run_mac_tests.ps1
-GithubToken $(GitHub.Token)
Expand Down