protected async Task <ProcessExecutionResult> RunMacCatalystApp( AppBundleInformation appInfo, TimeSpan timeout, IEnumerable <string> appArguments, Dictionary <string, object> environmentVariables, CancellationToken cancellationToken) { using var systemLog = _captureLogFactory.Create( path: _logs.CreateFile("MacCatalyst.system.log", LogType.SystemLog), systemLogPath: SystemLogPath, entireFile: false, LogType.SystemLog); // We need to make the binary executable var binaryPath = Path.Combine(appInfo.AppPath, "Contents", "MacOS", appInfo.BundleExecutable ?? appInfo.AppName); if (File.Exists(binaryPath)) { await _processManager.ExecuteCommandAsync("chmod", new[] { "+x", binaryPath }, _mainLog, TimeSpan.FromSeconds(10), cancellationToken : cancellationToken); } var arguments = new List <string> { "-W", appInfo.LaunchAppPath }; arguments.AddRange(appArguments); var envVars = environmentVariables.ToDictionary( p => p.Key, p => p.Value is bool?p.Value.ToString().ToLowerInvariant() : p.Value.ToString()); // turns "True" to "true" systemLog.StartCapture(); try { return(await _processManager.ExecuteCommandAsync("open", arguments, _mainLog, timeout, envVars, cancellationToken)); } finally { systemLog.StopCapture(waitIfEmpty: TimeSpan.FromSeconds(10)); } }
public async Task<(string DeviceName, TestExecutingResult Result, string ResultMessage)> RunApp( AppBundleInformation appInformation, TestTarget target, TimeSpan timeout, TimeSpan testLaunchTimeout, string? deviceName = null, string? companionDeviceName = null, bool ensureCleanSimulatorState = false, int verbosity = 1, XmlResultJargon xmlResultJargon = XmlResultJargon.xUnit, CancellationToken cancellationToken = default) { var args = new MlaunchArguments { new SetAppArgumentArgument("-connection-mode"), new SetAppArgumentArgument("none"), // This will prevent the app from trying to connect to any IDEs new SetAppArgumentArgument("-autostart", true), new SetEnvVariableArgument(EnviromentVariables.AutoStart, true), new SetAppArgumentArgument("-autoexit", true), new SetEnvVariableArgument(EnviromentVariables.AutoExit, true), new SetAppArgumentArgument("-enablenetwork", true), new SetEnvVariableArgument(EnviromentVariables.EnableNetwork, true), // On macOS we can't edit the TCC database easily // (it requires adding the mac has to be using MDM: https://carlashley.com/2018/09/28/tcc-round-up/) // So by default ignore any tests that would pop up permission dialogs in CI. new SetEnvVariableArgument(EnviromentVariables.DisableSystemPermissionTests, 1), }; for (int i = -1; i < verbosity; i++) { args.Add(new VerbosityArgument()); } var isSimulator = target.IsSimulator(); if (isSimulator) { args.Add(new SetAppArgumentArgument("-hostname:127.0.0.1", true)); args.Add(new SetEnvVariableArgument(EnviromentVariables.HostName, "127.0.0.1")); } else { var ipAddresses = System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName()).AddressList.Select(ip => ip.ToString()); var ips = string.Join(",", ipAddresses); args.Add(new SetAppArgumentArgument($"-hostname:{ips}", true)); args.Add(new SetEnvVariableArgument(EnviromentVariables.HostName, ips)); } var listenerLog = _logs.Create($"test-{target.AsString()}-{_helpers.Timestamp}.log", LogType.TestLog.ToString(), timestamp: true); var (transport, listener, listenerTmpFile) = _listenerFactory.Create(target.ToRunMode(), log: _mainLog, testLog: listenerLog, isSimulator: isSimulator, autoExit: true, xmlOutput: true); // cli always uses xml // Initialize has to be called before we try to get Port (internal implementation of the listener says so) // TODO: Improve this to not get into a broken state - it was really hard to debug when I moved this lower listener.Initialize(); args.Add(new SetAppArgumentArgument($"-transport:{transport}", true)); args.Add(new SetEnvVariableArgument(EnviromentVariables.Transport, transport.ToString().ToUpper())); if (transport == ListenerTransport.File) { args.Add(new SetEnvVariableArgument(EnviromentVariables.LogFilePath, listenerTmpFile)); } args.Add(new SetAppArgumentArgument($"-hostport:{listener.Port}", true)); args.Add(new SetEnvVariableArgument(EnviromentVariables.HostPort, listener.Port)); if (_listenerFactory.UseTunnel && !isSimulator) // simulators do not support tunnels { args.Add(new SetEnvVariableArgument(EnviromentVariables.UseTcpTunnel, true)); } if (_useXmlOutput) { // let the runner now via envars that we want to get a xml output, else the runner will default to plain text args.Add (new SetEnvVariableArgument (EnviromentVariables.EnableXmlOutput, true)); args.Add (new SetEnvVariableArgument (EnviromentVariables.XmlMode, "wrapped")); args.Add (new SetEnvVariableArgument (EnviromentVariables.XmlVersion, $"{xmlResultJargon}")); } listener.StartAsync(); var crashLogs = new Logs(_logs.Directory); if (appInformation.Extension.HasValue) { switch (appInformation.Extension) { case Extension.TodayExtension: args.Add(isSimulator ? (MlaunchArgument)new LaunchSimulatorExtensionArgument(appInformation.LaunchAppPath, appInformation.BundleIdentifier) : new LaunchDeviceExtensionArgument(appInformation.LaunchAppPath, appInformation.BundleIdentifier)); break; case Extension.WatchKit2: default: throw new NotImplementedException(); } } else { args.Add(isSimulator ? (MlaunchArgument)new LaunchSimulatorArgument(appInformation.LaunchAppPath) : new LaunchDeviceArgument(appInformation.LaunchAppPath)); } var runMode = target.ToRunMode(); ICrashSnapshotReporter crashReporter; ITestReporter testReporter; if (isSimulator) { crashReporter = _snapshotReporterFactory.Create(_mainLog, crashLogs, isDevice: !isSimulator, deviceName: null!); testReporter = _testReporterFactory.Create(_mainLog, _mainLog, _logs, crashReporter, listener, new XmlResultParser(), appInformation, runMode, xmlResultJargon, device: null, timeout, null, (level, message) => _mainLog.WriteLine(message)); using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(testReporter.CancellationToken, cancellationToken); listener.ConnectedTask .TimeoutAfter(testLaunchTimeout) .ContinueWith(testReporter.LaunchCallback) .DoNotAwait(); await _simulatorLoader.LoadDevices(_logs.Create($"simulator-list-{_helpers.Timestamp}.log", "Simulator list"), false, false); var simulators = await _simulatorLoader.FindSimulators(target, _mainLog); if (!(simulators?.Any() ?? false)) { _mainLog.WriteLine("Didn't find any suitable simulators"); throw new NoDeviceFoundException(); } var simulator = string.IsNullOrEmpty(deviceName) ? simulators.FirstOrDefault() : simulators.FirstOrDefault(s => string.Equals(s.Name, deviceName, StringComparison.InvariantCultureIgnoreCase)); if (simulator == null) { throw new NoDeviceFoundException(); } deviceName = simulator.Name; if (!target.IsWatchOSTarget()) { var stderrTty = _helpers.GetTerminalName(2); if (!string.IsNullOrEmpty(stderrTty)) { args.Add(new SetStderrArgument(stderrTty)); } else { var stdoutLog = _logs.CreateFile($"mlaunch-stdout-{_helpers.Timestamp}.log", "Standard output"); var stderrLog = _logs.CreateFile($"mlaunch-stderr-{_helpers.Timestamp}.log", "Standard error"); args.Add(new SetStdoutArgument(stdoutLog)); args.Add(new SetStderrArgument(stderrLog)); } } var systemLogs = new List<ICaptureLog>(); foreach (var sim in simulators) { // Upload the system log _mainLog.WriteLine("System log for the '{1}' simulator is: {0}", sim.SystemLog, sim.Name); bool isCompanion = sim != simulator; var logDescription = isCompanion ? LogType.CompanionSystemLog.ToString() : LogType.SystemLog.ToString(); var log = _captureLogFactory.Create( Path.Combine(_logs.Directory, sim.Name + ".log"), sim.SystemLog, true, logDescription); log.StartCapture(); _logs.Add(log); systemLogs.Add(log); } _mainLog.WriteLine("*** Executing {0}/{1} in the simulator ***", appInformation.AppName, target); if (ensureCleanSimulatorState) { foreach (var sim in simulators) { await sim.PrepareSimulator(_mainLog, appInformation.BundleIdentifier); } } args.Add(new SimulatorUDIDArgument(simulator.UDID)); await crashReporter.StartCaptureAsync(); _mainLog.WriteLine("Starting test run"); var result = _processManager.ExecuteCommandAsync(args, _mainLog, timeout, cancellationToken: linkedCts.Token); await testReporter.CollectSimulatorResult(result); // cleanup after us if (ensureCleanSimulatorState) { await simulator.KillEverything(_mainLog); } foreach (var log in systemLogs) { log.StopCapture(); } } else { args.Add(new DisableMemoryLimitsArgument()); if (deviceName == null) { IHardwareDevice? companionDevice = null; IHardwareDevice device = await _hardwareDeviceLoader.FindDevice(runMode, _mainLog, includeLocked: false, force: false); if (target.IsWatchOSTarget()) { companionDevice = await _hardwareDeviceLoader.FindCompanionDevice(_mainLog, device); } deviceName = companionDevice?.Name ?? device.Name; } if (deviceName == null) { throw new NoDeviceFoundException(); } crashReporter = _snapshotReporterFactory.Create(_mainLog, crashLogs, isDevice: !isSimulator, deviceName); testReporter = _testReporterFactory.Create(_mainLog, _mainLog, _logs, crashReporter, listener, new XmlResultParser(), appInformation, runMode, xmlResultJargon, deviceName, timeout, null, (level, message) => _mainLog.WriteLine(message)); using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(testReporter.CancellationToken, cancellationToken); listener.ConnectedTask .TimeoutAfter(testLaunchTimeout) .ContinueWith(testReporter.LaunchCallback) .DoNotAwait(); _mainLog.WriteLine("*** Executing {0}/{1} on device '{2}' ***", appInformation.AppName, target, deviceName); if (target.IsWatchOSTarget()) { args.Add(new AttachNativeDebuggerArgument()); // this prevents the watch from backgrounding the app. } else { args.Add(new WaitForExitArgument()); } args.Add(new DeviceNameArgument(deviceName)); var deviceSystemLog = _logs.Create($"device-{deviceName}-{_helpers.Timestamp}.log", "Device log"); var deviceLogCapturer = _deviceLogCapturerFactory.Create(_mainLog, deviceSystemLog, deviceName); deviceLogCapturer.StartCapture(); try { await crashReporter.StartCaptureAsync(); // create a tunnel to communicate with the device if (transport == ListenerTransport.Tcp && _listenerFactory.UseTunnel && listener is SimpleTcpListener tcpListener) { // create a new tunnel using the listener var tunnel = _listenerFactory.TunnelBore.Create(deviceName, _mainLog); tunnel.Open(deviceName, tcpListener, timeout, _mainLog); // wait until we started the tunnel await tunnel.Started; } _mainLog.WriteLine("Starting test run"); // We need to check for MT1111 (which means that mlaunch won't wait for the app to exit). var aggregatedLog = Log.CreateAggregatedLog(testReporter.CallbackLog, _mainLog); Task<ProcessExecutionResult> runTestTask = _processManager.ExecuteCommandAsync( args, aggregatedLog, timeout, cancellationToken: linkedCts.Token); await testReporter.CollectDeviceResult(runTestTask); } finally { deviceLogCapturer.StopCapture(); deviceSystemLog.Dispose(); // close a tunnel if it was created if (!isSimulator && _listenerFactory.UseTunnel) await _listenerFactory.TunnelBore.Close(deviceName); } // Upload the system log if (File.Exists(deviceSystemLog.FullPath)) { _mainLog.WriteLine("A capture of the device log is: {0}", deviceSystemLog.FullPath); } } listener.Cancel(); listener.Dispose(); // check the final status, copy all the required data var (testResult, resultMessage) = await testReporter.ParseResult(); return (deviceName, testResult, resultMessage); }
private async Task RunSimulatorTests( MlaunchArguments mlaunchArguments, AppBundleInformation appInformation, ICrashSnapshotReporter crashReporter, ITestReporter testReporter, ISimulatorDevice simulator, ISimulatorDevice?companionSimulator, bool ensureCleanSimulatorState, TimeSpan timeout, CancellationToken cancellationToken) { var systemLogs = new List <ICaptureLog>(); try { _mainLog.WriteLine("System log for the '{1}' simulator is: {0}", simulator.SystemLog, simulator.Name); var simulatorLog = _captureLogFactory.Create( path: Path.Combine(_logs.Directory, simulator.Name + ".log"), systemLogPath: simulator.SystemLog, entireFile: true, LogType.SystemLog.ToString()); simulatorLog.StartCapture(); _logs.Add(simulatorLog); systemLogs.Add(simulatorLog); if (companionSimulator != null) { _mainLog.WriteLine("System log for the '{1}' companion simulator is: {0}", companionSimulator.SystemLog, companionSimulator.Name); var companionLog = _captureLogFactory.Create( path: Path.Combine(_logs.Directory, companionSimulator.Name + ".log"), systemLogPath: companionSimulator.SystemLog, entireFile: true, LogType.CompanionSystemLog.ToString()); companionLog.StartCapture(); _logs.Add(companionLog); systemLogs.Add(companionLog); } if (ensureCleanSimulatorState) { await simulator.PrepareSimulator(_mainLog, appInformation.BundleIdentifier); if (companionSimulator != null) { await companionSimulator.PrepareSimulator(_mainLog, appInformation.BundleIdentifier); } } await crashReporter.StartCaptureAsync(); _mainLog.WriteLine("Starting test run"); var result = _processManager.ExecuteCommandAsync(mlaunchArguments, _mainLog, timeout, cancellationToken: cancellationToken); await testReporter.CollectSimulatorResult(result); // cleanup after us if (ensureCleanSimulatorState) { await simulator.KillEverything(_mainLog); if (companionSimulator != null) { await companionSimulator.KillEverything(_mainLog); } } } finally { foreach (ICaptureLog?log in systemLogs) { log.StopCapture(); log.Dispose(); } } }
protected async Task <ProcessExecutionResult> RunMacCatalystApp( AppBundleInformation appInformation, ILog appOutputLog, TimeSpan timeout, bool waitForExit, IEnumerable <string> extraArguments, Dictionary <string, string> environmentVariables, CancellationToken cancellationToken) { using var systemLog = _captureLogFactory.Create( path: _logs.CreateFile("MacCatalyst.system.log", LogType.SystemLog), systemLogPath: SystemLogPath, entireFile: false, LogType.SystemLog); // We need to make the binary executable var binaryPath = Path.Combine(appInformation.AppPath, "Contents", "MacOS", appInformation.BundleExecutable ?? appInformation.AppName); if (File.Exists(binaryPath)) { await _processManager.ExecuteCommandAsync("chmod", new[] { "+x", binaryPath }, _mainLog, TimeSpan.FromSeconds(10), cancellationToken : cancellationToken); } // On Big Sur it seems like the launch services database is not updated fast enough after we do some I/O with the app bundle. // Force registration for the app by running // /System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -f /path/to/app.app var lsRegisterPath = @"/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister"; await _processManager.ExecuteCommandAsync(lsRegisterPath, new[] { "-f", appInformation.LaunchAppPath }, _mainLog, TimeSpan.FromSeconds(10), cancellationToken : cancellationToken); var arguments = new List <string> { "-n" // Open a new instance of the application(s) even if one is already running. }; if (waitForExit) { // Wait until the applications exit (even if they were already open) arguments.Add("-W"); } arguments.Add(appInformation.LaunchAppPath); arguments.AddRange(extraArguments); systemLog.StartCapture(); var logStreamScanToken = CaptureMacCatalystLog(appInformation, cancellationToken); try { return(await _processManager.ExecuteCommandAsync( "open", arguments, _mainLog, appOutputLog, appOutputLog, timeout, environmentVariables, cancellationToken)); } finally { if (waitForExit) { systemLog.StopCapture(waitIfEmpty: TimeSpan.FromSeconds(10)); } // Stop scanning the logs logStreamScanToken.Cancel(); } }