diff --git a/scripts/run-audio-unit-extension-tests/README.md b/scripts/run-audio-unit-extension-tests/README.md new file mode 100644 index 000000000000..20ab4feae288 --- /dev/null +++ b/scripts/run-audio-unit-extension-tests/README.md @@ -0,0 +1,13 @@ + + +# run-audio-unit-extension-tests + +Runs the `monotouch-test` audio-unit app extension from the command line. + +It handles: + +* registering the host app and `.appex`, +* wiring the `test.name` and `log.file` NSUserDefaults used by `TouchOptions`, +* launching the container host, +* tracking the spawned AppExtension PID so the run can be timed out cleanly, and +* validating that the extension reached real NUnit execution. diff --git a/scripts/run-audio-unit-extension-tests/fragment.mk b/scripts/run-audio-unit-extension-tests/fragment.mk new file mode 100644 index 000000000000..d9b3838397ed --- /dev/null +++ b/scripts/run-audio-unit-extension-tests/fragment.mk @@ -0,0 +1,5 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +include $(TOP)/scripts/template.mk +$(eval $(call TemplateScript,RUN_AUDIO_UNIT_EXTENSION_TESTS,run-audio-unit-extension-tests)) diff --git a/scripts/run-audio-unit-extension-tests/run-audio-unit-extension-tests.cs b/scripts/run-audio-unit-extension-tests/run-audio-unit-extension-tests.cs new file mode 100644 index 000000000000..feeff62f048c --- /dev/null +++ b/scripts/run-audio-unit-extension-tests/run-audio-unit-extension-tests.cs @@ -0,0 +1,394 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; + +using Xamarin.Utils; + +if (!OperatingSystem.IsMacOS ()) { + Console.Error.WriteLine ("This script only supports macOS hosts."); + return 1; +} + +Options options; +try { + options = Options.Parse (args); +} catch (Exception ex) { + Console.Error.WriteLine (ex.Message); + PrintUsage (); + return 1; +} + +if (options.ShowHelp) { + PrintUsage (); + return 0; +} + +var runner = new AudioUnitExtensionTestRunner (options); +return await runner.RunAsync (); + +static void PrintUsage () +{ + Console.WriteLine ("Usage:"); + Console.WriteLine (" run-audio-unit-extension-tests --platform --rid --config --app --extension --executable --log-file --timeout-seconds [--test-filter ] [--lsregister ]"); +} + +sealed class AudioUnitExtensionTestRunner { + const string BundleIdentifier = "com.xamarin.monotouch-test.AudioUnitExtension"; + + // Predicate used to capture the system log for diagnostic purposes only. The + // actual test results are streamed back over a TCP connection (see below). + const string LogPredicate = "process == \"monotouchtest\" OR process == \"ContainerApp\" OR eventMessage CONTAINS[c] \"monotouch-test-audio-unit\""; + + const string EndMarker = ""; + + static readonly Regex TestResultsTagRegex = new ("]*>", RegexOptions.Compiled); + + readonly Options options; + readonly object logLock = new (); + + string ResultsFilePath { + get { + var directory = Path.GetDirectoryName (options.LogFilePath)!; + var name = Path.GetFileNameWithoutExtension (options.LogFilePath); + return Path.Combine (directory, name + ".nunit-results.xml"); + } + } + + public AudioUnitExtensionTestRunner (Options options) + { + this.options = options; + } + + public async Task RunAsync () + { + Directory.CreateDirectory (Path.GetDirectoryName (options.LogFilePath)!); + File.WriteAllText (options.LogFilePath, ""); + + Log ($"Platform: {options.Platform}"); + Log ($"RID: {options.Rid}"); + Log ($"Config: {options.Config}"); + Log ($"App: {options.AppPath}"); + Log ($"Extension: {options.ExtensionPath}"); + Log ($"Log file: {options.LogFilePath}"); + if (!string.IsNullOrEmpty (options.TestFilter)) + Log ($"Test filter: {options.TestFilter}"); + Log (""); + + var exitCode = 0; + var logStart = DateTime.Now; + + // Listen on a free localhost port. The extension connects back to this + // port and streams the NUnit XML result (see Touch.Client's TouchOptions + // / TouchRunner, which read the network configuration from NSUserDefaults). + var listener = new TcpListener (IPAddress.Loopback, 0); + listener.Start (); + var port = ((IPEndPoint) listener.LocalEndpoint).Port; + Log ($"Listening for test results on 127.0.0.1:{port}."); + + using var hostCts = new CancellationTokenSource (); + Task? hostTask = null; + + try { + await ConfigureDefaultsAsync (port); + + await RunToolAsync (options.LsRegisterPath, "-f", options.AppPath); + Log (""); + await RunToolAsync ("pluginkit", "-a", options.ExtensionPath); + Log (""); + + hostTask = StartHost (hostCts.Token); + + var (result, timedOut) = await ReceiveResultsAsync (listener); + + if (timedOut) { + Log ($"Timed out waiting for the extension test results after {options.Timeout.TotalMinutes:0} minutes."); + exitCode = 1; + } else { + exitCode = Math.Max (exitCode, ProcessResults (result)); + } + } catch (Exception ex) { + Log (ex.ToString ()); + exitCode = 1; + } finally { + listener.Stop (); + + // Stop the container host so its process (and the extension) can exit. + hostCts.Cancel (); + if (hostTask is not null) { + try { + await hostTask; + } catch { + } + } + + await CleanupDefaultsAsync (); + + var logEnd = DateTime.Now; + Log (""); + Log ("System log (diagnostics):"); + await CaptureSystemLogAsync (logStart, logEnd); + } + + return exitCode; + } + + async Task ConfigureDefaultsAsync (int port) + { + await RunToolAsync ("defaults", "write", BundleIdentifier, "network.enabled", "-bool", "YES"); + await RunToolAsync ("defaults", "write", BundleIdentifier, "network.host.name", "-string", "127.0.0.1"); + await RunToolAsync ("defaults", "write", BundleIdentifier, "network.host.port", "-int", port.ToString (CultureInfo.InvariantCulture)); + await RunToolAsync ("defaults", "write", BundleIdentifier, "network.transport", "-string", "TCP"); + await RunToolAsync ("defaults", "write", BundleIdentifier, "execution.usetcptunnel", "-bool", "NO"); + await RunToolAsync ("defaults", "write", BundleIdentifier, "xml.enabled", "-bool", "YES"); + + if (string.IsNullOrEmpty (options.TestFilter)) { + await RunBestEffortAsync ("defaults", "delete", BundleIdentifier, "test.name"); + } else { + await RunToolAsync ("defaults", "write", BundleIdentifier, "test.name", "-string", options.TestFilter); + } + Log (""); + } + + async Task CleanupDefaultsAsync () + { + foreach (var key in new [] { "network.enabled", "network.host.name", "network.host.port", "network.transport", "execution.usetcptunnel", "xml.enabled", "test.name" }) + await RunBestEffortAsync ("defaults", "delete", BundleIdentifier, key); + } + + Task StartHost (CancellationToken cancellationToken) + { + var environment = new Dictionary { + ["RUN_EXTENSION_TESTS"] = "1", + }; + Log ($"Executing: RUN_EXTENSION_TESTS=1 {options.ExecutablePath}"); + return Execution.RunWithCallbacksAsync ( + options.ExecutablePath, + new List (), + environment: environment, + standardOutput: Log, + standardError: Log, + cancellationToken: cancellationToken); + } + + async Task<(string Result, bool TimedOut)> ReceiveResultsAsync (TcpListener listener) + { + using var timeoutCts = new CancellationTokenSource (options.Timeout); + + TcpClient client; + try { + client = await listener.AcceptTcpClientAsync (timeoutCts.Token); + } catch (OperationCanceledException) { + Log ("The extension never connected to report test results."); + return ("", true); + } + + Log ("The extension connected; reading test results."); + + var payload = new StringBuilder (); + var timedOut = false; + var gotEnd = false; + + using (client) + using (var stream = client.GetStream ()) + using (var reader = new StreamReader (stream, Encoding.UTF8)) { + while (true) { + string? line; + try { + line = await reader.ReadLineAsync (timeoutCts.Token); + } catch (OperationCanceledException) { + timedOut = true; + break; + } + + if (line is null) + break; + + payload.AppendLine (line); + AppendToLogFile (line); + + if (line.Contains (EndMarker, StringComparison.Ordinal)) { + gotEnd = true; + break; + } + } + } + + if (gotEnd) + Log ("Received the end-of-results marker."); + else if (!timedOut) + Log ("The extension disconnected before sending the end-of-results marker."); + + return (payload.ToString (), timedOut); + } + + int ProcessResults (string payload) + { + if (string.IsNullOrWhiteSpace (payload)) { + Log ("Did not receive any test results from the extension."); + return 1; + } + + // Persist the NUnit XML result (everything up to and including + // ) for consumption by CI. + var endTag = ""; + var endIndex = payload.IndexOf (endTag, StringComparison.Ordinal); + var xml = endIndex >= 0 ? payload.Substring (0, endIndex + endTag.Length) : payload; + File.WriteAllText (ResultsFilePath, xml); + Log ($"Wrote NUnit results to: {ResultsFilePath}"); + + var tagMatch = TestResultsTagRegex.Match (payload); + if (!tagMatch.Success) { + Log ("Did not find an NUnit element in the test output."); + return 1; + } + + var tag = tagMatch.Value; + var total = GetAttribute (tag, "total"); + var errors = GetAttribute (tag, "errors"); + var failures = GetAttribute (tag, "failures"); + var notRun = GetAttribute (tag, "not-run"); + var inconclusive = GetAttribute (tag, "inconclusive"); + var ignored = GetAttribute (tag, "ignored"); + + Log ($"Tests run: {total} Failures: {failures} Errors: {errors} Not-run: {notRun} Inconclusive: {inconclusive} Ignored: {ignored}"); + + if (total <= 0) { + Log ("The extension did not execute any tests."); + return 1; + } + + if (failures > 0 || errors > 0) { + Log ($"❌ Extension test run failed ({failures} failures, {errors} errors)."); + return 1; + } + + Log ("✅ Extension test run succeeded"); + return 0; + } + + static int GetAttribute (string tag, string name) + { + var match = Regex.Match (tag, name + "=\"(\\d+)\""); + return match.Success ? int.Parse (match.Groups [1].Value, CultureInfo.InvariantCulture) : -1; + } + + async Task CaptureSystemLogAsync (DateTime start, DateTime end) + { + Log ($"Executing: log show --style compact --predicate {LogPredicate} --start {FormatTimestamp (start)} --end {FormatTimestamp (end)}"); + var execution = await Execution.RunWithCallbacksAsync ( + "log", + new List { "show", "--style", "compact", "--predicate", LogPredicate, "--start", FormatTimestamp (start), "--end", FormatTimestamp (end) }, + standardOutput: AppendToLogFile, + standardError: AppendToLogFile); + if (execution.ExitCode != 0) + Log ($"'log show' exited with code {execution.ExitCode}."); + } + + async Task RunToolAsync (string fileName, params string [] arguments) + { + Log ($"Executing: {StringUtils.FormatArguments (Prepend (fileName, arguments))}"); + var execution = await Execution.RunWithCallbacksAsync (fileName, arguments, standardOutput: AppendToLogFile, standardError: AppendToLogFile); + if (execution.ExitCode != 0) + throw new InvalidOperationException ($"'{fileName}' exited with code {execution.ExitCode}."); + } + + async Task RunBestEffortAsync (string fileName, params string [] arguments) + { + await Execution.RunWithCallbacksAsync (fileName, arguments, standardOutput: AppendToLogFile, standardError: AppendToLogFile); + } + + static IList Prepend (string fileName, string [] arguments) + { + var list = new List (arguments.Length + 1) { fileName }; + list.AddRange (arguments); + return list; + } + + void Log (string line) + { + lock (logLock) { + File.AppendAllText (options.LogFilePath, line + Environment.NewLine); + Console.WriteLine (line); + } + } + + void AppendToLogFile (string line) + { + lock (logLock) { + File.AppendAllText (options.LogFilePath, line + Environment.NewLine); + } + } + + static string FormatTimestamp (DateTime timestamp) + => timestamp.ToString ("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture); +} + +sealed class Options { + public bool ShowHelp { get; private init; } + public string Platform { get; private init; } = ""; + public string Rid { get; private init; } = ""; + public string Config { get; private init; } = ""; + public string AppPath { get; private init; } = ""; + public string ExtensionPath { get; private init; } = ""; + public string ExecutablePath { get; private init; } = ""; + public string LogFilePath { get; private init; } = ""; + public string LsRegisterPath { get; private init; } = "/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister"; + public string? TestFilter { get; private init; } + public TimeSpan Timeout { get; private init; } + + public static Options Parse (string [] args) + { + var parsed = new Dictionary (StringComparer.Ordinal); + + for (var i = 0; i < args.Length; i++) { + var argument = args [i]; + if (argument is "--help" or "-h") + return new Options { ShowHelp = true }; + if (!argument.StartsWith ("--", StringComparison.Ordinal)) + throw new ArgumentException ($"Unknown argument: {argument}"); + if (i + 1 >= args.Length) + throw new ArgumentException ($"Missing value for argument: {argument}"); + parsed [argument] = args [++i]; + } + + var timeoutSeconds = int.Parse (GetRequired (parsed, "--timeout-seconds"), CultureInfo.InvariantCulture); + if (timeoutSeconds <= 0) + throw new ArgumentOutOfRangeException (nameof (args), "The timeout must be a positive number of seconds."); + + return new Options { + Platform = GetRequired (parsed, "--platform"), + Rid = GetRequired (parsed, "--rid"), + Config = GetRequired (parsed, "--config"), + AppPath = Path.GetFullPath (GetRequired (parsed, "--app")), + ExtensionPath = Path.GetFullPath (GetRequired (parsed, "--extension")), + ExecutablePath = Path.GetFullPath (GetRequired (parsed, "--executable")), + LogFilePath = Path.GetFullPath (GetRequired (parsed, "--log-file")), + LsRegisterPath = GetOptional (parsed, "--lsregister") ?? "/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister", + TestFilter = GetOptional (parsed, "--test-filter"), + Timeout = TimeSpan.FromSeconds (timeoutSeconds), + }; + } + + static string GetRequired (Dictionary parsed, string key) + { + if (!parsed.TryGetValue (key, out var value) || string.IsNullOrEmpty (value)) + throw new ArgumentException ($"Missing required argument: {key}"); + return value; + } + + static string? GetOptional (Dictionary parsed, string key) + { + parsed.TryGetValue (key, out var value); + return string.IsNullOrEmpty (value) ? null : value; + } +} diff --git a/scripts/run-audio-unit-extension-tests/run-audio-unit-extension-tests.csproj b/scripts/run-audio-unit-extension-tests/run-audio-unit-extension-tests.csproj new file mode 100644 index 000000000000..29f157e66384 --- /dev/null +++ b/scripts/run-audio-unit-extension-tests/run-audio-unit-extension-tests.csproj @@ -0,0 +1,11 @@ + + + + + net$(BundledNETCoreAppTargetFrameworkVersion) + + + + + + diff --git a/tests/common/Touch.Unit/Touch.Client/Runner/ExtensionTestRunner.cs b/tests/common/Touch.Unit/Touch.Client/Runner/ExtensionTestRunner.cs new file mode 100644 index 000000000000..a6ccc15ea7b3 --- /dev/null +++ b/tests/common/Touch.Unit/Touch.Client/Runner/ExtensionTestRunner.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; + +using Foundation; + +#if !__MACOS__ +using UIKit; +#endif + +#nullable enable + +namespace MonoTouch.NUnit.UI { + public static class ExtensionTestRunner { + public static BaseTouchRunner CreateHeadlessRunner (IEnumerable assemblies, string? testName = null, Action? log = null) + { + ArgumentNullException.ThrowIfNull (assemblies); + + var options = new TouchOptions ([]) { + AutoStart = true, + TerminateAfterExecution = false, + }; + if (!string.IsNullOrEmpty (testName)) + options.TestName = testName; + TouchOptions.Current = options; + + if (log is null) + log = Console.WriteLine; + + var runner = new HeadlessTouchRunner (); + runner.RunOnMainThread = true; + log ($"Loading {assemblies.Count ()} assemblies"); + foreach (var assembly in assemblies) { + log ($"Loaded assembly: {assembly}"); + runner.Load (assembly); + } + return runner; + } + + public static Task RunAsync (BaseTouchRunner runner) + { + ArgumentNullException.ThrowIfNull (runner); + + if (runner is HeadlessTouchRunner headless) + return headless.RunOnMainThreadAsync (); + + return runner.RunAsync (); + } + + sealed class HeadlessTouchRunner : BaseTouchRunner { + public Task RunOnMainThreadAsync () + { + var tcs = new TaskCompletionSource (); + ExecuteOnMainThread (() => { + try { + Run (); + tcs.SetResult (null); + } catch (Exception ex) { + tcs.SetException (ex); + } + }); + return tcs.Task; + } + + protected override void WriteDeviceInformation (TextWriter writer) + { +#if __MACOS__ + var processInfo = NSProcessInfo.ProcessInfo; + writer.WriteLine ("[macOS: {0}]", processInfo.OperatingSystemVersionString); +#else + var device = UIDevice.CurrentDevice; + writer.WriteLine ("[{0}:\t{1} v{2}]", device.Model, device.SystemName, device.SystemVersion); + writer.WriteLine ("[Device Name:\t{0}]", device.Name); +#endif + } + } + } +} diff --git a/tests/common/Touch.Unit/Touch.Client/Runner/TouchRunner.cs b/tests/common/Touch.Unit/Touch.Client/Runner/TouchRunner.cs index 1c23046fb905..5a84d015f535 100644 --- a/tests/common/Touch.Unit/Touch.Client/Runner/TouchRunner.cs +++ b/tests/common/Touch.Unit/Touch.Client/Runner/TouchRunner.cs @@ -73,6 +73,20 @@ public abstract class BaseTouchRunner : ITestListener { ITestFilter filter = TestFilter.Empty; bool connection_failure; + public Action? LogCallback; + + public void LogLine (string format, params object? [] args) + { + LogLine (string.Format (format, args)); + } + + public void LogLine (string message) + { + if (LogCallback is not null) + LogCallback (message); + Console.WriteLine (message); + } + public int PassedCount { get; private set; } public int FailedCount { get; private set; } public int IgnoredCount { get; private set; } @@ -96,6 +110,8 @@ public ITestFilter Filter { public HashSet? ExcludedCategories { get; set; } + public bool RunOnMainThread { get; set; } + public bool TerminateAfterExecution { get { return TouchOptions.Current.TerminateAfterExecution && !connection_failure; } set { TouchOptions.Current.TerminateAfterExecution = value; } @@ -134,7 +150,7 @@ protected void FlushConsole () static extern void exit (int code); protected virtual void TerminateWithSuccess () { - Console.WriteLine ("Exiting test run with success"); + LogLine ("Exiting test run with success"); FlushConsole (); exit (0); } @@ -145,7 +161,7 @@ protected virtual void TerminateWithExitCode (int exitCode) if (exitCode == 0) { TerminateWithSuccess (); } else { - Console.WriteLine ($"Exiting test run with code {exitCode}"); + LogLine ($"Exiting test run with code {exitCode}"); exit (exitCode); } } @@ -209,9 +225,9 @@ public void SelectLastTestSuite () #if !__MACCATALYST__ [Conditional ("IGNORED")] #endif - internal static void TraceLine (string message) + internal void TraceLine (string message) { - Console.WriteLine (message); + LogLine (message); } public void AutoRun () @@ -246,6 +262,7 @@ public void AutoRun () public Task RunAsync () { + LogLine ($"Running tests async..."); Run (); return Task.CompletedTask; } @@ -254,7 +271,7 @@ public Task RunAsync () public void Run () { if (running) { - Console.WriteLine ("Not running because another test run is already in progress."); + LogLine ("Not running because another test run is already in progress."); return; } @@ -340,7 +357,7 @@ public bool OpenWriter (string message) case "FILE": if (string.IsNullOrEmpty (options.LogFile)) throw new InvalidOperationException ("The FILE transport requires a log file path."); - Console.WriteLine ("[{0}] Sending '{1}' results to the file {2}", now, message, options.LogFile); + LogLine ("[{0}] Sending '{1}' results to the file {2}", now, message, options.LogFile); defaultWriter = new StreamWriter (options.LogFile, true, System.Text.Encoding.UTF8) { AutoFlush = true, }; @@ -351,8 +368,8 @@ public bool OpenWriter (string message) var hostnames = options.HostName.Split (','); hostname = hostnames [0]; if (hostnames.Length > 1) - Console.WriteLine ("[{0}] Found multiple host names ({1}); will only try sending to the first ({2})", now, options.HostName, hostname); - Console.WriteLine ("[{0}] Sending '{1}' results to {2}:{3}", now, message, hostname, options.HostPort); + LogLine ("[{0}] Found multiple host names ({1}); will only try sending to the first ({2})", now, options.HostName, hostname); + LogLine ("[{0}] Sending '{1}' results to {2}:{3}", now, message, hostname, options.HostPort); var w = new HttpTextWriter () { HostName = hostname, Port = options.HostPort, @@ -362,7 +379,7 @@ public bool OpenWriter (string message) WriterFinishedTask = w.FinishedTask; break; default: - Console.WriteLine ("Unknown transport '{0}': switching to default (TCP)", options.Transport); + LogLine ("Unknown transport '{0}': switching to default (TCP)", options.Transport); goto case "TCP"; case "TCP": if (string.IsNullOrWhiteSpace (options.HostName)) @@ -372,13 +389,13 @@ public bool OpenWriter (string message) else hostname = "localhost"; if (string.IsNullOrEmpty (hostname)) { - Console.WriteLine ("Couldn't establish a TCP connection with any of the hostnames: {0}", options.HostName); + LogLine ("Couldn't establish a TCP connection with any of the hostnames: {0}", options.HostName); break; } if (!options.UseTcpTunnel) - Console.WriteLine ("[{0}] Sending '{1}' results to {2}:{3}", now, message, hostname, options.HostPort); + LogLine ("[{0}] Sending '{1}' results to {2}:{3}", now, message, hostname, options.HostPort); else - Console.WriteLine ("[{0}] Sending '{1}' results to {2} over a tcp tunnel", now, message, options.HostPort); + LogLine ("[{0}] Sending '{1}' results to {2} over a tcp tunnel", now, message, options.HostPort); defaultWriter = new TcpTextWriter (hostname, options.HostPort, options.UseTcpTunnel); break; } @@ -410,10 +427,12 @@ public bool OpenWriter (string message) if (!ShowConnectionErrorAlert (options.HostName, options.HostPort, ex)) return false; - Console.WriteLine ("Network error: Cannot connect to {0}:{1}: {2}. Continuing on console.", options.HostName, options.HostPort, ex); + LogLine ("Network error: Cannot connect to {0}:{1}: {2}. Continuing on console.", options.HostName, options.HostPort, ex); } } writers.Add (Console.Out); + if (LogCallback is not null) + writers.Add (new CallbackTextWriter (LogCallback)); Writer = new MultiplexedTextWriter (writers); } @@ -454,7 +473,7 @@ bool ShowConnectionErrorAlert (string? hostname, int port, Exception ex) if (NSBundle.MainBundle.BundlePath.EndsWith (".appex", StringComparison.Ordinal)) return true; - Console.WriteLine ("Network error: Cannot connect to {0}:{1}: {2}.", hostname, port, ex); + LogLine ("Network error: Cannot connect to {0}:{1}: {2}.", hostname, port, ex); var alertDelegate = new UIAlertViewDelegate (); UIAlertView alert = new UIAlertView ("Network Error", String.Format ("Cannot connect to {0}:{1}: {2}. Continue on console ?", hostname, port, ex.Message), @@ -467,8 +486,8 @@ bool ShowConnectionErrorAlert (string? hostname, int port, Exception ex) alert.Show (); while (button == -1) NSRunLoop.Current.RunUntil (NSDate.FromTimeIntervalSinceNow (0.5)); - Console.WriteLine (button); - Console.WriteLine ("[Host unreachable: {0}]", button == 0 ? "Execution cancelled" : "Switching to console output"); + LogLine (button.ToString ()); + LogLine ("[Host unreachable: {0}]", button == 0 ? "Execution cancelled" : "Switching to console output"); return button != 0; #endif } @@ -578,17 +597,17 @@ public virtual void TestFinished (ITestResult r) Dictionary default_settings = new Dictionary () { #if NUNITLITE_NUGET - { "RunOnMainThread", true }, + // { "RunOnMainThread", true }, #endif }; SettingsDictionary CreateSettings (SettingsDictionary? settings) { - if (fixtures is null && (settings is null || settings.Count == 0)) - return default_settings; - var dict = new Dictionary (default_settings); + if (RunOnMainThread) + dict ["RunOnMainThread"] = true; + if (settings is not null) { foreach (var key in settings.Keys) { if (key is not null) { @@ -646,6 +665,7 @@ public bool Load (Assembly assembly, SettingsDictionary? settings = null) bool AddSuite (TestSuite ts) { + LogLine ($"AddSuite ({ts})"); if (ts is null) return false; suite.Add (ts); @@ -654,6 +674,7 @@ bool AddSuite (TestSuite ts) public void Run (Test test) { + LogLine ($"Run ({test} - {test.FullName})"); PassedCount = 0; IgnoredCount = 0; FailedCount = 0; @@ -669,8 +690,12 @@ public void Run (Test test) filter.AndFilters.Add (new ExcludeCategoryFilter (ExcludedCategories)); if (!string.IsNullOrEmpty (TouchOptions.Current.TestName)) filter.AndFilters.Add (TestFilter.FromXml ($"{TouchOptions.Current.TestName.Replace ("&", "&").Replace ("<", "<")}")); - foreach (var runner in runners) + + LogLine ($"Run ({test} - {test.FullName}) {runners.Count ()} runners"); + foreach (var runner in runners) { + LogLine ($"Run ({test} - {test.FullName}) runner: {runner}"); runner.Run (this, filter); + } // The TestResult we get back from the runner is for the top-most test suite, // which isn't necessarily the test that we ran. So look for the TestResult @@ -761,7 +786,7 @@ public UINavigationController NavigationController { protected override void TerminateWithSuccess () { - Console.WriteLine ($"Exiting test run with success"); + LogLine ($"Exiting test run with success"); FlushConsole (); Selector selector = new Selector ("terminateWithSuccess"); UIApplication.SharedApplication.PerformSelector (selector, UIApplication.SharedApplication, 0); @@ -1033,4 +1058,68 @@ public override void WriteLine (string? value) writer.WriteLine (value); } } + + class CallbackTextWriter : TextWriter { + Action writeLine; + StringBuilder lineBuffer = new StringBuilder (); + + public CallbackTextWriter (Action writeLine) + { + this.writeLine = writeLine; + } + + public override Encoding Encoding { + get { + return Encoding.UTF8; + } + } + + public override void Close () + { + Flush (); + } + + public override void Flush () + { + if (lineBuffer.Length > 0) { + writeLine (lineBuffer.ToString ()); + lineBuffer.Clear (); + } + } + + public override void Write (char value) + { + if (value == '\n') { + Flush (); + } else { + lineBuffer.Append (value); + } + } + + public override void Write (char []? buffer) + { + if (buffer is not null) + Write (new string (buffer)); + } + + public override void Write (string? value) + { + if (value is null) + return; + + var lines = value.Split ('\n'); + for (var i = 0; i < lines.Length; i++) { + lineBuffer.Append (lines [i]); + if (i < lines.Length - 1) + Flush (); + } + } + + public override void WriteLine (string? value) + { + if (value is not null) + lineBuffer.Append (value); + Flush (); + } + } } diff --git a/tests/common/Touch.Unit/Touch.Client/dotnet/shared.csproj b/tests/common/Touch.Unit/Touch.Client/dotnet/shared.csproj index 97edb9f41e74..beba0aa7fe41 100644 --- a/tests/common/Touch.Unit/Touch.Client/dotnet/shared.csproj +++ b/tests/common/Touch.Unit/Touch.Client/dotnet/shared.csproj @@ -12,6 +12,9 @@ ExcludedCategoryFilter.cs + + ExtensionTestRunner.cs + HttpTextWriter.cs diff --git a/tests/common/shared-dotnet.csproj b/tests/common/shared-dotnet.csproj index b6da56214c9e..f4ce4bd675a0 100644 --- a/tests/common/shared-dotnet.csproj +++ b/tests/common/shared-dotnet.csproj @@ -46,6 +46,11 @@ maccatalyst-x64 + + + $(DefineConstants);APP_EXTENSION + + diff --git a/tests/dotnet/AudioUnitExtension/AppExtension/AudioUnitExtension.cs b/tests/dotnet/AudioUnitExtension/AppExtension/AudioUnitExtension.cs new file mode 100644 index 000000000000..0f0c181e321e --- /dev/null +++ b/tests/dotnet/AudioUnitExtension/AppExtension/AudioUnitExtension.cs @@ -0,0 +1,64 @@ +using System; + +using AudioToolbox; +using AudioUnit; +using AVFoundation; +using Foundation; +using ObjCRuntime; + +namespace AudioUnitExtensionTest { + [Register ("TestAudioUnitFactory")] + public class TestAudioUnitFactory : NSObject, IAUAudioUnitFactory { + public TestAudioUnitFactory (NativeHandle handle) : base (handle) + { + } + + public AUAudioUnit CreateAudioUnit (AudioComponentDescription desc, out NSError error) + { + error = null; + return new TestAudioUnit (desc, out error); + } + + [Export ("beginRequestWithExtensionContext:")] + public void BeginRequestWithExtensionContext (NSExtensionContext context) + { + } + } + + [Register ("TestAudioUnit")] + public class TestAudioUnit : AUAudioUnit { + AUAudioUnitBusArray inputBusArray; + AUAudioUnitBusArray outputBusArray; + + public TestAudioUnit (AudioComponentDescription componentDescription, out NSError error) + : base (componentDescription, AudioComponentInstantiationOptions.OutOfProcess, out error) + { + var format = new AVAudioFormat (44100, 2); + var inputBus = new AUAudioUnitBus (format, out error); + var outputBus = new AUAudioUnitBus (format, out error); + inputBusArray = new AUAudioUnitBusArray (this, AUAudioUnitBusType.Input, new [] { inputBus }); + outputBusArray = new AUAudioUnitBusArray (this, AUAudioUnitBusType.Output, new [] { outputBus }); + } + + public TestAudioUnit (NativeHandle handle) : base (handle) + { + } + + public override AUAudioUnitBusArray InputBusses => inputBusArray; + + public override AUAudioUnitBusArray OutputBusses => outputBusArray; + + public override AUInternalRenderBlock InternalRenderBlock { + get { + return (ref AudioUnitRenderActionFlags actionFlags, ref AudioTimeStamp timestamp, + uint frameCount, nint outputBusNumber, AudioBuffers outputData, + AURenderEventEnumerator realtimeEventListHead, AURenderPullInputBlock pullInputBlock) => { + if (pullInputBlock is null) + return AudioUnitStatus.NoError; + pullInputBlock (ref actionFlags, ref timestamp, frameCount, 0, outputData); + return AudioUnitStatus.NoError; + }; + } + } + } +} diff --git a/tests/dotnet/AudioUnitExtension/AppExtension/Makefile b/tests/dotnet/AudioUnitExtension/AppExtension/Makefile new file mode 100644 index 000000000000..a97c2cbb3d5d --- /dev/null +++ b/tests/dotnet/AudioUnitExtension/AppExtension/Makefile @@ -0,0 +1,2 @@ +TOP=../../../.. +include $(TOP)/tests/common/shared-dotnet-test.mk diff --git a/tests/dotnet/AudioUnitExtension/AppExtension/iOS/AppExtension.csproj b/tests/dotnet/AudioUnitExtension/AppExtension/iOS/AppExtension.csproj new file mode 100644 index 000000000000..86d408734aa8 --- /dev/null +++ b/tests/dotnet/AudioUnitExtension/AppExtension/iOS/AppExtension.csproj @@ -0,0 +1,7 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-ios + + + diff --git a/tests/dotnet/AudioUnitExtension/AppExtension/iOS/AppExtension.slnx b/tests/dotnet/AudioUnitExtension/AppExtension/iOS/AppExtension.slnx new file mode 100644 index 000000000000..84162c981590 --- /dev/null +++ b/tests/dotnet/AudioUnitExtension/AppExtension/iOS/AppExtension.slnx @@ -0,0 +1,3 @@ + + + diff --git a/tests/dotnet/AudioUnitExtension/AppExtension/iOS/Entitlements.plist b/tests/dotnet/AudioUnitExtension/AppExtension/iOS/Entitlements.plist new file mode 100644 index 000000000000..5ea1ec76e117 --- /dev/null +++ b/tests/dotnet/AudioUnitExtension/AppExtension/iOS/Entitlements.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/tests/dotnet/AudioUnitExtension/AppExtension/iOS/Info.plist b/tests/dotnet/AudioUnitExtension/AppExtension/iOS/Info.plist new file mode 100644 index 000000000000..d6203fd0d6c2 --- /dev/null +++ b/tests/dotnet/AudioUnitExtension/AppExtension/iOS/Info.plist @@ -0,0 +1,57 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + AudioUnitExtension + CFBundleExecutable + AppExtension + CFBundleIdentifier + com.xamarin.AudioUnitExtensionTest + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + XPC! + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + NSExtension + + NSExtensionAttributes + + AudioComponents + + + type + aufx + subtype + test + manufacturer + Xmrn + name + XamarinTest: TestAU + version + 1 + sandboxSafe + + tags + + Effects + + + + + NSExtensionPointIdentifier + com.apple.AudioUnit + NSExtensionPrincipalClass + TestAudioUnitFactory + + + diff --git a/tests/dotnet/AudioUnitExtension/AppExtension/iOS/Makefile b/tests/dotnet/AudioUnitExtension/AppExtension/iOS/Makefile new file mode 100644 index 000000000000..110d078f4577 --- /dev/null +++ b/tests/dotnet/AudioUnitExtension/AppExtension/iOS/Makefile @@ -0,0 +1 @@ +include ../shared.mk diff --git a/tests/dotnet/AudioUnitExtension/AppExtension/macOS/AppExtension.csproj b/tests/dotnet/AudioUnitExtension/AppExtension/macOS/AppExtension.csproj new file mode 100644 index 000000000000..a77287b9ba00 --- /dev/null +++ b/tests/dotnet/AudioUnitExtension/AppExtension/macOS/AppExtension.csproj @@ -0,0 +1,7 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-macos + + + diff --git a/tests/dotnet/AudioUnitExtension/AppExtension/macOS/AppExtension.slnx b/tests/dotnet/AudioUnitExtension/AppExtension/macOS/AppExtension.slnx new file mode 100644 index 000000000000..84162c981590 --- /dev/null +++ b/tests/dotnet/AudioUnitExtension/AppExtension/macOS/AppExtension.slnx @@ -0,0 +1,3 @@ + + + diff --git a/tests/dotnet/AudioUnitExtension/AppExtension/macOS/Entitlements.plist b/tests/dotnet/AudioUnitExtension/AppExtension/macOS/Entitlements.plist new file mode 100644 index 000000000000..76c47e5ef322 --- /dev/null +++ b/tests/dotnet/AudioUnitExtension/AppExtension/macOS/Entitlements.plist @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + + diff --git a/tests/dotnet/AudioUnitExtension/AppExtension/macOS/Info.plist b/tests/dotnet/AudioUnitExtension/AppExtension/macOS/Info.plist new file mode 100644 index 000000000000..d6203fd0d6c2 --- /dev/null +++ b/tests/dotnet/AudioUnitExtension/AppExtension/macOS/Info.plist @@ -0,0 +1,57 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + AudioUnitExtension + CFBundleExecutable + AppExtension + CFBundleIdentifier + com.xamarin.AudioUnitExtensionTest + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + XPC! + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + NSExtension + + NSExtensionAttributes + + AudioComponents + + + type + aufx + subtype + test + manufacturer + Xmrn + name + XamarinTest: TestAU + version + 1 + sandboxSafe + + tags + + Effects + + + + + NSExtensionPointIdentifier + com.apple.AudioUnit + NSExtensionPrincipalClass + TestAudioUnitFactory + + + diff --git a/tests/dotnet/AudioUnitExtension/AppExtension/macOS/Makefile b/tests/dotnet/AudioUnitExtension/AppExtension/macOS/Makefile new file mode 100644 index 000000000000..110d078f4577 --- /dev/null +++ b/tests/dotnet/AudioUnitExtension/AppExtension/macOS/Makefile @@ -0,0 +1 @@ +include ../shared.mk diff --git a/tests/dotnet/AudioUnitExtension/AppExtension/shared.csproj b/tests/dotnet/AudioUnitExtension/AppExtension/shared.csproj new file mode 100644 index 000000000000..c16e555cf8e7 --- /dev/null +++ b/tests/dotnet/AudioUnitExtension/AppExtension/shared.csproj @@ -0,0 +1,12 @@ + + + + true + + + + + + + + diff --git a/tests/dotnet/AudioUnitExtension/AppExtension/shared.mk b/tests/dotnet/AudioUnitExtension/AppExtension/shared.mk new file mode 100644 index 000000000000..3b1ea8d6cf39 --- /dev/null +++ b/tests/dotnet/AudioUnitExtension/AppExtension/shared.mk @@ -0,0 +1,2 @@ +TOP=../../../../.. +include $(TOP)/tests/common/shared-dotnet.mk diff --git a/tests/dotnet/AudioUnitExtension/ContainerApp/AppDelegate.cs b/tests/dotnet/AudioUnitExtension/ContainerApp/AppDelegate.cs new file mode 100644 index 000000000000..db627351190b --- /dev/null +++ b/tests/dotnet/AudioUnitExtension/ContainerApp/AppDelegate.cs @@ -0,0 +1,17 @@ +using System; +using System.Runtime.InteropServices; + +using Foundation; + +namespace MySimpleApp { + public class Program { + static int Main (string [] args) + { + GC.KeepAlive (typeof (NSObject)); // prevent linking away the platform assembly + + Console.WriteLine (Environment.GetEnvironmentVariable ("MAGIC_WORD")); + + return args.Length; + } + } +} diff --git a/tests/dotnet/AudioUnitExtension/ContainerApp/Makefile b/tests/dotnet/AudioUnitExtension/ContainerApp/Makefile new file mode 100644 index 000000000000..a97c2cbb3d5d --- /dev/null +++ b/tests/dotnet/AudioUnitExtension/ContainerApp/Makefile @@ -0,0 +1,2 @@ +TOP=../../../.. +include $(TOP)/tests/common/shared-dotnet-test.mk diff --git a/tests/dotnet/AudioUnitExtension/ContainerApp/iOS/ContainerApp.csproj b/tests/dotnet/AudioUnitExtension/ContainerApp/iOS/ContainerApp.csproj new file mode 100644 index 000000000000..86d408734aa8 --- /dev/null +++ b/tests/dotnet/AudioUnitExtension/ContainerApp/iOS/ContainerApp.csproj @@ -0,0 +1,7 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-ios + + + diff --git a/tests/dotnet/AudioUnitExtension/ContainerApp/iOS/ContainerApp.slnx b/tests/dotnet/AudioUnitExtension/ContainerApp/iOS/ContainerApp.slnx new file mode 100644 index 000000000000..1990cc1541b6 --- /dev/null +++ b/tests/dotnet/AudioUnitExtension/ContainerApp/iOS/ContainerApp.slnx @@ -0,0 +1,4 @@ + + + + diff --git a/tests/dotnet/AudioUnitExtension/ContainerApp/iOS/Makefile b/tests/dotnet/AudioUnitExtension/ContainerApp/iOS/Makefile new file mode 100644 index 000000000000..110d078f4577 --- /dev/null +++ b/tests/dotnet/AudioUnitExtension/ContainerApp/iOS/Makefile @@ -0,0 +1 @@ +include ../shared.mk diff --git a/tests/dotnet/AudioUnitExtension/ContainerApp/macOS/ContainerApp.csproj b/tests/dotnet/AudioUnitExtension/ContainerApp/macOS/ContainerApp.csproj new file mode 100644 index 000000000000..7cc5ce7ca345 --- /dev/null +++ b/tests/dotnet/AudioUnitExtension/ContainerApp/macOS/ContainerApp.csproj @@ -0,0 +1,8 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-macos + true + + + diff --git a/tests/dotnet/AudioUnitExtension/ContainerApp/macOS/ContainerApp.slnx b/tests/dotnet/AudioUnitExtension/ContainerApp/macOS/ContainerApp.slnx new file mode 100644 index 000000000000..df1e2c429898 --- /dev/null +++ b/tests/dotnet/AudioUnitExtension/ContainerApp/macOS/ContainerApp.slnx @@ -0,0 +1,4 @@ + + + + diff --git a/tests/dotnet/AudioUnitExtension/ContainerApp/macOS/Makefile b/tests/dotnet/AudioUnitExtension/ContainerApp/macOS/Makefile new file mode 100644 index 000000000000..110d078f4577 --- /dev/null +++ b/tests/dotnet/AudioUnitExtension/ContainerApp/macOS/Makefile @@ -0,0 +1 @@ +include ../shared.mk diff --git a/tests/dotnet/AudioUnitExtension/ContainerApp/shared.csproj b/tests/dotnet/AudioUnitExtension/ContainerApp/shared.csproj new file mode 100644 index 000000000000..6444d4798f64 --- /dev/null +++ b/tests/dotnet/AudioUnitExtension/ContainerApp/shared.csproj @@ -0,0 +1,22 @@ + + + + Exe + + ContainerApp + com.xamarin.audiounitextensiontest.containerapp + 1.0 + + + + + + + + + + + true + + + diff --git a/tests/dotnet/AudioUnitExtension/ContainerApp/shared.mk b/tests/dotnet/AudioUnitExtension/ContainerApp/shared.mk new file mode 100644 index 000000000000..3b1ea8d6cf39 --- /dev/null +++ b/tests/dotnet/AudioUnitExtension/ContainerApp/shared.mk @@ -0,0 +1,2 @@ +TOP=../../../../.. +include $(TOP)/tests/common/shared-dotnet.mk diff --git a/tests/dotnet/ExtensionConsumer/macOS/Info.plist b/tests/dotnet/ExtensionConsumer/macOS/Info.plist new file mode 100644 index 000000000000..3fdadb363727 --- /dev/null +++ b/tests/dotnet/ExtensionConsumer/macOS/Info.plist @@ -0,0 +1,26 @@ + + + + + UTExportedTypeDeclarations + + + UTTypeIdentifier + com.xamarin.test-preview + UTTypeConformsTo + + public.data + + UTTypeDescription + Test Preview File + UTTypeTagSpecification + + public.filename-extension + + xpreview + + + + + + diff --git a/tests/dotnet/ExtensionProject/iOS/Info.plist b/tests/dotnet/ExtensionProject/iOS/Info.plist index 64639a6bc4f9..799b7b3e8653 100644 --- a/tests/dotnet/ExtensionProject/iOS/Info.plist +++ b/tests/dotnet/ExtensionProject/iOS/Info.plist @@ -5,13 +5,15 @@ CFBundleDevelopmentRegion en CFBundleDisplayName - MyShareExtension + PreviewExtension + CFBundleExecutable + ExtensionProject CFBundleIdentifier - com.xamarin.MyMasterDetailApp.MyShareExtension + com.xamarin.PreviewExtensionTest CFBundleInfoDictionaryVersion 6.0 CFBundleName - com.xamarin.MyShareExtension + $(PRODUCT_NAME) CFBundlePackageType XPC! CFBundleShortVersionString @@ -24,17 +26,17 @@ NSExtensionAttributes - NSExtensionActivationRule - TRUEPREDICATE - NSExtensionPointName - com.apple.share-services - NSExtensionPointVersion - 1.0 + QLSupportedContentTypes + + com.xamarin.test-preview + + QLSupportsSearchableItems + - NSExtensionMainStoryboard - MainInterface NSExtensionPointIdentifier - com.apple.share-services + com.apple.quicklook.preview + NSExtensionPrincipalClass + PreviewViewController diff --git a/tests/dotnet/ExtensionProject/iOS/MainInterface.storyboard b/tests/dotnet/ExtensionProject/iOS/MainInterface.storyboard deleted file mode 100644 index 10d089e2a175..000000000000 --- a/tests/dotnet/ExtensionProject/iOS/MainInterface.storyboard +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/dotnet/ExtensionProject/iOS/PreviewViewController.cs b/tests/dotnet/ExtensionProject/iOS/PreviewViewController.cs new file mode 100644 index 000000000000..c60b2f682174 --- /dev/null +++ b/tests/dotnet/ExtensionProject/iOS/PreviewViewController.cs @@ -0,0 +1,20 @@ +using System; + +using Foundation; +using ObjCRuntime; +using QuickLook; +using UIKit; + +namespace PreviewExtensionTest { + [Register ("PreviewViewController")] + public class PreviewViewController : UIViewController, IQLPreviewingController { + public PreviewViewController (NativeHandle handle) : base (handle) + { + } + + public void PreparePreviewOfFile (NSUrl url, Action completionHandler) + { + completionHandler (null); + } + } +} diff --git a/tests/dotnet/ExtensionProject/iOS/ShareViewController.cs b/tests/dotnet/ExtensionProject/iOS/ShareViewController.cs deleted file mode 100644 index f0361286f481..000000000000 --- a/tests/dotnet/ExtensionProject/iOS/ShareViewController.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Drawing; - -using Foundation; -using Social; -using UIKit; - -namespace MyShareExtension { - public partial class ShareViewController : SLComposeServiceViewController { - public ShareViewController (IntPtr handle) : base (handle) - { - } - - public override bool IsContentValid () - { - // Do validation of contentText and/or NSExtensionContext attachments here - return true; - } - - public override void DidSelectPost () - { - // This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments. - - // Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context. - ExtensionContext?.CompleteRequest ([], null); - } - - public override SLComposeSheetConfigurationItem [] GetConfigurationItems () - { - // To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here. - return new SLComposeSheetConfigurationItem [0]; - } - } -} diff --git a/tests/dotnet/ExtensionProject/iOS/ShareViewController.designer.cs b/tests/dotnet/ExtensionProject/iOS/ShareViewController.designer.cs deleted file mode 100644 index ac1e6b1b25f8..000000000000 --- a/tests/dotnet/ExtensionProject/iOS/ShareViewController.designer.cs +++ /dev/null @@ -1,18 +0,0 @@ -// -// This file has been generated automatically by MonoDevelop to store outlets and -// actions made in the Xcode designer. If it is removed, they will be lost. -// Manual changes to this file may not be handled correctly. -// - -using Foundation; - -namespace MyShareExtension -{ - [Register ("ShareViewController")] - partial class ShareViewController - { - void ReleaseDesignerOutlets () - { - } - } -} diff --git a/tests/dotnet/ExtensionProject/macOS/ExtensionProject.csproj b/tests/dotnet/ExtensionProject/macOS/ExtensionProject.csproj index 9c2d837e8f91..a77287b9ba00 100644 --- a/tests/dotnet/ExtensionProject/macOS/ExtensionProject.csproj +++ b/tests/dotnet/ExtensionProject/macOS/ExtensionProject.csproj @@ -2,7 +2,6 @@ net$(BundledNETCoreAppTargetFrameworkVersion)-macos - $(SourceDirectory)Entitlements.plist diff --git a/tests/dotnet/ExtensionProject/macOS/Info.plist b/tests/dotnet/ExtensionProject/macOS/Info.plist index dfa94d4fa9fd..1ff39ceeb9bb 100644 --- a/tests/dotnet/ExtensionProject/macOS/Info.plist +++ b/tests/dotnet/ExtensionProject/macOS/Info.plist @@ -5,11 +5,11 @@ CFBundleDevelopmentRegion en CFBundleDisplayName - ShareExtension + PreviewExtension CFBundleExecutable ExtensionProject CFBundleIdentifier - com.xamarin.ShareExtensionTest + com.xamarin.PreviewExtensionTest CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -26,15 +26,18 @@ NSExtensionAttributes - NSExtensionActivationRule - TRUEPREDICATE + QLSupportedContentTypes + + com.xamarin.test-preview + + QLSupportsSearchableItems + NSExtensionPointIdentifier - com.apple.share-services + com.apple.quicklook.preview NSExtensionPrincipalClass - ShareViewController + PreviewViewController - diff --git a/tests/dotnet/ExtensionProject/macOS/PreviewViewController.cs b/tests/dotnet/ExtensionProject/macOS/PreviewViewController.cs new file mode 100644 index 000000000000..b25909e8c36b --- /dev/null +++ b/tests/dotnet/ExtensionProject/macOS/PreviewViewController.cs @@ -0,0 +1,25 @@ +using System; + +using AppKit; +using Foundation; +using ObjCRuntime; +using QuickLookUI; + +namespace PreviewExtensionTest { + [Register ("PreviewViewController")] + public class PreviewViewController : NSViewController, IQLPreviewingController { + public PreviewViewController (NativeHandle handle) : base (handle) + { + } + + public override void LoadView () + { + View = new NSView (new CoreGraphics.CGRect (0, 0, 400, 300)); + } + + public void PreparePreviewOfFile (NSUrl url, Action completionHandler) + { + completionHandler (null); + } + } +} diff --git a/tests/dotnet/ExtensionProject/macOS/ShareViewController.cs b/tests/dotnet/ExtensionProject/macOS/ShareViewController.cs deleted file mode 100644 index 3d59f883e3b4..000000000000 --- a/tests/dotnet/ExtensionProject/macOS/ShareViewController.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Drawing; - -using NotificationCenter; -using Foundation; -using Social; -using AppKit; -using System.Linq; - -namespace ShareExtensionTest { - public partial class ShareViewController : NSViewController { - public ShareViewController (IntPtr handle) : base (handle) - { - } - - public override void LoadView () - { - base.LoadView (); - - NSExtensionItem item = ExtensionContext?.InputItems.First () ?? new NSExtensionItem (); - Console.WriteLine ("Attachments {0}", item); - } - - partial void Cancel (Foundation.NSObject sender) - { - NSExtensionItem outputItem = new NSExtensionItem (); - var outputItems = new [] { outputItem }; - ExtensionContext?.CompleteRequest (outputItems, null); - } - - partial void Send (Foundation.NSObject sender) - { - NSError cancelError = NSError.FromDomain (NSError.CocoaErrorDomain, 3072, null); - ExtensionContext?.CancelRequest (cancelError); - } - } -} diff --git a/tests/dotnet/ExtensionProject/macOS/ShareViewController.designer.cs b/tests/dotnet/ExtensionProject/macOS/ShareViewController.designer.cs deleted file mode 100644 index 6801a6932c4b..000000000000 --- a/tests/dotnet/ExtensionProject/macOS/ShareViewController.designer.cs +++ /dev/null @@ -1,24 +0,0 @@ -// -// This file has been generated automatically by MonoDevelop to store outlets and -// actions made in the Xcode designer. If it is removed, they will be lost. -// Manual changes to this file may not be handled correctly. -// - -using Foundation; - -namespace ShareExtensionTest -{ - [Register ("ShareViewController")] - partial class ShareViewController - { - [Action ("Cancel:")] - partial void Cancel (Foundation.NSObject sender); - - [Action ("Send:")] - partial void Send (Foundation.NSObject sender); - - void ReleaseDesignerOutlets () - { - } - } -} diff --git a/tests/dotnet/ExtensionProject/macOS/ShareViewController.xib b/tests/dotnet/ExtensionProject/macOS/ShareViewController.xib deleted file mode 100644 index 592a458e43a2..000000000000 --- a/tests/dotnet/ExtensionProject/macOS/ShareViewController.xib +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/dotnet/SpotlightImportExtension/AppExtension/Makefile b/tests/dotnet/SpotlightImportExtension/AppExtension/Makefile new file mode 100644 index 000000000000..a97c2cbb3d5d --- /dev/null +++ b/tests/dotnet/SpotlightImportExtension/AppExtension/Makefile @@ -0,0 +1,2 @@ +TOP=../../../.. +include $(TOP)/tests/common/shared-dotnet-test.mk diff --git a/tests/dotnet/SpotlightImportExtension/AppExtension/SpotlightImportExtension.cs b/tests/dotnet/SpotlightImportExtension/AppExtension/SpotlightImportExtension.cs new file mode 100644 index 000000000000..c5fd706cac0e --- /dev/null +++ b/tests/dotnet/SpotlightImportExtension/AppExtension/SpotlightImportExtension.cs @@ -0,0 +1,22 @@ +using System; + +using CoreSpotlight; +using Foundation; +using ObjCRuntime; + +namespace SpotlightImportExtensionTest { + [Register ("ImportExtension")] + public class ImportExtension : CSImportExtension { + public ImportExtension (NativeHandle handle) : base (handle) + { + } + + public override bool Update (CSSearchableItemAttributeSet attributes, NSUrl contentUrl, out NSError error) + { + error = null; + attributes.Title = "Test Spotlight Import"; + attributes.ContentDescription = "Imported by test extension"; + return true; + } + } +} diff --git a/tests/dotnet/SpotlightImportExtension/AppExtension/iOS/AppExtension.csproj b/tests/dotnet/SpotlightImportExtension/AppExtension/iOS/AppExtension.csproj new file mode 100644 index 000000000000..86d408734aa8 --- /dev/null +++ b/tests/dotnet/SpotlightImportExtension/AppExtension/iOS/AppExtension.csproj @@ -0,0 +1,7 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-ios + + + diff --git a/tests/dotnet/SpotlightImportExtension/AppExtension/iOS/AppExtension.slnx b/tests/dotnet/SpotlightImportExtension/AppExtension/iOS/AppExtension.slnx new file mode 100644 index 000000000000..84162c981590 --- /dev/null +++ b/tests/dotnet/SpotlightImportExtension/AppExtension/iOS/AppExtension.slnx @@ -0,0 +1,3 @@ + + + diff --git a/tests/dotnet/SpotlightImportExtension/AppExtension/iOS/Entitlements.plist b/tests/dotnet/SpotlightImportExtension/AppExtension/iOS/Entitlements.plist new file mode 100644 index 000000000000..5ea1ec76e117 --- /dev/null +++ b/tests/dotnet/SpotlightImportExtension/AppExtension/iOS/Entitlements.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/tests/dotnet/SpotlightImportExtension/AppExtension/iOS/Info.plist b/tests/dotnet/SpotlightImportExtension/AppExtension/iOS/Info.plist new file mode 100644 index 000000000000..a248fac5d2e2 --- /dev/null +++ b/tests/dotnet/SpotlightImportExtension/AppExtension/iOS/Info.plist @@ -0,0 +1,40 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + SpotlightImportExtension + CFBundleExecutable + AppExtension + CFBundleIdentifier + com.xamarin.SpotlightImportExtensionTest + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + XPC! + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + NSExtension + + NSExtensionAttributes + + CSSupportedContentTypes + + com.xamarin.test-spotlight + + + NSExtensionPointIdentifier + com.apple.spotlight.import + NSExtensionPrincipalClass + ImportExtension + + + diff --git a/tests/dotnet/SpotlightImportExtension/AppExtension/iOS/Makefile b/tests/dotnet/SpotlightImportExtension/AppExtension/iOS/Makefile new file mode 100644 index 000000000000..110d078f4577 --- /dev/null +++ b/tests/dotnet/SpotlightImportExtension/AppExtension/iOS/Makefile @@ -0,0 +1 @@ +include ../shared.mk diff --git a/tests/dotnet/SpotlightImportExtension/AppExtension/macOS/AppExtension.csproj b/tests/dotnet/SpotlightImportExtension/AppExtension/macOS/AppExtension.csproj new file mode 100644 index 000000000000..a77287b9ba00 --- /dev/null +++ b/tests/dotnet/SpotlightImportExtension/AppExtension/macOS/AppExtension.csproj @@ -0,0 +1,7 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-macos + + + diff --git a/tests/dotnet/SpotlightImportExtension/AppExtension/macOS/AppExtension.slnx b/tests/dotnet/SpotlightImportExtension/AppExtension/macOS/AppExtension.slnx new file mode 100644 index 000000000000..84162c981590 --- /dev/null +++ b/tests/dotnet/SpotlightImportExtension/AppExtension/macOS/AppExtension.slnx @@ -0,0 +1,3 @@ + + + diff --git a/tests/dotnet/SpotlightImportExtension/AppExtension/macOS/Entitlements.plist b/tests/dotnet/SpotlightImportExtension/AppExtension/macOS/Entitlements.plist new file mode 100644 index 000000000000..76c47e5ef322 --- /dev/null +++ b/tests/dotnet/SpotlightImportExtension/AppExtension/macOS/Entitlements.plist @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + + diff --git a/tests/dotnet/SpotlightImportExtension/AppExtension/macOS/Info.plist b/tests/dotnet/SpotlightImportExtension/AppExtension/macOS/Info.plist new file mode 100644 index 000000000000..a248fac5d2e2 --- /dev/null +++ b/tests/dotnet/SpotlightImportExtension/AppExtension/macOS/Info.plist @@ -0,0 +1,40 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + SpotlightImportExtension + CFBundleExecutable + AppExtension + CFBundleIdentifier + com.xamarin.SpotlightImportExtensionTest + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + XPC! + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + NSExtension + + NSExtensionAttributes + + CSSupportedContentTypes + + com.xamarin.test-spotlight + + + NSExtensionPointIdentifier + com.apple.spotlight.import + NSExtensionPrincipalClass + ImportExtension + + + diff --git a/tests/dotnet/SpotlightImportExtension/AppExtension/macOS/Makefile b/tests/dotnet/SpotlightImportExtension/AppExtension/macOS/Makefile new file mode 100644 index 000000000000..110d078f4577 --- /dev/null +++ b/tests/dotnet/SpotlightImportExtension/AppExtension/macOS/Makefile @@ -0,0 +1 @@ +include ../shared.mk diff --git a/tests/dotnet/SpotlightImportExtension/AppExtension/shared.csproj b/tests/dotnet/SpotlightImportExtension/AppExtension/shared.csproj new file mode 100644 index 000000000000..c16e555cf8e7 --- /dev/null +++ b/tests/dotnet/SpotlightImportExtension/AppExtension/shared.csproj @@ -0,0 +1,12 @@ + + + + true + + + + + + + + diff --git a/tests/dotnet/SpotlightImportExtension/AppExtension/shared.mk b/tests/dotnet/SpotlightImportExtension/AppExtension/shared.mk new file mode 100644 index 000000000000..3b1ea8d6cf39 --- /dev/null +++ b/tests/dotnet/SpotlightImportExtension/AppExtension/shared.mk @@ -0,0 +1,2 @@ +TOP=../../../../.. +include $(TOP)/tests/common/shared-dotnet.mk diff --git a/tests/dotnet/SpotlightImportExtension/ContainerApp/AppDelegate.cs b/tests/dotnet/SpotlightImportExtension/ContainerApp/AppDelegate.cs new file mode 100644 index 000000000000..db627351190b --- /dev/null +++ b/tests/dotnet/SpotlightImportExtension/ContainerApp/AppDelegate.cs @@ -0,0 +1,17 @@ +using System; +using System.Runtime.InteropServices; + +using Foundation; + +namespace MySimpleApp { + public class Program { + static int Main (string [] args) + { + GC.KeepAlive (typeof (NSObject)); // prevent linking away the platform assembly + + Console.WriteLine (Environment.GetEnvironmentVariable ("MAGIC_WORD")); + + return args.Length; + } + } +} diff --git a/tests/dotnet/SpotlightImportExtension/ContainerApp/Makefile b/tests/dotnet/SpotlightImportExtension/ContainerApp/Makefile new file mode 100644 index 000000000000..a97c2cbb3d5d --- /dev/null +++ b/tests/dotnet/SpotlightImportExtension/ContainerApp/Makefile @@ -0,0 +1,2 @@ +TOP=../../../.. +include $(TOP)/tests/common/shared-dotnet-test.mk diff --git a/tests/dotnet/SpotlightImportExtension/ContainerApp/iOS/ContainerApp.csproj b/tests/dotnet/SpotlightImportExtension/ContainerApp/iOS/ContainerApp.csproj new file mode 100644 index 000000000000..86d408734aa8 --- /dev/null +++ b/tests/dotnet/SpotlightImportExtension/ContainerApp/iOS/ContainerApp.csproj @@ -0,0 +1,7 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-ios + + + diff --git a/tests/dotnet/SpotlightImportExtension/ContainerApp/iOS/ContainerApp.slnx b/tests/dotnet/SpotlightImportExtension/ContainerApp/iOS/ContainerApp.slnx new file mode 100644 index 000000000000..1990cc1541b6 --- /dev/null +++ b/tests/dotnet/SpotlightImportExtension/ContainerApp/iOS/ContainerApp.slnx @@ -0,0 +1,4 @@ + + + + diff --git a/tests/dotnet/SpotlightImportExtension/ContainerApp/iOS/Info.plist b/tests/dotnet/SpotlightImportExtension/ContainerApp/iOS/Info.plist new file mode 100644 index 000000000000..86ec1718a13c --- /dev/null +++ b/tests/dotnet/SpotlightImportExtension/ContainerApp/iOS/Info.plist @@ -0,0 +1,26 @@ + + + + + UTExportedTypeDeclarations + + + UTTypeIdentifier + com.xamarin.test-spotlight + UTTypeConformsTo + + public.data + + UTTypeDescription + Test Spotlight File + UTTypeTagSpecification + + public.filename-extension + + xspotlight + + + + + + diff --git a/tests/dotnet/SpotlightImportExtension/ContainerApp/iOS/Makefile b/tests/dotnet/SpotlightImportExtension/ContainerApp/iOS/Makefile new file mode 100644 index 000000000000..110d078f4577 --- /dev/null +++ b/tests/dotnet/SpotlightImportExtension/ContainerApp/iOS/Makefile @@ -0,0 +1 @@ +include ../shared.mk diff --git a/tests/dotnet/SpotlightImportExtension/ContainerApp/macOS/ContainerApp.csproj b/tests/dotnet/SpotlightImportExtension/ContainerApp/macOS/ContainerApp.csproj new file mode 100644 index 000000000000..7cc5ce7ca345 --- /dev/null +++ b/tests/dotnet/SpotlightImportExtension/ContainerApp/macOS/ContainerApp.csproj @@ -0,0 +1,8 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-macos + true + + + diff --git a/tests/dotnet/SpotlightImportExtension/ContainerApp/macOS/ContainerApp.slnx b/tests/dotnet/SpotlightImportExtension/ContainerApp/macOS/ContainerApp.slnx new file mode 100644 index 000000000000..df1e2c429898 --- /dev/null +++ b/tests/dotnet/SpotlightImportExtension/ContainerApp/macOS/ContainerApp.slnx @@ -0,0 +1,4 @@ + + + + diff --git a/tests/dotnet/SpotlightImportExtension/ContainerApp/macOS/Info.plist b/tests/dotnet/SpotlightImportExtension/ContainerApp/macOS/Info.plist new file mode 100644 index 000000000000..86ec1718a13c --- /dev/null +++ b/tests/dotnet/SpotlightImportExtension/ContainerApp/macOS/Info.plist @@ -0,0 +1,26 @@ + + + + + UTExportedTypeDeclarations + + + UTTypeIdentifier + com.xamarin.test-spotlight + UTTypeConformsTo + + public.data + + UTTypeDescription + Test Spotlight File + UTTypeTagSpecification + + public.filename-extension + + xspotlight + + + + + + diff --git a/tests/dotnet/SpotlightImportExtension/ContainerApp/macOS/Makefile b/tests/dotnet/SpotlightImportExtension/ContainerApp/macOS/Makefile new file mode 100644 index 000000000000..110d078f4577 --- /dev/null +++ b/tests/dotnet/SpotlightImportExtension/ContainerApp/macOS/Makefile @@ -0,0 +1 @@ +include ../shared.mk diff --git a/tests/dotnet/SpotlightImportExtension/ContainerApp/shared.csproj b/tests/dotnet/SpotlightImportExtension/ContainerApp/shared.csproj new file mode 100644 index 000000000000..7e3628f9f3a7 --- /dev/null +++ b/tests/dotnet/SpotlightImportExtension/ContainerApp/shared.csproj @@ -0,0 +1,22 @@ + + + + Exe + + ContainerApp + com.xamarin.spotlightimportextensiontest.containerapp + 1.0 + + + + + + + + + + + true + + + diff --git a/tests/dotnet/SpotlightImportExtension/ContainerApp/shared.mk b/tests/dotnet/SpotlightImportExtension/ContainerApp/shared.mk new file mode 100644 index 000000000000..3b1ea8d6cf39 --- /dev/null +++ b/tests/dotnet/SpotlightImportExtension/ContainerApp/shared.mk @@ -0,0 +1,2 @@ +TOP=../../../../.. +include $(TOP)/tests/common/shared-dotnet.mk diff --git a/tests/dotnet/UnitTests/RegistrarTest.cs b/tests/dotnet/UnitTests/RegistrarTest.cs index f7aad07cb565..a0ef312198ca 100644 --- a/tests/dotnet/UnitTests/RegistrarTest.cs +++ b/tests/dotnet/UnitTests/RegistrarTest.cs @@ -108,6 +108,285 @@ public void ClassRewriterTest (ApplePlatform platform, bool rewriteHandles) } } + // Ref: https://github.com/dotnet/macios/issues/24869 + // Extensions crash at runtime when using the managed-static registrar because + // the registrar's function table has a zero entry for the extension class's constructor callback. + // The default registrar for iOS/tvOS device builds is managed-static, so extensions crash by default on device. + // [TestCase (ApplePlatform.iOS)] + // [TestCase (ApplePlatform.TVOS)] + [TestCase (ApplePlatform.MacOSX)] + public void ExtensionWithManagedStaticRegistrar (ApplePlatform platform) + { + Configuration.IgnoreIfIgnoredPlatform (platform); + var runtimeIdentifiers = GetDefaultRuntimeIdentifier (platform); + Configuration.AssertRuntimeIdentifiersAvailable (platform, runtimeIdentifiers); + + var dotnetTestDir = Path.Combine (Configuration.SourceRoot, "tests", "dotnet", "AudioUnitExtension"); + var platformName = platform.AsString (); + var containerProjectPath = Path.Combine (dotnetTestDir, "ContainerApp", platformName, "ContainerApp.csproj"); + var extensionProjectPath = Path.Combine (dotnetTestDir, "AppExtension", platformName, "AppExtension.csproj"); + var appPath = Path.Combine (Path.GetDirectoryName (containerProjectPath)!, "bin", "Debug", platform.ToFramework (), runtimeIdentifiers, "ContainerApp.app"); + + Clean (extensionProjectPath); + Clean (containerProjectPath); + + var properties = GetDefaultProperties (runtimeIdentifiers); + properties ["Registrar"] = "managed-static"; + + DotNet.AssertBuild (containerProjectPath, properties); + + var extensionPath = Path.Combine (appPath, GetPlugInsRelativePath (platform), "AppExtension.appex"); + Assert.That (Directory.Exists (extensionPath), Is.True, $"App extension directory does not exist: {extensionPath}"); + + if (CanExecute (platform, runtimeIdentifiers)) { + // Verify the host app can be executed. + ExecuteProjectWithMagicWordAndAssert (containerProjectPath, platform, runtimeIdentifiers); + + if (platform == ApplePlatform.MacOSX) + TriggerAudioUnitExtension (appPath, extensionPath); + } + } + + [TestCase (ApplePlatform.MacOSX)] + [TestCase (ApplePlatform.MacCatalyst)] + public void MonotouchTestInAudioUnitExtension (ApplePlatform platform) + { + Configuration.IgnoreIfIgnoredPlatform (platform); + var runtimeIdentifiers = GetDefaultRuntimeIdentifier (platform); + Configuration.AssertRuntimeIdentifiersAvailable (platform, runtimeIdentifiers); + + var extensionRoot = Path.Combine (Configuration.SourceRoot, "tests", "monotouch-test", "dotnet", "extensions", "audio-unit", platform.AsString ()); + var containerProjectPath = Path.Combine (extensionRoot, "ContainerApp", "ContainerApp.csproj"); + var extensionProjectPath = Path.Combine (extensionRoot, "AppExtension", "AppExtension.csproj"); + var appPath = Path.Combine (Path.GetDirectoryName (containerProjectPath)!, "bin", "Debug", platform.ToFramework (), runtimeIdentifiers, "ContainerApp.app"); + + Clean (extensionProjectPath); + Clean (containerProjectPath); + + var properties = GetDefaultProperties (runtimeIdentifiers); + properties ["Registrar"] = "managed-static"; + properties ["MonotouchExtensionTestName"] = "MonoTouchFixtures.AudioUnit.AppExtensionSmokeTest"; + + DotNet.AssertBuild (containerProjectPath, properties); + + var extensionPath = Path.Combine (appPath, GetPlugInsRelativePath (platform), "AppExtension.appex"); + Assert.That (Directory.Exists (extensionPath), Is.True, $"App extension directory does not exist: {extensionPath}"); + + if (CanExecute (platform, runtimeIdentifiers)) { + ExecuteProjectWithMagicWordAndAssert (containerProjectPath, platform, runtimeIdentifiers); + + var logText = TriggerAudioUnitExtension (appPath, extensionPath, "MonoTouchFixtures.AudioUnit.AppExtensionSmokeTest"); + Assert.That (logText, Does.Contain ("[monotouch-test-audio-unit-extension] Starting monotouch-test audio unit extension test run"), "Start marker"); + Assert.That (logText, Does.Contain ("MonoTouchFixtures.AudioUnit.AppExtensionSmokeTest"), "Smoke test selection"); + Assert.That (logText, Does.Contain ("[monotouch-test-audio-unit-extension] Finished monotouch-test audio unit extension test run. Passed: 1 Failed: 0"), "Summary"); + } + } + + // Trigger the Audio Unit extension via auvaltool to verify the extension + // actually loads and runs with the managed-static registrar. + // This requires: + // 1. A non-ad-hoc signing certificate (extension discovery requires a team ID) + // 2. The host app to be registered with Launch Services (for extension discovery) + // 3. auvaltool -v to trigger Audio Unit validation which loads the extension + string TriggerAudioUnitExtension (string appPath, string extensionPath, string? testName = null) + { + int exitCode; + StringBuilder output; + var testFilterFile = Path.Combine (extensionPath, "Contents", "Resources", "monotouch-extension-test-filter.txt"); + var hostTestFilterFile = Path.Combine (appPath, "Contents", "Resources", "monotouch-extension-test-filter.txt"); + + // Register the app with Launch Services so the system discovers the + // extension and its AudioComponents. + var lsregister = "/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister"; + Console.WriteLine ($"Executing: {lsregister} -f {appPath}"); + exitCode = ExecutionHelper.Execute (lsregister, new [] { "-f", appPath }, out output, (string) null!); + Console.WriteLine ($"Exit code: {exitCode}"); + Console.WriteLine (output); + + // Register the extension explicitly with pluginkit. + Console.WriteLine ($"Executing: pluginkit -a {extensionPath}"); + exitCode = ExecutionHelper.Execute ("pluginkit", new [] { "-a", extensionPath }, out output, (string) null!); + Console.WriteLine ($"Exit code: {exitCode}"); + Console.WriteLine (output); + + try { + if (string.IsNullOrEmpty (testName)) { + ExecutionHelper.Execute ("defaults", new [] { "delete", "com.xamarin.monotouch-test.AudioUnitExtension", "test.name" }, out output, (string) null!); + if (File.Exists (testFilterFile)) + File.Delete (testFilterFile); + if (File.Exists (hostTestFilterFile)) + File.Delete (hostTestFilterFile); + } else { + exitCode = ExecutionHelper.Execute ("defaults", new [] { "write", "com.xamarin.monotouch-test.AudioUnitExtension", "test.name", "-string", testName }, out output, (string) null!); + Directory.CreateDirectory (Path.GetDirectoryName (testFilterFile)!); + Directory.CreateDirectory (Path.GetDirectoryName (hostTestFilterFile)!); + File.WriteAllText (testFilterFile, testName); + File.WriteAllText (hostTestFilterFile, testName); + } + + // Record the current time so we can query system logs after triggering. + var logStartTime = DateTime.Now; + + // Run auvaltool to validate the Audio Unit, which triggers the system + // to discover and launch the extension process. + // aufx = effect type, test = subtype, Xmrn = manufacturer (matching Info.plist). + // auvaltool may fail validation (the AU is minimal), but the system will + // still attempt to load the extension process. + Console.WriteLine ("Executing: auvaltool -v aufx test Xmrn"); + exitCode = ExecutionHelper.Execute ("auvaltool", new [] { "-v", "aufx", "test", "Xmrn" }, out output, (string) null!, timeout: TimeSpan.FromMinutes (2)); + Console.WriteLine ($"Exit code: {exitCode}"); + Console.WriteLine (output); + var auvalOutput = output.ToString (); + Assert.That (auvalOutput, Does.Contain ("Loaded AudioUnit out-of-process: true"), + "auvaltool did not report loading the audio unit extension out-of-process."); + + // Check system logs for evidence the extension process was launched. + var logEnd = DateTime.Now; + var logStartStr = logStartTime.ToString ("yyyy-MM-dd HH:mm:ss"); + var logEndStr = logEnd.ToString ("yyyy-MM-dd HH:mm:ss"); + var logArgs = new [] { + "show", + "--style", "compact", + "--predicate", "process == \"AppExtension\" OR eventMessage CONTAINS[c] \"monotouch-test-audio-unit-extension\" OR eventMessage CONTAINS[c] \"AppExtensionSmokeTest\"", + "--start", logStartStr, + "--end", logEndStr, + }; + Console.WriteLine ($"Executing: log {string.Join (" ", logArgs)}"); + exitCode = ExecutionHelper.Execute ("log", logArgs, out output, (string) null!); + Console.WriteLine ($"Exit code: {exitCode}"); + var logText = output.ToString (); + Console.WriteLine (logText); + return auvalOutput + Environment.NewLine + logText; + } finally { + ExecutionHelper.Execute ("defaults", new [] { "delete", "com.xamarin.monotouch-test.AudioUnitExtension", "test.name" }, out output, (string) null!); + if (File.Exists (testFilterFile)) + File.Delete (testFilterFile); + if (File.Exists (hostTestFilterFile)) + File.Delete (hostTestFilterFile); + } + } + + [TestCase (ApplePlatform.MacOSX)] + public void SpotlightImportExtension (ApplePlatform platform) + { + Configuration.IgnoreIfIgnoredPlatform (platform); + var runtimeIdentifiers = GetDefaultRuntimeIdentifier (platform); + Configuration.AssertRuntimeIdentifiersAvailable (platform, runtimeIdentifiers); + + var dotnetTestDir = Path.Combine (Configuration.SourceRoot, "tests", "dotnet", "SpotlightImportExtension"); + var platformName = platform.AsString (); + var containerProjectPath = Path.Combine (dotnetTestDir, "ContainerApp", platformName, "ContainerApp.csproj"); + var extensionProjectPath = Path.Combine (dotnetTestDir, "AppExtension", platformName, "AppExtension.csproj"); + var appPath = Path.Combine (Path.GetDirectoryName (containerProjectPath)!, "bin", "Debug", platform.ToFramework (), runtimeIdentifiers, "ContainerApp.app"); + + Clean (extensionProjectPath); + Clean (containerProjectPath); + + var properties = GetDefaultProperties (runtimeIdentifiers); + + DotNet.AssertBuild (containerProjectPath, properties); + + var extensionPath = Path.Combine (appPath, GetPlugInsRelativePath (platform), "AppExtension.appex"); + Assert.That (Directory.Exists (extensionPath), Is.True, $"App extension directory does not exist: {extensionPath}"); + + if (CanExecute (platform, runtimeIdentifiers)) { + ExecuteProjectWithMagicWordAndAssert (containerProjectPath, platform, runtimeIdentifiers); + + if (platform == ApplePlatform.MacOSX) + TriggerSpotlightImportExtension (appPath, extensionPath); + } + } + + void TriggerSpotlightImportExtension (string appPath, string extensionPath) + { + int exitCode; + StringBuilder output; + + var lsregister = "/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister"; + Console.WriteLine ($"Executing: {lsregister} -f {appPath}"); + exitCode = ExecutionHelper.Execute (lsregister, new [] { "-f", appPath }, out output, (string) null!); + Console.WriteLine ($"Exit code: {exitCode}"); + Console.WriteLine (output); + + Console.WriteLine ($"Executing: pluginkit -a {extensionPath}"); + exitCode = ExecutionHelper.Execute ("pluginkit", new [] { "-a", extensionPath }, out output, (string) null!); + Console.WriteLine ($"Exit code: {exitCode}"); + Console.WriteLine (output); + + // Create a test file with the .xspotlight extension. + var tmpDir = Cache.CreateTemporaryDirectory (); + var testFile = Path.Combine (tmpDir, "test.xspotlight"); + File.WriteAllText (testFile, "spotlight test content"); + + // Use mdimport -t -o to test-import the file and capture the + // imported attributes to a plist file. This verifies the UTI + // (com.xamarin.test-spotlight) is correctly registered. + var outFile = Path.Combine (tmpDir, "attributes.plist"); + Console.WriteLine ($"Executing: mdimport -t -d3 -o {outFile} {testFile}"); + exitCode = ExecutionHelper.Execute ("mdimport", new [] { "-t", "-d3", "-o", outFile, testFile }, out output, (string) null!, timeout: TimeSpan.FromSeconds (15)); + Console.WriteLine ($"Exit code: {exitCode}"); + Console.WriteLine (output); + Assert.That (File.Exists (outFile), Is.True, "mdimport did not produce an output file"); + var attributes = File.ReadAllText (outFile); + Console.WriteLine (attributes); + Assert.That (attributes, Does.Contain ("com.xamarin.test-spotlight"), + "The imported attributes should contain the custom UTI."); + + var logStartTime = DateTime.Now; + + // Use mdimport -m to trigger the modern (app extension based) importer, + // which actually launches our extension process. + Console.WriteLine ($"Executing: mdimport -m -y com.xamarin.test-spotlight -u file://{testFile}"); + exitCode = ExecutionHelper.Execute ("mdimport", new [] { "-m", "-y", "com.xamarin.test-spotlight", "-u", $"file://{testFile}" }, out output, (string) null!, timeout: TimeSpan.FromSeconds (15)); + Console.WriteLine ($"Exit code: {exitCode}"); + Console.WriteLine (output); + + // Check system logs for evidence the extension process was launched. + var logEnd = DateTime.Now; + var logStartStr = logStartTime.ToString ("yyyy-MM-dd HH:mm:ss"); + var logEndStr = logEnd.ToString ("yyyy-MM-dd HH:mm:ss"); + var logArgs = new [] { + "show", + "--predicate", "eventMessage CONTAINS \"SpotlightImportExtensionTest\"", + "--start", logStartStr, + "--end", logEndStr, + }; + Console.WriteLine ($"Executing: log {string.Join (" ", logArgs)}"); + exitCode = ExecutionHelper.Execute ("log", logArgs, out output, (string) null!); + Console.WriteLine ($"Exit code: {exitCode}"); + var logText = output.ToString (); + Console.WriteLine (logText); + Assert.That (logText, Does.Contain ("SpotlightImportExtensionTest"), + "The Spotlight import extension process was not launched by the system."); + } + + static string? FindCodesignCertificate () + { + var rv = ExecutionHelper.Execute ("security", new [] { "find-identity", "-v", "-p", "codesigning" }, out var output); + if (rv != 0) + return null; + + // Parse output lines like: + // 1) C884... "Apple Development: Name (ID)" + // Skip ad-hoc ("-") and pick the first valid certificate. + foreach (var line in output.ToString ().Split ('\n')) { + var trimmed = line.Trim (); + if (!trimmed.Contains (')')) + continue; + var parts = trimmed.Split (')'); + if (parts.Length < 2) + continue; + var afterParen = parts [0]; + var sha = afterParen.Split (' ').LastOrDefault ()?.Trim (); + if (string.IsNullOrEmpty (sha) || sha.Length != 40) + continue; + if (trimmed.Contains ("\"Apple Development:") || trimmed.Contains ("\"Developer ID Application:")) + return sha; + } + + return null; + } + IEnumerable AllTypes (ModuleDefinition module) { foreach (var type in module.Types) { @@ -129,4 +408,3 @@ IEnumerable InnerTypes (TypeDefinition type) } } } - diff --git a/tests/monotouch-test/AudioUnit/AppExtensionSmokeTest.cs b/tests/monotouch-test/AudioUnit/AppExtensionSmokeTest.cs new file mode 100644 index 000000000000..6dabd81c0195 --- /dev/null +++ b/tests/monotouch-test/AudioUnit/AppExtensionSmokeTest.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Linq; + +using Foundation; + +namespace MonoTouchFixtures.AudioUnit { + [TestFixture] + [Preserve (AllMembers = true)] + public class AppExtensionSmokeTest { + [Test] + public void RunsInsideAudioUnitAppExtension () + { + var bundlePath = NSBundle.MainBundle.BundlePath; + if (!bundlePath.EndsWith (".appex", StringComparison.OrdinalIgnoreCase)) + Assert.Ignore ("This test only applies when monotouch-test is hosted from an app extension."); + + Assert.That (NSBundle.MainBundle.InfoDictionary? ["NSExtension"], Is.Not.Null, "NSExtension"); + Assert.That (TestLoader.GetTestAssemblies ().Count (), Is.GreaterThanOrEqualTo (3), "Test assembly count"); + } + } +} diff --git a/tests/monotouch-test/CoreFoundation/BundleTest.cs b/tests/monotouch-test/CoreFoundation/BundleTest.cs index 6e7cd189b4fe..d17442ffc33d 100644 --- a/tests/monotouch-test/CoreFoundation/BundleTest.cs +++ b/tests/monotouch-test/CoreFoundation/BundleTest.cs @@ -9,7 +9,14 @@ namespace MonoTouchFixtures.CoreFoundation { [TestFixture] [Preserve (AllMembers = true)] public class BundleTest { +#if APP_EXTENSION + const string ExpectedAppName = "monotouchtest.appex"; + const string ExpectedBundleId = "com.xamarin.monotouch-test.AudioUnitExtension"; +#else const string ExpectedAppName = "monotouchtest.app"; + const string ExpectedBundleId = "com.xamarin.monotouch-test"; +#endif + const string ExpectedExecutableName = "monotouchtest"; [Test] public void TestGetAll () @@ -70,8 +77,7 @@ public void TestGetBundleIdNull (string id) public void TestGetMain () { var main = CFBundle.GetMain (); - var expectedBundleId = "com.xamarin.monotouch-test"; - Assert.That (main.Identifier, Is.EqualTo (expectedBundleId)); + Assert.That (main.Identifier, Is.EqualTo (ExpectedBundleId)); Assert.That (main.HasLoadedExecutable, Is.True); } @@ -87,9 +93,9 @@ public void TestExecutableUrl () { var main = CFBundle.GetMain (); #if __MACCATALYST__ || __MACOS__ - var executableRelativePath = Path.Combine (ExpectedAppName, "Contents", "MacOS", "monotouchtest"); + var executableRelativePath = Path.Combine (ExpectedAppName, "Contents", "MacOS", ExpectedExecutableName); #else - var executableRelativePath = Path.Combine (ExpectedAppName, "monotouchtest"); + var executableRelativePath = Path.Combine (ExpectedAppName, ExpectedExecutableName); #endif var alternativeRelativePath = executableRelativePath.Replace (ExpectedAppName, "PublicStaging.app"); Assert.That (main.ExecutableUrl.ToString (), Does.Contain (executableRelativePath).Or.Contain (alternativeRelativePath)); diff --git a/tests/monotouch-test/CoreServices/FSEventStreamTest.cs b/tests/monotouch-test/CoreServices/FSEventStreamTest.cs index a7401e0c8f32..3f2d7f9214ef 100644 --- a/tests/monotouch-test/CoreServices/FSEventStreamTest.cs +++ b/tests/monotouch-test/CoreServices/FSEventStreamTest.cs @@ -2,7 +2,7 @@ // Unit tests for FSEventStream // -#if __MACOS__ +#if __MACOS__ && !APP_EXTENSION using System.IO; using System.Runtime.InteropServices; diff --git a/tests/monotouch-test/Foundation/ThreadTest.cs b/tests/monotouch-test/Foundation/ThreadTest.cs index 93028e29763b..c9a5766e28d1 100644 --- a/tests/monotouch-test/Foundation/ThreadTest.cs +++ b/tests/monotouch-test/Foundation/ThreadTest.cs @@ -24,6 +24,9 @@ public void MainThread () } [Test] +#if APP_EXTENSION + [Ignore ("App extensions don't have an entry assembly.")] +#endif public void GetEntryAssemblyReturnsOk () { Assert.That (Assembly.GetEntryAssembly (), Is.Not.Null); diff --git a/tests/monotouch-test/MediaAccessibility/ImageCaptioningTest.cs b/tests/monotouch-test/MediaAccessibility/ImageCaptioningTest.cs index 82da8929bd03..80525b0d0256 100644 --- a/tests/monotouch-test/MediaAccessibility/ImageCaptioningTest.cs +++ b/tests/monotouch-test/MediaAccessibility/ImageCaptioningTest.cs @@ -55,6 +55,9 @@ public void GetMetadataTagPath () } [Test] +#if APP_EXTENSION + [Ignore ("App extension bundle resources are read-only.")] +#endif public void SetCaption () { TestRuntime.AssertXcodeVersion (11, 0); diff --git a/tests/monotouch-test/Network/NWBrowserTest.cs b/tests/monotouch-test/Network/NWBrowserTest.cs index 08798722d889..aacc218868c2 100644 --- a/tests/monotouch-test/Network/NWBrowserTest.cs +++ b/tests/monotouch-test/Network/NWBrowserTest.cs @@ -64,6 +64,9 @@ public void TestStartNoQ () } [Test] +#if APP_EXTENSION + [Ignore ("Local network access is not available in app extensions.")] +#endif public void TestStateChangesHandler () { // This test may cause cause a dialog asking for access to the local network. The test will work if access is either granted or diff --git a/tests/monotouch-test/NetworkExtension/VpnManagerTest.cs b/tests/monotouch-test/NetworkExtension/VpnManagerTest.cs index cb742738da41..f877199560a6 100644 --- a/tests/monotouch-test/NetworkExtension/VpnManagerTest.cs +++ b/tests/monotouch-test/NetworkExtension/VpnManagerTest.cs @@ -19,6 +19,9 @@ namespace MonoTouchFixtures.NetworkExtension { public class VpnManagerTest { [Test] +#if APP_EXTENSION + [Ignore ("App extensions don't have VPN entitlements.")] +#endif public void SharedManager () { TestRuntime.AssertSystemVersion (ApplePlatform.iOS, 8, 0, throwIfOtherPlatform: false); diff --git a/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs b/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs index c72e97e531d4..9e36c1660a30 100644 --- a/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs +++ b/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs @@ -2245,7 +2245,7 @@ public void TestCtors () } // This test uses Assembly.LoadFrom, which isn't supported with NativeAOT -#if __MACOS__ && !NATIVEAOT +#if __MACOS__ && !NATIVEAOT && !APP_EXTENSION [Test] [UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "This test loads an assembly dynamically, so it's expected to not be trimmer safe. It works though, so unless something changes, we're going to assume it's trimmer-compatible.")] [UnconditionalSuppressMessage ("Trimming", "IL2072", Justification = "This test loads an assembly dynamically, so it's expected to not be trimmer safe. It works though, so unless something changes, we're going to assume it's trimmer-compatible.")] diff --git a/tests/monotouch-test/ScreenTime/STWebHistoryTest.cs b/tests/monotouch-test/ScreenTime/STWebHistoryTest.cs index 2fa6b305f5b0..018d4734a9d6 100644 --- a/tests/monotouch-test/ScreenTime/STWebHistoryTest.cs +++ b/tests/monotouch-test/ScreenTime/STWebHistoryTest.cs @@ -21,7 +21,8 @@ public class STWebHistoryTest { public void Create_WithBundleIdentifier () { TestRuntime.AssertXcodeVersion (16, 3); - using var obj = STWebHistory.Create ("com.xamarin.monotouch-test", out var error); + var bundleId = NSBundle.MainBundle.BundleIdentifier; + using var obj = STWebHistory.Create (bundleId, out var error); Assert.That (obj, Is.Not.Null, "Object"); Assert.That (error, Is.Null, "Error"); } @@ -30,7 +31,8 @@ public void Create_WithBundleIdentifier () public void Create_WithBundleIdentifierAndProfile () { TestRuntime.AssertXcodeVersion (16, 3); - using var obj = STWebHistory.Create ("com.xamarin.monotouch-test", (NSString) "profile", out var error); + var bundleId = NSBundle.MainBundle.BundleIdentifier; + using var obj = STWebHistory.Create (bundleId, (NSString) "profile", out var error); Assert.That (obj, Is.Not.Null, "Object"); Assert.That (error, Is.Null, "Error"); } diff --git a/tests/monotouch-test/SearchKit/SearchKitTest.cs b/tests/monotouch-test/SearchKit/SearchKitTest.cs index 0d4c842e9561..8a758ce6c47b 100644 --- a/tests/monotouch-test/SearchKit/SearchKitTest.cs +++ b/tests/monotouch-test/SearchKit/SearchKitTest.cs @@ -8,7 +8,7 @@ namespace apitest { [TestFixture] [Preserve (AllMembers = true)] public class SearchKitTests { - string path = $"/tmp/mmptest-my-{System.Diagnostics.Process.GetCurrentProcess ().Id}.index"; + string path = Path.Combine (Path.GetTempPath (), $"mmptest-my-{System.Diagnostics.Process.GetCurrentProcess ().Id}.index"); [SetUp] public void Setup () diff --git a/tests/monotouch-test/SystemConfiguration/CaptiveNetworkTest.cs b/tests/monotouch-test/SystemConfiguration/CaptiveNetworkTest.cs index e6da5b5ef926..2669c48275c2 100644 --- a/tests/monotouch-test/SystemConfiguration/CaptiveNetworkTest.cs +++ b/tests/monotouch-test/SystemConfiguration/CaptiveNetworkTest.cs @@ -123,6 +123,9 @@ public void SetSupportedSSIDs_Null () } [Test] +#if APP_EXTENSION + [Ignore ("App extensions don't have CaptiveNetwork entitlements.")] +#endif public void SetSupportedSSIDs () { TestRuntime.AssertSystemVersion (ApplePlatform.MacOSX, 10, 8, throwIfOtherPlatform: false); diff --git a/tests/monotouch-test/WebKit/NSAttributedStringCatagoryTest.cs b/tests/monotouch-test/WebKit/NSAttributedStringCatagoryTest.cs index a1c3a813de0f..3f04c26550eb 100644 --- a/tests/monotouch-test/WebKit/NSAttributedStringCatagoryTest.cs +++ b/tests/monotouch-test/WebKit/NSAttributedStringCatagoryTest.cs @@ -1,4 +1,7 @@ #if __IOS__ || MONOMAC +#if APP_EXTENSION +// WebKit's web content process is not available in app extensions. +#else using System.IO; @@ -42,4 +45,5 @@ public void LoadHtmlAsync_NSUrl () } } } -#endif +#endif // !APP_EXTENSION +#endif // __IOS__ || MONOMAC diff --git a/tests/monotouch-test/dotnet/extensions/audio-unit/AppExtension-Info.plist b/tests/monotouch-test/dotnet/extensions/audio-unit/AppExtension-Info.plist new file mode 100644 index 000000000000..1145090e37b6 --- /dev/null +++ b/tests/monotouch-test/dotnet/extensions/audio-unit/AppExtension-Info.plist @@ -0,0 +1,42 @@ + + + + + + CFBundleDisplayName + MonoTouchTestAudioUnitExtension + CFBundleName + MonoTouchTestAudioUnitExtension + NSExtension + + NSExtensionAttributes + + AudioComponents + + + type + aufx + subtype + test + manufacturer + Xmrn + name + XamarinTest: MonoTouchTestExtension + version + 1 + sandboxSafe + + tags + + Effects + + + + + NSExtensionPointIdentifier + com.apple.AudioUnit + NSExtensionPrincipalClass + MonotouchTestAudioUnitFactory + + + diff --git a/tests/monotouch-test/dotnet/extensions/audio-unit/AppExtension-shared.csproj b/tests/monotouch-test/dotnet/extensions/audio-unit/AppExtension-shared.csproj new file mode 100644 index 000000000000..eacb484703f8 --- /dev/null +++ b/tests/monotouch-test/dotnet/extensions/audio-unit/AppExtension-shared.csproj @@ -0,0 +1,65 @@ + + + + + Library + true + monotouchtest + monotouchtestappextension + com.xamarin.monotouch-test.AudioUnitExtension + 1 + 1.0 + + + + + + + + + + + + + + + + + + AudioUnitTestExtension.cs + + + + + + Configuration.cs + + + ConfigurationNUnit.cs + + + ExecutionHelper.cs + + + PlatformInfo.cs + + + Extensions.cs + + + Cache.cs + + + Execution.cs + + + OSPlatformAttributeExtensions.cs + + + TargetFramework.cs + + + StringUtils.cs + + + diff --git a/tests/monotouch-test/dotnet/extensions/audio-unit/AudioUnitTestExtension.cs b/tests/monotouch-test/dotnet/extensions/audio-unit/AudioUnitTestExtension.cs new file mode 100644 index 000000000000..e5596c84967c --- /dev/null +++ b/tests/monotouch-test/dotnet/extensions/audio-unit/AudioUnitTestExtension.cs @@ -0,0 +1,186 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +using AudioToolbox; +using AudioUnit; +using AVFoundation; +using Foundation; +using MonoTouch.NUnit.UI; +using ObjCRuntime; + +namespace MonotouchTest.AudioUnitExtensionHost { + static class ExtensionTestHost { + const string logPrefix = "[monotouch-test-audio-unit-extension]"; + static bool hasRun; + static bool debugHooksInstalled; + static readonly object guard = new object (); + + // xamarin_log is a non-variadic native function in libxamarin + // that calls NSLog internally. We use this instead of P/Invoking + // NSLog directly, because NSLog is variadic, and P/Invoke doesn't + // handle variadic functions correctly on ARM64. + [DllImport ("__Internal")] + static extern void xamarin_log (IntPtr unicodeMessage); + + [DllImport ("/usr/lib/libSystem.B.dylib")] + static extern unsafe int atexit (delegate* unmanaged callback); + + static void Log (string message) + { + Console.WriteLine (message); + var logMessage = $"ZZZZ {message}"; + unsafe { + fixed (char* ptr = logMessage) + xamarin_log ((IntPtr) ptr); + } + } + + static void SafeLog (string message) + { + try { + Log (message); + } catch (Exception ex) { + Console.WriteLine ($"{message}{Environment.NewLine}{ex}"); + } + } + + public static unsafe void InstallDebugHooks () + { + lock (guard) { + if (debugHooksInstalled) + return; + debugHooksInstalled = true; + } + + Runtime.MarshalObjectiveCException += ObjectiveCExceptionMarshaled; + Runtime.MarshalManagedException += ManagedExceptionMarshaled; + + var rv = atexit (&AtExitCallback); + SafeLog ($"{logPrefix} Installed debug hooks (atexit registration result: {rv})."); + } + + static void ObjectiveCExceptionMarshaled (object? sender, MarshalObjectiveCExceptionEventArgs args) + { + var stackTrace = args.Exception.CallStackSymbols is null + ? new StackTrace (1, true).ToString () + : string.Join (Environment.NewLine, args.Exception.CallStackSymbols); + SafeLog ($"{logPrefix} Objective-C exception marshaled. Mode: {args.ExceptionMode}. Name: {args.Exception.Name}. Reason: {args.Exception.Reason}{Environment.NewLine}{stackTrace}"); + } + + static void ManagedExceptionMarshaled (object? sender, MarshalManagedExceptionEventArgs args) + { + SafeLog ($"{logPrefix} Managed exception marshaled. Mode: {args.ExceptionMode}.{Environment.NewLine}{args.Exception}"); + } + + [UnmanagedCallersOnly] + static void AtExitCallback () + { + SafeLog ($"{logPrefix} Process is exiting.{Environment.NewLine}{new StackTrace (1, true)}"); + } + + static string? GetTestName () + { + var testName = NSUserDefaults.StandardUserDefaults.StringForKey ("test.name"); + if (string.IsNullOrWhiteSpace (testName)) + return null; + SafeLog ($"{logPrefix} Using test filter from NSUserDefaults: {testName}"); + return testName; + } + + public static async Task RunOnce () + { + lock (guard) { + if (hasRun) + return; + hasRun = true; + } + + var testName = GetTestName (); + + var runner = ExtensionTestRunner.CreateHeadlessRunner (TestLoader.GetTestAssemblies (), testName, Log); + runner.LogCallback = Log; + + var runDescription = string.IsNullOrEmpty (testName) ? "all monotouch-test tests" : testName; + var startMessage = $"{logPrefix} Starting monotouch-test audio unit extension test run ({runDescription})"; + Log (startMessage); + try { + await ExtensionTestRunner.RunAsync (runner); + var summary = $"{logPrefix} Finished monotouch-test audio unit extension test run. Passed: {runner.PassedCount} Failed: {runner.FailedCount} Ignored: {runner.IgnoredCount} Inconclusive: {runner.InconclusiveCount}"; + Log (summary); + } catch (Exception ex) { + var failure = $"{logPrefix} Extension test run failed: {ex}"; + Log (failure); + } + } + } + + + [Register ("MonotouchTestAudioUnitFactory")] + public class MonotouchTestAudioUnitFactory : NSObject, IAUAudioUnitFactory { + public MonotouchTestAudioUnitFactory (NativeHandle handle) : base (handle) + { + } + + public AUAudioUnit CreateAudioUnit (AudioComponentDescription desc, out NSError error) + { + ExtensionTestHost.InstallDebugHooks (); + error = null; + var audioUnit = new MonotouchTestAudioUnit (desc, out error); + if (error is null) + Task.Run (async () => { + await Task.Delay (1000); + await ExtensionTestHost.RunOnce (); + }); + return audioUnit; + } + + [Export ("beginRequestWithExtensionContext:")] + public void BeginRequestWithExtensionContext (NSExtensionContext context) + { + ExtensionTestHost.InstallDebugHooks (); + Task.Run (async () => await ExtensionTestHost.RunOnce ()); + } + } + + [Register ("MonotouchTestAudioUnit")] + public class MonotouchTestAudioUnit : AUAudioUnit { + AUAudioUnitBusArray inputBusArray; + AUAudioUnitBusArray outputBusArray; + + public MonotouchTestAudioUnit (AudioComponentDescription componentDescription, out NSError error) + : base (componentDescription, AudioComponentInstantiationOptions.OutOfProcess, out error) + { + var format = new AVAudioFormat (44100, 2); + var inputBus = new AUAudioUnitBus (format, out error); + var outputBus = new AUAudioUnitBus (format, out error); + inputBusArray = new AUAudioUnitBusArray (this, AUAudioUnitBusType.Input, new [] { inputBus }); + outputBusArray = new AUAudioUnitBusArray (this, AUAudioUnitBusType.Output, new [] { outputBus }); + } + + public MonotouchTestAudioUnit (NativeHandle handle) : base (handle) + { + } + + public override AUAudioUnitBusArray InputBusses => inputBusArray; + + public override AUAudioUnitBusArray OutputBusses => outputBusArray; + + public override AUInternalRenderBlock InternalRenderBlock { + get { + return (ref AudioUnitRenderActionFlags actionFlags, ref AudioTimeStamp timestamp, + uint frameCount, nint outputBusNumber, AudioBuffers outputData, + AURenderEventEnumerator realtimeEventListHead, AURenderPullInputBlock pullInputBlock) => { + if (pullInputBlock is null) + return AudioUnitStatus.NoError; + pullInputBlock (ref actionFlags, ref timestamp, frameCount, 0, outputData); + return AudioUnitStatus.NoError; + }; + } + } + } +} diff --git a/tests/monotouch-test/dotnet/extensions/audio-unit/ContainerApp-shared.csproj b/tests/monotouch-test/dotnet/extensions/audio-unit/ContainerApp-shared.csproj new file mode 100644 index 000000000000..ba8612fbd2d1 --- /dev/null +++ b/tests/monotouch-test/dotnet/extensions/audio-unit/ContainerApp-shared.csproj @@ -0,0 +1,24 @@ + + + + + Exe + ContainerApp + com.xamarin.monotouch-test.audiounit.containerapp + 1.0 + + + + + + + ContainerApp.cs + + + + + + true + + + diff --git a/tests/monotouch-test/dotnet/extensions/audio-unit/ContainerApp.cs b/tests/monotouch-test/dotnet/extensions/audio-unit/ContainerApp.cs new file mode 100644 index 000000000000..e8cbc4629402 --- /dev/null +++ b/tests/monotouch-test/dotnet/extensions/audio-unit/ContainerApp.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Threading; + +using AudioUnit; +using Foundation; + +namespace MonotouchTest.AudioUnitExtensionHost { + public class Program { + const string logPrefix = "[monotouch-test-audio-unit-container]"; + + static int Main (string [] args) + { + GC.KeepAlive (typeof (NSObject)); // prevent linking away the platform assembly + + if (ShouldRunExtensionTests (args)) + return RunExtensionTests (); + + Console.WriteLine (Environment.GetEnvironmentVariable ("MAGIC_WORD")); + + return args.Length; + } + + static bool ShouldRunExtensionTests (string [] args) + { + if (Environment.GetEnvironmentVariable ("RUN_EXTENSION_TESTS") == "1") + return true; + + foreach (var arg in args) { + if (arg == "--run-extension-tests") + return true; + } + + return false; + } + + static int RunExtensionTests () + { + var desc = new AudioComponentDescription { + ComponentType = AudioComponentType.Effect, + ComponentSubType = (AudioUnitSubType) FourCC ("test"), + ComponentManufacturer = (AudioComponentManufacturerType) (uint) FourCC ("Xmrn"), + }; + + Console.WriteLine ($"{logPrefix} Instantiating audio unit extension: {desc}"); + + NSError? error = null; + AUAudioUnit? audioUnit = null; + using var instantiated = new ManualResetEventSlim (); + AUAudioUnit.FromComponentDescription (desc, AudioComponentInstantiationOptions.OutOfProcess, (au, err) => { + audioUnit = au; + error = err; + instantiated.Set (); + }); + + while (!instantiated.IsSet) + NSRunLoop.Current.RunUntil (NSDate.FromTimeIntervalSinceNow (0.25)); + + if (error is not null || audioUnit is null || audioUnit.Handle == IntPtr.Zero) { + Console.Error.WriteLine ($"{logPrefix} Failed to instantiate the audio unit extension: {error}"); + return 1; + } + + using (audioUnit) { +#if __MACOS__ || __MACCATALYST__ + Console.WriteLine ($"{logPrefix} Loaded AudioUnit out-of-process: {!audioUnit.IsLoadedInProcess}"); +#endif + Console.WriteLine ($"{logPrefix} Holding the host open while the extension runs tests."); + + while (true) + NSRunLoop.Current.RunUntil (NSDate.FromTimeIntervalSinceNow (0.25)); + } + } + + static int FourCC (string value) + { + if (value.Length != 4) + throw new ArgumentException ("A FourCC must be exactly four characters long.", nameof (value)); + + return (value [0] << 24) | (value [1] << 16) | (value [2] << 8) | value [3]; + } + } +} diff --git a/tests/monotouch-test/dotnet/extensions/audio-unit/MacCatalyst/AppExtension/AppExtension.csproj b/tests/monotouch-test/dotnet/extensions/audio-unit/MacCatalyst/AppExtension/AppExtension.csproj new file mode 100644 index 000000000000..07af4de0abf0 --- /dev/null +++ b/tests/monotouch-test/dotnet/extensions/audio-unit/MacCatalyst/AppExtension/AppExtension.csproj @@ -0,0 +1,8 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-maccatalyst + + + + diff --git a/tests/monotouch-test/dotnet/extensions/audio-unit/MacCatalyst/AppExtension/AppExtension.slnx b/tests/monotouch-test/dotnet/extensions/audio-unit/MacCatalyst/AppExtension/AppExtension.slnx new file mode 100644 index 000000000000..84162c981590 --- /dev/null +++ b/tests/monotouch-test/dotnet/extensions/audio-unit/MacCatalyst/AppExtension/AppExtension.slnx @@ -0,0 +1,3 @@ + + + diff --git a/tests/monotouch-test/dotnet/extensions/audio-unit/MacCatalyst/ContainerApp/ContainerApp.csproj b/tests/monotouch-test/dotnet/extensions/audio-unit/MacCatalyst/ContainerApp/ContainerApp.csproj new file mode 100644 index 000000000000..330f667a2281 --- /dev/null +++ b/tests/monotouch-test/dotnet/extensions/audio-unit/MacCatalyst/ContainerApp/ContainerApp.csproj @@ -0,0 +1,8 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-maccatalyst + + + + diff --git a/tests/monotouch-test/dotnet/extensions/audio-unit/MacCatalyst/ContainerApp/ContainerApp.slnx b/tests/monotouch-test/dotnet/extensions/audio-unit/MacCatalyst/ContainerApp/ContainerApp.slnx new file mode 100644 index 000000000000..938f40d69d61 --- /dev/null +++ b/tests/monotouch-test/dotnet/extensions/audio-unit/MacCatalyst/ContainerApp/ContainerApp.slnx @@ -0,0 +1,4 @@ + + + + diff --git a/tests/monotouch-test/dotnet/extensions/audio-unit/MacCatalyst/Makefile b/tests/monotouch-test/dotnet/extensions/audio-unit/MacCatalyst/Makefile new file mode 100644 index 000000000000..359523bb0cb1 --- /dev/null +++ b/tests/monotouch-test/dotnet/extensions/audio-unit/MacCatalyst/Makefile @@ -0,0 +1,16 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +include ../shared.mk + +# Build and run monotouch-test from the audio unit extension for this platform: +# make build +# make run +# +# Select the runtime, registrar and configuration using TEST_VARIATION (see +# tests/common/test-variations.csproj for the full list), e.g.: +# make run TEST_VARIATION=coreclr +# make run TEST_VARIATION='trimmable-static-registrar|release' +# +# Set TEST_FILTER to run a specific test instead of the full suite. +# Any arguments to 'dotnet build' can be set using the BUILD_PARAMETERS variable. diff --git a/tests/monotouch-test/dotnet/extensions/audio-unit/Makefile b/tests/monotouch-test/dotnet/extensions/audio-unit/Makefile new file mode 100644 index 000000000000..8000425106f8 --- /dev/null +++ b/tests/monotouch-test/dotnet/extensions/audio-unit/Makefile @@ -0,0 +1,10 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +TOP=../../../../.. +DOTNET_PLATFORMS=macOS MacCatalyst iOS tvOS +DOTNET_DESKTOP_PLATFORMS=macOS MacCatalyst + +include $(TOP)/tests/common/shared-dotnet-test.mk + +TESTNAME=monotouch-test-audio-unit-extension diff --git a/tests/monotouch-test/dotnet/extensions/audio-unit/iOS/AppExtension/AppExtension.csproj b/tests/monotouch-test/dotnet/extensions/audio-unit/iOS/AppExtension/AppExtension.csproj new file mode 100644 index 000000000000..6c140fd04db7 --- /dev/null +++ b/tests/monotouch-test/dotnet/extensions/audio-unit/iOS/AppExtension/AppExtension.csproj @@ -0,0 +1,8 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-ios + + + + diff --git a/tests/monotouch-test/dotnet/extensions/audio-unit/iOS/AppExtension/AppExtension.slnx b/tests/monotouch-test/dotnet/extensions/audio-unit/iOS/AppExtension/AppExtension.slnx new file mode 100644 index 000000000000..84162c981590 --- /dev/null +++ b/tests/monotouch-test/dotnet/extensions/audio-unit/iOS/AppExtension/AppExtension.slnx @@ -0,0 +1,3 @@ + + + diff --git a/tests/monotouch-test/dotnet/extensions/audio-unit/iOS/ContainerApp/ContainerApp.csproj b/tests/monotouch-test/dotnet/extensions/audio-unit/iOS/ContainerApp/ContainerApp.csproj new file mode 100644 index 000000000000..ac0ef5a73797 --- /dev/null +++ b/tests/monotouch-test/dotnet/extensions/audio-unit/iOS/ContainerApp/ContainerApp.csproj @@ -0,0 +1,8 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-ios + + + + diff --git a/tests/monotouch-test/dotnet/extensions/audio-unit/iOS/ContainerApp/ContainerApp.slnx b/tests/monotouch-test/dotnet/extensions/audio-unit/iOS/ContainerApp/ContainerApp.slnx new file mode 100644 index 000000000000..938f40d69d61 --- /dev/null +++ b/tests/monotouch-test/dotnet/extensions/audio-unit/iOS/ContainerApp/ContainerApp.slnx @@ -0,0 +1,4 @@ + + + + diff --git a/tests/monotouch-test/dotnet/extensions/audio-unit/iOS/Makefile b/tests/monotouch-test/dotnet/extensions/audio-unit/iOS/Makefile new file mode 100644 index 000000000000..359523bb0cb1 --- /dev/null +++ b/tests/monotouch-test/dotnet/extensions/audio-unit/iOS/Makefile @@ -0,0 +1,16 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +include ../shared.mk + +# Build and run monotouch-test from the audio unit extension for this platform: +# make build +# make run +# +# Select the runtime, registrar and configuration using TEST_VARIATION (see +# tests/common/test-variations.csproj for the full list), e.g.: +# make run TEST_VARIATION=coreclr +# make run TEST_VARIATION='trimmable-static-registrar|release' +# +# Set TEST_FILTER to run a specific test instead of the full suite. +# Any arguments to 'dotnet build' can be set using the BUILD_PARAMETERS variable. diff --git a/tests/monotouch-test/dotnet/extensions/audio-unit/macOS/AppExtension/AppExtension.csproj b/tests/monotouch-test/dotnet/extensions/audio-unit/macOS/AppExtension/AppExtension.csproj new file mode 100644 index 000000000000..8c852d32d562 --- /dev/null +++ b/tests/monotouch-test/dotnet/extensions/audio-unit/macOS/AppExtension/AppExtension.csproj @@ -0,0 +1,8 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-macos + + + + diff --git a/tests/monotouch-test/dotnet/extensions/audio-unit/macOS/AppExtension/AppExtension.slnx b/tests/monotouch-test/dotnet/extensions/audio-unit/macOS/AppExtension/AppExtension.slnx new file mode 100644 index 000000000000..84162c981590 --- /dev/null +++ b/tests/monotouch-test/dotnet/extensions/audio-unit/macOS/AppExtension/AppExtension.slnx @@ -0,0 +1,3 @@ + + + diff --git a/tests/monotouch-test/dotnet/extensions/audio-unit/macOS/ContainerApp/ContainerApp.csproj b/tests/monotouch-test/dotnet/extensions/audio-unit/macOS/ContainerApp/ContainerApp.csproj new file mode 100644 index 000000000000..35db614f4799 --- /dev/null +++ b/tests/monotouch-test/dotnet/extensions/audio-unit/macOS/ContainerApp/ContainerApp.csproj @@ -0,0 +1,8 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-macos + + + + diff --git a/tests/monotouch-test/dotnet/extensions/audio-unit/macOS/ContainerApp/ContainerApp.slnx b/tests/monotouch-test/dotnet/extensions/audio-unit/macOS/ContainerApp/ContainerApp.slnx new file mode 100644 index 000000000000..938f40d69d61 --- /dev/null +++ b/tests/monotouch-test/dotnet/extensions/audio-unit/macOS/ContainerApp/ContainerApp.slnx @@ -0,0 +1,4 @@ + + + + diff --git a/tests/monotouch-test/dotnet/extensions/audio-unit/macOS/Makefile b/tests/monotouch-test/dotnet/extensions/audio-unit/macOS/Makefile new file mode 100644 index 000000000000..359523bb0cb1 --- /dev/null +++ b/tests/monotouch-test/dotnet/extensions/audio-unit/macOS/Makefile @@ -0,0 +1,16 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +include ../shared.mk + +# Build and run monotouch-test from the audio unit extension for this platform: +# make build +# make run +# +# Select the runtime, registrar and configuration using TEST_VARIATION (see +# tests/common/test-variations.csproj for the full list), e.g.: +# make run TEST_VARIATION=coreclr +# make run TEST_VARIATION='trimmable-static-registrar|release' +# +# Set TEST_FILTER to run a specific test instead of the full suite. +# Any arguments to 'dotnet build' can be set using the BUILD_PARAMETERS variable. diff --git a/tests/monotouch-test/dotnet/extensions/audio-unit/shared.mk b/tests/monotouch-test/dotnet/extensions/audio-unit/shared.mk new file mode 100644 index 000000000000..cb3f085ae7bc --- /dev/null +++ b/tests/monotouch-test/dotnet/extensions/audio-unit/shared.mk @@ -0,0 +1,128 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +TOP=../../../../../.. + +include $(TOP)/Make.config +include $(TOP)/mk/colors.mk +include $(TOP)/scripts/run-audio-unit-extension-tests/fragment.mk + +# This file is meant to be included from +# tests/monotouch-test/dotnet/extensions/audio-unit//Makefile. + +BINLOG_TIMESTAMP:=$(shell date +%Y-%m-%d-%H%M%S) +AUVAL_ARGUMENTS?=-v aufx test Xmrn +RUN_TIMEOUT_SECONDS?=600 +BUILD_PARAMETERS+=$(BUILD_ARGUMENTS) + +ifeq ($(PLATFORM),) +PLATFORM=$(shell basename "$(CURDIR)") +endif + +LOGFILENAME:=$(TMPDIR)/monotouch-test/extensions/audio-unit/$(PLATFORM)-$(shell date +%Y-%m-%d--%H:%M:%S).log + +# The runtime (CoreCLR/MonoVM), the registrar and other options are selected +# using TEST_VARIATION (see tests/common/test-variations.csproj for the full +# list of variations). Multiple variations can be combined with a pipe +# character, e.g. TEST_VARIATION='trimmable-static-registrar|release'. +ifeq ($(findstring |release|,|$(TEST_VARIATION)|),|release|) +CONFIG=Release +endif + +ifeq ($(CONFIG),) +CONFIG=Debug +endif +CONFIG_ARGUMENT=/p:Configuration=$(CONFIG) + +ifeq ($(RID),) +ifeq ($(PLATFORM),MacCatalyst) +ifeq ($(CONFIG),Release) +RID=maccatalyst-x64;maccatalyst-arm64 +else ifneq ($(UNIVERSAL),) +RID=maccatalyst-x64;maccatalyst-arm64 +else ifeq ($(shell arch),arm64) +RID=maccatalyst-arm64 +else +RID=maccatalyst-x64 +endif +else ifeq ($(PLATFORM),macOS) +ifeq ($(CONFIG),Release) +RID=osx-x64;osx-arm64 +else ifneq ($(UNIVERSAL),) +RID=osx-x64;osx-arm64 +else ifeq ($(shell arch),arm64) +RID=osx-arm64 +else +RID=osx-x64 +endif +else ifeq ($(PLATFORM),iOS) +RID=iossimulator-arm64 +else ifeq ($(PLATFORM),tvOS) +RID=tvossimulator-arm64 +else +RID=unknown-platform-$(PLATFORM) +endif +endif + +ifneq ($(UNIVERSAL),) +UNIVERSAL_ARGUMENT=/p:UniversalBuild=true +endif + +ifneq ($(TEST_VARIATION),) +TEST_VARIATION_ARGUMENT='/p:TestVariation=$(TEST_VARIATION)' +endif + +ifneq ($(findstring ;,$(RID)),) +RID_ARGUMENT=/p:RuntimeIdentifiers=$(RID) +PATH_RID= +else +RID_ARGUMENT=/p:RuntimeIdentifier=$(RID) +PATH_RID=$(RID)/ +endif + +CONTAINER_PROJECT=$(abspath $(CURDIR))/ContainerApp/ContainerApp.csproj +APP_PATH=$(abspath $(CURDIR))/ContainerApp/bin/$(CONFIG)/$(DOTNET_TFM)-$(shell echo $(PLATFORM) | tr 'A-Z' 'a-z')/$(PATH_RID)ContainerApp.app +EXTENSION_PATH=$(APP_PATH)/Contents/PlugIns/monotouchtest.appex +EXECUTABLE=$(APP_PATH)/Contents/MacOS/ContainerApp +LSREGISTER=/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister +prepare: + @# nothing to do here right now + +build: prepare + $(Q) echo "Building extension test project: $(COLOR_GRAY)$(CONTAINER_PROJECT)$(COLOR_CLEAR) [$(CONFIG) $(RID)]" + $(Q) rm -rf "$(abspath $(CURDIR))/AppExtension/bin" "$(abspath $(CURDIR))/AppExtension/obj" "$(abspath $(CURDIR))/ContainerApp/bin" "$(abspath $(CURDIR))/ContainerApp/obj" + $(Q) $(DOTNET) build "$(CONTAINER_PROJECT)" "/bl:$(abspath build-$(BINLOG_TIMESTAMP).binlog)" $(DOTNET_BUILD_VERBOSITY) $(BUILD_PARAMETERS) $(CONFIG_ARGUMENT) $(RID_ARGUMENT) $(UNIVERSAL_ARGUMENT) $(TEST_VARIATION_ARGUMENT) + $(Q) echo "Build completed." + +register-extension: build + $(Q) "$(LSREGISTER)" -f "$(APP_PATH)" + $(Q) pluginkit -a "$(EXTENSION_PATH)" + +run: $(RUN_AUDIO_UNIT_EXTENSION_TESTS) + $(Q) echo "Running monotouch-test from the audio unit extension: $(COLOR_GRAY)$(EXTENSION_PATH)$(COLOR_CLEAR)" + $(Q) echo "Writing output to: $(COLOR_GRAY)$(LOGFILENAME)$(COLOR_CLEAR)" + $(Q) $(RUN_AUDIO_UNIT_EXTENSION_TESTS_EXEC) \ + --platform "$(PLATFORM)" \ + --rid "$(RID)" \ + --config "$(CONFIG)" \ + --app "$(APP_PATH)" \ + --extension "$(EXTENSION_PATH)" \ + --executable "$(EXECUTABLE)" \ + --log-file "$(LOGFILENAME)" \ + --timeout-seconds "$(RUN_TIMEOUT_SECONDS)" \ + --lsregister "$(LSREGISTER)" \ + $(if $(TEST_FILTER),--test-filter "$(TEST_FILTER)") + +run-bare: run + +print-app-path: + @echo $(APP_PATH) + +print-extension-path: + @echo $(EXTENSION_PATH) + +print-executable: + @echo $(EXECUTABLE) + +clean: + rm -Rf AppExtension/bin AppExtension/obj ContainerApp/bin ContainerApp/obj *.binlog diff --git a/tests/monotouch-test/dotnet/extensions/audio-unit/tvOS/AppExtension/AppExtension.csproj b/tests/monotouch-test/dotnet/extensions/audio-unit/tvOS/AppExtension/AppExtension.csproj new file mode 100644 index 000000000000..bb822930b9be --- /dev/null +++ b/tests/monotouch-test/dotnet/extensions/audio-unit/tvOS/AppExtension/AppExtension.csproj @@ -0,0 +1,8 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-tvos + + + + diff --git a/tests/monotouch-test/dotnet/extensions/audio-unit/tvOS/AppExtension/AppExtension.slnx b/tests/monotouch-test/dotnet/extensions/audio-unit/tvOS/AppExtension/AppExtension.slnx new file mode 100644 index 000000000000..84162c981590 --- /dev/null +++ b/tests/monotouch-test/dotnet/extensions/audio-unit/tvOS/AppExtension/AppExtension.slnx @@ -0,0 +1,3 @@ + + + diff --git a/tests/monotouch-test/dotnet/extensions/audio-unit/tvOS/ContainerApp/ContainerApp.csproj b/tests/monotouch-test/dotnet/extensions/audio-unit/tvOS/ContainerApp/ContainerApp.csproj new file mode 100644 index 000000000000..d149e648ebef --- /dev/null +++ b/tests/monotouch-test/dotnet/extensions/audio-unit/tvOS/ContainerApp/ContainerApp.csproj @@ -0,0 +1,8 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-tvos + + + + diff --git a/tests/monotouch-test/dotnet/extensions/audio-unit/tvOS/ContainerApp/ContainerApp.slnx b/tests/monotouch-test/dotnet/extensions/audio-unit/tvOS/ContainerApp/ContainerApp.slnx new file mode 100644 index 000000000000..938f40d69d61 --- /dev/null +++ b/tests/monotouch-test/dotnet/extensions/audio-unit/tvOS/ContainerApp/ContainerApp.slnx @@ -0,0 +1,4 @@ + + + + diff --git a/tests/monotouch-test/dotnet/extensions/audio-unit/tvOS/Makefile b/tests/monotouch-test/dotnet/extensions/audio-unit/tvOS/Makefile new file mode 100644 index 000000000000..359523bb0cb1 --- /dev/null +++ b/tests/monotouch-test/dotnet/extensions/audio-unit/tvOS/Makefile @@ -0,0 +1,16 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +include ../shared.mk + +# Build and run monotouch-test from the audio unit extension for this platform: +# make build +# make run +# +# Select the runtime, registrar and configuration using TEST_VARIATION (see +# tests/common/test-variations.csproj for the full list), e.g.: +# make run TEST_VARIATION=coreclr +# make run TEST_VARIATION='trimmable-static-registrar|release' +# +# Set TEST_FILTER to run a specific test instead of the full suite. +# Any arguments to 'dotnet build' can be set using the BUILD_PARAMETERS variable. diff --git a/tests/monotouch-test/dotnet/shared.csproj b/tests/monotouch-test/dotnet/shared.csproj index 94c12b195172..b58996adceb1 100644 --- a/tests/monotouch-test/dotnet/shared.csproj +++ b/tests/monotouch-test/dotnet/shared.csproj @@ -1,14 +1,13 @@ - Exe + Exe $(DefineConstants);NET - monotouchtest - True - ..\..\..\..\product.snk - monotouchtest - $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)\..\..')) + monotouchtest + True + $(RootTestsDirectory)\..\product.snk + monotouchtest $(RootTestsDirectory)\test-libraries $(RootTestsDirectory)\monotouch-test @@ -109,7 +108,7 @@ SdkVersions.cs - +