Beispiel #1
0
        protected override async Task <ExitCode> InvokeInternal(ILogger logger)
        {
            var processManager  = new MLaunchProcessManager(_arguments.XcodeRoot, _arguments.MlaunchPath);
            var deviceLoader    = new HardwareDeviceLoader(processManager);
            var simulatorLoader = new SimulatorLoader(processManager);

            var logs = new Logs(_arguments.OutputDirectory);

            var cts = new CancellationTokenSource();

            cts.CancelAfter(_arguments.Timeout);

            var exitCode = ExitCode.SUCCESS;

            foreach (TestTarget target in _arguments.TestTargets)
            {
                var tunnelBore = (_arguments.CommunicationChannel == CommunicationChannel.UsbTunnel && !target.IsSimulator())
                    ? new TunnelBore(processManager)
                    : null;
                var exitCodeForRun = await RunTest(logger, target, logs, processManager, deviceLoader, simulatorLoader,
                                                   tunnelBore, cts.Token);

                if (exitCodeForRun != ExitCode.SUCCESS)
                {
                    exitCode = exitCodeForRun;
                }
            }

            return(exitCode);
        }
Beispiel #2
0
        private async Task <ExitCode> RunTest(
            ILogger logger,
            TestTarget target,
            Logs logs,
            MLaunchProcessManager processManager,
            IHardwareDeviceLoader deviceLoader,
            ISimulatorLoader simulatorLoader,
            ITunnelBore?tunnelBore,
            CancellationToken cancellationToken = default)
        {
            var isLldbEnabled = IsLldbEnabled();

            if (isLldbEnabled && !_arguments.EnableLldb)
            {
                // the file is present, but the user did not set it, warn him about it
                logger.LogWarning("Lldb will be used since '~/.mtouch-launch-with-lldb' was found in the system but it was not created by xharness.");
            }
            else if (_arguments.EnableLldb)
            {
                if (!File.Exists(_mlaunchLldbConfigFile))
                {
                    // create empty file
                    var f = File.Create(_mlaunchLldbConfigFile);
                    f.Dispose();
                    _createdLldbFile = true;
                }
            }


            logger.LogInformation($"Starting test for {target.AsString()}{ (_arguments.DeviceName != null ? " targeting " + _arguments.DeviceName : null) }..");

            string         mainLogFile = Path.Join(_arguments.OutputDirectory, $"run-{target}{(_arguments.DeviceName != null ? "-" + _arguments.DeviceName : null)}.log");
            IFileBackedLog mainLog     = logs.Create(mainLogFile, LogType.ExecutionLog.ToString(), true);
            int            verbosity   = GetMlaunchVerbosity(_arguments.Verbosity);

            string?deviceName = _arguments.DeviceName;

            var appBundleInformationParser = new AppBundleInformationParser(processManager);

            AppBundleInformation appBundleInfo;

            try
            {
                appBundleInfo = await appBundleInformationParser.ParseFromAppBundle(_arguments.AppPackagePath, target, mainLog, cancellationToken);
            }
            catch (Exception e)
            {
                logger.LogError($"Failed to get bundle information: {e.Message}");
                return(ExitCode.FAILED_TO_GET_BUNDLE_INFO);
            }

            if (!target.IsSimulator())
            {
                logger.LogInformation($"Installing application '{appBundleInfo.AppName}' on " + (deviceName != null ? $" on device '{deviceName}'" : target.AsString()));

                var appInstaller = new AppInstaller(processManager, deviceLoader, mainLog, verbosity);

                ProcessExecutionResult result;

                try
                {
                    (deviceName, result) = await appInstaller.InstallApp(appBundleInfo, target, cancellationToken : cancellationToken);
                }
                catch (NoDeviceFoundException)
                {
                    logger.LogError($"Failed to find suitable device for target {target.AsString()}");
                    return(ExitCode.DEVICE_NOT_FOUND);
                }
                catch (Exception e)
                {
                    logger.LogError($"Failed to install the app bundle:{Environment.NewLine}{e}");
                    return(ExitCode.PACKAGE_INSTALLATION_FAILURE);
                }

                if (!result.Succeeded)
                {
                    // use the knowledge base class to decide if the error is known, if it is, let the user know
                    // the failure reason
                    if (_errorKnowledgeBase.IsKnownInstallIssue(mainLog, out var errorMessage))
                    {
                        logger.LogError($"Failed to install the app bundle (exit code={result.ExitCode}): {errorMessage}");
                    }
                    else
                    {
                        logger.LogError($"Failed to install the app bundle (exit code={result.ExitCode})");
                    }

                    return(ExitCode.PACKAGE_INSTALLATION_FAILURE);
                }

                logger.LogInformation($"Application '{appBundleInfo.AppName}' was installed successfully on device '{deviceName}'");
            }

            logger.LogInformation($"Starting application '{appBundleInfo.AppName}' on " + (deviceName != null ? $"device '{deviceName}'" : target.AsString()));

            // only add the extra callback if we do know that the feature was indeed enabled
            Action <string>?logCallback = isLldbEnabled ? (l) => NotifyUserLldbCommand(logger, l) : (Action <string>?)null;

            var appRunner = new AppRunner(
                processManager,
                deviceLoader,
                simulatorLoader,
                new SimpleListenerFactory(tunnelBore),
                new CrashSnapshotReporterFactory(processManager),
                new CaptureLogFactory(),
                new DeviceLogCapturerFactory(processManager),
                new TestReporterFactory(processManager),
                mainLog,
                logs,
                new Helpers(),
                useXmlOutput: true, // the cli ALWAYS will get the output as xml
                logCallback: logCallback);

            try
            {
                string resultMessage;
                TestExecutingResult testResult;
                (deviceName, testResult, resultMessage) = await appRunner.RunApp(appBundleInfo,
                                                                                 target,
                                                                                 _arguments.Timeout,
                                                                                 _arguments.LaunchTimeout,
                                                                                 deviceName,
                                                                                 verbosity : verbosity,
                                                                                 xmlResultJargon : _arguments.XmlResultJargon,
                                                                                 cancellationToken : cancellationToken);

                switch (testResult)
                {
                case TestExecutingResult.Succeeded:
                    logger.LogInformation($"Application finished the test run successfully");
                    logger.LogInformation(resultMessage);

                    return(ExitCode.SUCCESS);

                case TestExecutingResult.Failed:
                    logger.LogInformation($"Application finished the test run successfully with some failed tests");
                    logger.LogInformation(resultMessage);

                    return(ExitCode.TESTS_FAILED);

                case TestExecutingResult.Crashed:

                    if (resultMessage != null)
                    {
                        logger.LogError($"Application run crashed:{Environment.NewLine}" +
                                        $"{resultMessage}{Environment.NewLine}{Environment.NewLine}" +
                                        $"Check logs for more information.");
                    }
                    else
                    {
                        logger.LogError($"Application run crashed. Check logs for more information");
                    }

                    return(ExitCode.APP_CRASH);

                case TestExecutingResult.TimedOut:
                    logger.LogWarning($"Application run timed out");

                    return(ExitCode.TIMED_OUT);

                default:

                    if (resultMessage != null)
                    {
                        logger.LogError($"Application run ended in an unexpected way: '{testResult}'{Environment.NewLine}" +
                                        $"{resultMessage}{Environment.NewLine}{Environment.NewLine}" +
                                        $"Check logs for more information.");
                    }
                    else
                    {
                        logger.LogError($"Application run ended in an unexpected way: '{testResult}'. Check logs for more information");
                    }

                    return(ExitCode.GENERAL_FAILURE);
                }
            }
            catch (NoDeviceFoundException)
            {
                logger.LogError($"Failed to find suitable device for target {target.AsString()}");
                return(ExitCode.DEVICE_NOT_FOUND);
            }
            catch (Exception e)
            {
                if (_errorKnowledgeBase.IsKnownTestIssue(mainLog, out var failureMessage))
                {
                    logger.LogError($"Application run failed:{Environment.NewLine}{failureMessage}");
                }
                else
                {
                    logger.LogError($"Application run failed:{Environment.NewLine}{e}");
                }

                return(ExitCode.APP_CRASH);
            }
            finally
            {
                if (!target.IsSimulator() && deviceName != null)
                {
                    logger.LogInformation($"Uninstalling the application '{appBundleInfo.AppName}' from device '{deviceName}'");

                    var appUninstaller  = new AppUninstaller(processManager, mainLog, verbosity);
                    var uninstallResult = await appUninstaller.UninstallApp(deviceName, appBundleInfo.BundleIdentifier, cancellationToken);

                    if (!uninstallResult.Succeeded)
                    {
                        logger.LogError($"Failed to uninstall the app bundle with exit code: {uninstallResult.ExitCode}");
                    }
                    else
                    {
                        logger.LogInformation($"Application '{appBundleInfo.AppName}' was uninstalled successfully");
                    }
                }

                if (_createdLldbFile) // clean after the setting
                {
                    File.Delete(_mlaunchLldbConfigFile);
                }
            }
        }
        protected override async Task <ExitCode> InvokeInternal(ILogger logger)
        {
            var processManager  = new MLaunchProcessManager(mlaunchPath: _arguments.MlaunchPath);
            var deviceLoader    = new HardwareDeviceLoader(processManager);
            var simulatorLoader = new SimulatorLoader(processManager);
            var log             = new MemoryLog(); // do we really want to log this?

            var mlaunchLog = new MemoryLog {
                Timestamp = false
            };

            ProcessExecutionResult result;

            try
            {
                result = await processManager.ExecuteCommandAsync(new MlaunchArguments(new MlaunchVersionArgument()), new MemoryLog(), mlaunchLog, new MemoryLog(), TimeSpan.FromSeconds(10));
            }
            catch (Exception e)
            {
                logger.LogError($"Failed to get mlaunch version info:{Environment.NewLine}{e}");
                return(ExitCode.GENERAL_FAILURE);
            }

            if (!result.Succeeded)
            {
                logger.LogError($"Failed to get mlaunch version info:{Environment.NewLine}{mlaunchLog}");
                return(ExitCode.GENERAL_FAILURE);
            }

            // build the required data, then depending on the format print out
            var info = new SystemInfo(
                machineName: Environment.MachineName,
                oSName: "Mac OS X",
                oSVersion: Darwin.GetVersion() ?? "",
                oSPlatform: "Darwin",
                xcodePath: processManager.XcodeRoot,
                xcodeVersion: processManager.XcodeVersion.ToString(),
                mlaunchPath: processManager.MlaunchPath,
                mlaunchVersion: mlaunchLog.ToString().Trim());

            try
            {
                await simulatorLoader.LoadDevices(log);
            }
            catch (Exception e)
            {
                logger.LogError($"Failed to load simulators:{Environment.NewLine}{e}");
                logger.LogInformation($"Execution log:{Environment.NewLine}{log}");
                return(ExitCode.GENERAL_FAILURE);
            }

            foreach (var sim in simulatorLoader.AvailableDevices)
            {
                info.Simulators.Add(new DeviceInfo(
                                        name: sim.Name,
                                        uDID: sim.UDID,
                                        type: sim.SimDeviceType.Remove(0, SimulatorPrefix.Length).Replace('-', ' '),
                                        oSVersion: sim.OSVersion));
            }

            try
            {
                await deviceLoader.LoadDevices(log);
            }
            catch (Exception e)
            {
                logger.LogError($"Failed to load connected devices:{Environment.NewLine}{e}");
                logger.LogInformation($"Execution log:{Environment.NewLine}{log}");
                return(ExitCode.GENERAL_FAILURE);
            }

            foreach (var dev in deviceLoader.ConnectedDevices)
            {
                info.Devices.Add(new DeviceInfo(
                                     name: dev.Name,
                                     uDID: dev.DeviceIdentifier,
                                     type: $"{dev.DeviceClass} {dev.DevicePlatform}",
                                     oSVersion: dev.OSVersion));
            }

            if (_arguments.UseJson)
            {
                await AsJson(info);
            }
            else
            {
                AsText(info);
            }

            return(ExitCode.SUCCESS);
        }
        protected async override Task <ExitCode> InvokeInternal(ILogger logger)
        {
            var processManager = new MLaunchProcessManager();

            // Validate the presence of Xamarin.iOS
            var missingXamariniOS = false;

            if (_arguments.TemplateType == TemplateType.Managed)
            {
                var dotnetLog = new MemoryLog()
                {
                    Timestamp = false
                };
                var process = new Process();
                process.StartInfo.FileName  = "bash";
                process.StartInfo.Arguments = "-c \"" + _arguments.DotnetPath + " --info | grep \\\"Base Path\\\" | cut -d':' -f 2 | tr -d '[:space:]'\"";

                var result = await processManager.RunAsync(process, new MemoryLog(), dotnetLog, new MemoryLog(), TimeSpan.FromSeconds(5));

                if (result.Succeeded)
                {
                    var sdkPath = dotnetLog.ToString().Trim();
                    if (Directory.Exists(sdkPath))
                    {
                        var xamarinIosPath = Path.Combine(sdkPath, "Xamarin", "iOS", "Xamarin.iOS.CSharp.targets");
                        if (!File.Exists(xamarinIosPath))
                        {
                            missingXamariniOS = true;
                            logger.LogWarning("Failed to find the Xamarin iOS package which is needed for the Managed template: " + xamarinIosPath);
                        }
                    }
                }
            }

            // create the factory, which will be used to find the diff assemblies
            var assemblyLocator           = new AssemblyLocator(Directory.GetCurrentDirectory());
            var assemblyDefinitionFactory = new AssemblyDefinitionFactory(_arguments.TestingFramework, assemblyLocator);

            ITemplatedProject template = _arguments.TemplateType switch
            {
                TemplateType.Managed => new XamariniOSTemplate
                {
                    AssemblyDefinitionFactory = assemblyDefinitionFactory,
                    AssemblyLocator           = assemblyDefinitionFactory.AssemblyLocator,
                    OutputDirectoryPath       = _arguments.OutputDirectory,
                    IgnoreFilesRootDirectory  = _arguments.IgnoreFilesRootDirectory,
                    ProjectFilter             = new ProjectFilter(_arguments.IgnoreFilesRootDirectory, _arguments.TraitsRootDirectory),
                },
                _ => throw new Exception("The 'Native' template is not yet supported. Please use the managed one."),
            };

            var logs       = new Logs(_arguments.WorkingDirectory);
            var runLog     = logs.Create("package-log.txt", "Package Log");
            var consoleLog = new CallbackLog(s => logger.LogInformation(s))
            {
                Timestamp = false
            };

            var aggregatedLog = Log.CreateReadableAggregatedLog(runLog, consoleLog);

            aggregatedLog.WriteLine("Generating scaffold app with:");
            aggregatedLog.WriteLine($"\tAppname: '{_arguments.AppPackageName}'");
            aggregatedLog.WriteLine($"\tAssemblies: '{string.Join(" ", _arguments.Assemblies)}'");
            aggregatedLog.WriteLine($"\tExtraArgs: '{_arguments.MtouchExtraArgs}'");

            // first step, generate the required info to be passed to the factory
            var projects = new List <(string Name, string[] Assemblies, string?ExtraArgs, double TimeoutMultiplier)> {
                // TODO: Timeout multiplier handling
                (Name : _arguments.AppPackageName, Assemblies : _arguments.Assemblies.ToArray(), ExtraArgs : _arguments.MtouchExtraArgs, TimeoutMultiplier : 1.0),
            };

            // TODO: we are not taking into account all the plaforms, just iOS
            var allProjects = new GeneratedProjects();

            foreach (var p in _arguments.Platforms)
            {
                // so wish that mono.options allowed use to use async :/
                var testProjects = await template.GenerateTestProjectsAsync(projects, XHarness.iOS.Shared.TestImporter.Platform.iOS);

                allProjects.AddRange(testProjects);
            }

            // We do have all the required projects, time to compile them
            aggregatedLog.WriteLine("Scaffold app generated.");

            // First step, nuget restore whatever is needed
            var projectPath = Path.Combine(_arguments.OutputDirectory, _arguments.AppPackageName + ".csproj");

            aggregatedLog.WriteLine($"Project path is {projectPath}");
            aggregatedLog.WriteLine($"Performing nuget restore.");

            using (var dotnetRestore = new Process())
            {
                dotnetRestore.StartInfo.FileName = _arguments.DotnetPath;
                var args = new List <string>
                {
                    "restore",
                    projectPath,
                    "--verbosity:detailed"
                };

                dotnetRestore.StartInfo.Arguments = StringUtils.FormatArguments(args);

                var result = await processManager.RunAsync(dotnetRestore, aggregatedLog, _nugetRestoreTimeout);

                if (result.TimedOut)
                {
                    aggregatedLog.WriteLine("nuget restore timedout.");
                    return(ExitCode.PACKAGE_BUNDLING_FAILURE_NUGET_RESTORE);
                }

                if (!result.Succeeded)
                {
                    aggregatedLog.WriteLine($"nuget restore exited with {result.ExitCode}");
                    return(ExitCode.PACKAGE_BUNDLING_FAILURE_NUGET_RESTORE);
                }
            }

            var finalResult = ExitCode.SUCCESS;

            // perform the build of the application
            using (var dotnetBuild = new Process())
            {
                dotnetBuild.StartInfo.FileName = _arguments.DotnetPath;

                // TODO: Only taking into account one platform
                // https://github.com/dotnet/xharness/issues/105
                if (_arguments.Platforms.Count > 1)
                {
                    logger.LogWarning($"Multi-platform targetting is not supported yet. Targetting {_arguments.Platforms[0]} only.");
                }

                dotnetBuild.StartInfo.Arguments = GetBuildArguments(projectPath, _arguments.Platforms[0].ToString());

                aggregatedLog.WriteLine($"Building {_arguments.AppPackageName} ({projectPath})");

                var result = await processManager.RunAsync(dotnetBuild, aggregatedLog, _msBuildTimeout);

                if (result.TimedOut)
                {
                    aggregatedLog.WriteLine("Build timed out after {0} seconds.", _msBuildTimeout.TotalSeconds);
                }
                else if (result.Succeeded)
                {
                    aggregatedLog.WriteLine("Build was successful.");
                }
                else if (!result.Succeeded)
                {
                    logger.LogError($"Build failed with return code: {result.ExitCode}");

                    finalResult = ExitCode.PACKAGE_BUNDLING_FAILURE_BUILD;

                    if (missingXamariniOS)
                    {
                        logger.LogWarning("Possible cause of the failure may be missing Xamarin iOS package");
                    }
                }
            }

            return(finalResult);
        }