Exemple #1
0
        static IEnumerable <string> GetModifiedFilesLocally(IProcessManager processManager, Harness harness, int pull_request)
        {
            var base_commit = $"origin/pr/{pull_request}/merge^";
            var head_commit = $"origin/pr/{pull_request}/merge";

            harness.Log("Fetching modified files for commit range {0}..{1}", base_commit, head_commit);

            if (string.IsNullOrEmpty(head_commit) || string.IsNullOrEmpty(base_commit))
            {
                return(null);
            }

            using (var git = new Process()) {
                git.StartInfo.FileName  = "git";
                git.StartInfo.Arguments = $"diff-tree --no-commit-id --name-only -r {base_commit}..{head_commit}";
                var output = new MemoryLog();
                var rv     = processManager.RunAsync(git, harness.HarnessLog, stdoutLog: output, stderrLog: output).Result;
                if (rv.Succeeded)
                {
                    return(output.ToString().Split(new char [] { '\n' }, StringSplitOptions.RemoveEmptyEntries));
                }

                harness.Log("Could not fetch commit range:");
                harness.Log(output.ToString());

                return(null);
            }
        }
        static string DetectXcodePath()
        {
            using var process           = new Process();
            process.StartInfo.FileName  = "xcode-select";
            process.StartInfo.Arguments = "-p";

            var log    = new MemoryLog();
            var stdout = new MemoryLog()
            {
                Timestamp = false
            };
            var stderr  = new ConsoleLog();
            var timeout = TimeSpan.FromSeconds(30);

            var result = RunAsyncInternal(process, log, stdout, stderr, timeout).GetAwaiter().GetResult();

            if (!result.Succeeded)
            {
                throw new Exception("Failed to detect Xcode path from xcode-select!");
            }

            // Something like /Applications/Xcode114.app/Contents/Developers
            var xcodeRoot = stdout.ToString().Trim();

            if (string.IsNullOrEmpty(xcodeRoot))
            {
                throw new Exception("Failed to detect Xcode path from xcode-select!");
            }

            // We need /Applications/Xcode114.app only
            return(Path.GetDirectoryName(Path.GetDirectoryName(xcodeRoot)));
        }
Exemple #3
0
    public async Task <string> GetAppBundlePath(ILog log, string bundleIdentifier, CancellationToken cancellationToken)
    {
        log.WriteLine($"Querying '{Name}' for bundle path of '{bundleIdentifier}'..");

        var output = new MemoryLog()
        {
            Timestamp = false
        };
        var result = await _processManager.ExecuteXcodeCommandAsync(
            "simctl",
            new[] { "get_app_container", UDID, bundleIdentifier },
            log,
            output,
            output,
            TimeSpan.FromSeconds(30));

        if (!result.Succeeded)
        {
            throw new Exception($"Failed to get information for '{bundleIdentifier}'. Please check the app is installed");
        }

        var bundlePath = output.ToString().Trim();

        log.WriteLine($"Found installed app bundle at '{bundlePath}'");

        return(bundlePath);
    }
Exemple #4
0
        protected async Task <(bool Succeeded, string Stdout)> ExecuteCommand(
            string filename,
            TimeSpan?timeout = null,
            params string[] arguments)
        {
            var stdoutLog = new MemoryLog()
            {
                Timestamp = false
            };
            var stderrLog = new MemoryLog()
            {
                Timestamp = false
            };

            var result = await _processManager.ExecuteCommandAsync(
                filename,
                arguments,
                new CallbackLog(m => Logger.LogDebug(m)),
                stdoutLog,
                stderrLog,
                timeout ?? TimeSpan.FromSeconds(30));

            string stderr = stderrLog.ToString();

            if (stderr.Length > 0)
            {
                Logger.LogDebug("Error output:" + Environment.NewLine + stderr);
            }

            return(result.Succeeded, stdoutLog.ToString());
        }
        private async Task <string> GetPlistProperty(string plistPath, string propertyName, ILog log, CancellationToken cancellationToken = default)
        {
            var args = new[]
            {
                "-c",
                $"Print {propertyName}",
                plistPath,
            };

            var commandOutput = new MemoryLog {
                Timestamp = false
            };
            var result = await _processManager.ExecuteCommandAsync(PlistBuddyPath, args, log, commandOutput, commandOutput, TimeSpan.FromSeconds(15), cancellationToken : cancellationToken);

            if (!result.Succeeded)
            {
                throw new Exception($"Failed to get bundle information: {commandOutput}");
            }

            return(commandOutput.ToString().Trim());
        }
Exemple #6
0
    private async Task <string> GetPlistProperty(
        string plistPath,
        string propertyName,
        ILog log,
        CancellationToken cancellationToken = default,
        int attempt     = 1,
        int maxAttempts = 3)
    {
        var args = new[]
        {
            "-c",
            $"Print {propertyName}",
            plistPath,
        };

        var commandOutput = new MemoryLog {
            Timestamp = false
        };
        var result = await _processManager.ExecuteCommandAsync(
            PlistBuddyPath,
            args,
            log,
            commandOutput,
            commandOutput,
            TimeSpan.FromSeconds(10),
            cancellationToken : cancellationToken);

        if (!result.Succeeded)
        {
            if (result.TimedOut && attempt < maxAttempts)
            {
                log.WriteLine($"Attempt to get {propertyName} from {plistPath} timed out, retrying {attempt + 1} out of {maxAttempts}...");
                return(await GetPlistProperty(plistPath, propertyName, log, cancellationToken, attempt + 1, maxAttempts));
            }

            throw new Exception($"Failed to get bundle information: {commandOutput}");
        }

        return(commandOutput.ToString().Trim());
    }
Exemple #7
0
        public IEnumerable <string> GetModifiedFiles(int pullRequest)
        {
            var path = Path.Combine(harness.LogDirectory, "pr" + pullRequest + "-files.log");

            if (!File.Exists(path))
            {
                var rv = GetModifiedFilesLocally(pullRequest);
                if (rv == null || rv.Count() == 0)
                {
                    rv = GetModifiedFilesRemotely(pullRequest);
                    if (rv == null)
                    {
                        rv = new string [] { }
                    }
                    ;
                }

                File.WriteAllLines(path, rv.ToArray());
                return(rv);
            }

            return(File.ReadAllLines(path));
        }

        IEnumerable <string> GetModifiedFilesRemotely(int pullRequest)
        {
            var path = Path.Combine(harness.LogDirectory, "pr" + pullRequest + "-remote-files.log");

            if (!File.Exists(path))
            {
                Directory.CreateDirectory(harness.LogDirectory);
                using (var client = CreateClient()) {
                    var rv  = new List <string> ();
                    var url = $"https://api.github.com/repos/xamarin/xamarin-macios/pulls/{pullRequest}/files?per_page=100";                     // 100 items per page is max
                    do
                    {
                        byte [] data;
                        try {
                            data = client.DownloadData(url);
                        } catch (WebException we) {
                            harness.Log("Could not load pull request files: {0}\n{1}", we, new StreamReader(we.Response.GetResponseStream()).ReadToEnd());
                            File.WriteAllText(path, string.Empty);
                            return(new string [] { });
                        }
                        var reader = JsonReaderWriterFactory.CreateJsonReader(data, new XmlDictionaryReaderQuotas());
                        var doc    = new XmlDocument();
                        doc.Load(reader);
                        foreach (XmlNode node in doc.SelectNodes("/root/item/filename"))
                        {
                            rv.Add(node.InnerText);
                        }

                        url = null;

                        var link = client.ResponseHeaders ["Link"];
                        try {
                            if (link != null)
                            {
                                var ltIdx = link.IndexOf('<');
                                var gtIdx = link.IndexOf('>', ltIdx + 1);
                                while (ltIdx >= 0 && gtIdx > ltIdx)
                                {
                                    var linkUrl = link.Substring(ltIdx + 1, gtIdx - ltIdx - 1);
                                    if (link [gtIdx + 1] != ';')
                                    {
                                        break;
                                    }
                                    var    commaIdx = link.IndexOf(',', gtIdx + 1);
                                    string rel;
                                    if (commaIdx != -1)
                                    {
                                        rel = link.Substring(gtIdx + 3, commaIdx - gtIdx - 3);
                                    }
                                    else
                                    {
                                        rel = link.Substring(gtIdx + 3);
                                    }

                                    if (rel == "rel=\"next\"")
                                    {
                                        url = linkUrl;
                                        break;
                                    }

                                    if (commaIdx == -1)
                                    {
                                        break;
                                    }

                                    ltIdx = link.IndexOf('<', commaIdx);
                                    gtIdx = link.IndexOf('>', ltIdx + 1);
                                }
                            }
                        } catch (Exception e) {
                            harness.Log("Could not paginate github response: {0}: {1}", link, e.Message);
                        }
                    } while (url != null);
                    File.WriteAllLines(path, rv.ToArray());
                    return(rv);
                }
            }

            return(File.ReadAllLines(path));
        }

        IEnumerable <string> GetModifiedFilesLocally(int pullRequest)
        {
            var base_commit = $"origin/pr/{pullRequest}/merge^";
            var head_commit = $"origin/pr/{pullRequest}/merge";

            harness.Log("Fetching modified files for commit range {0}..{1}", base_commit, head_commit);

            if (string.IsNullOrEmpty(head_commit) || string.IsNullOrEmpty(base_commit))
            {
                return(null);
            }

            using (var git = new Process()) {
                git.StartInfo.FileName  = "git";
                git.StartInfo.Arguments = $"diff-tree --no-commit-id --name-only -r {base_commit}..{head_commit}";
                var output = new MemoryLog()
                {
                    Timestamp = false                     // ensure we do not add the timestap or the logic for the file check will be hard and having it adds no value
                };
                var rv = processManager.RunAsync(git, harness.HarnessLog, stdoutLog: output, stderrLog: output).Result;
                if (rv.Succeeded)
                {
                    return(output.ToString().Split(new char [] { '\n' }, StringSplitOptions.RemoveEmptyEntries));
                }

                harness.Log("Could not fetch commit range:");
                harness.Log(output.ToString());

                return(null);
            }
        }

        byte[] DownloadPullRequestInfo(int pullRequest)
        {
            var path = Path.Combine(harness.LogDirectory, "pr" + pullRequest + ".log");

            if (!File.Exists(path))
            {
                Directory.CreateDirectory(harness.LogDirectory);
                using (var client = CreateClient()) {
                    byte [] data;
                    try {
                        data = client.DownloadData($"https://api.github.com/repos/xamarin/xamarin-macios/pulls/{pullRequest}");
                        File.WriteAllBytes(path, data);
                        return(data);
                    } catch (WebException we) {
                        harness.Log("Could not load pull request info: {0}\n{1}", we, new StreamReader(we.Response.GetResponseStream()).ReadToEnd());
                        File.WriteAllText(path, string.Empty);
                        return(new byte [0]);
                    }
                }
            }
            return(File.ReadAllBytes(path));
        }
    }
Exemple #8
0
        protected override async Task <ExitCode> InvokeInternal(ILogger logger)
        {
            var processManager  = new ProcessManager(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);
        }
Exemple #9
0
        public async Task CreateCopyAsync(ILog log, IProcessManager processManager, ITestTask test)
        {
            var directory = DirectoryUtilities.CreateTemporaryDirectory(test?.TestName ?? System.IO.Path.GetFileNameWithoutExtension(Path));

            Directory.CreateDirectory(directory);
            var original_path = Path;

            Path = System.IO.Path.Combine(directory, System.IO.Path.GetFileName(Path));

            await Task.Yield();

            XmlDocument doc;

            doc = new XmlDocument();
            doc.LoadWithoutNetworkAccess(original_path);
            var original_name = System.IO.Path.GetFileName(original_path);

            if (original_name.Contains("GuiUnit_NET") || original_name.Contains("GuiUnit_xammac_mobile"))
            {
                // The GuiUnit project files writes stuff outside their project directory using relative paths,
                // but override that so that we don't end up with multiple cloned projects writing stuff to
                // the same location.
                doc.SetOutputPath("bin\\$(Configuration)");
                doc.SetNode("DocumentationFile", "bin\\$(Configuration)\\nunitlite.xml");
            }
            doc.ResolveAllPaths(original_path);

            if (doc.IsDotNetProject())
            {
                // Many types of files below the csproj directory are included by default,
                // which means that we have to include them manually in the cloned csproj,
                // because the cloned project is stored in a very different directory.
                var test_dir = System.IO.Path.GetDirectoryName(original_path);

                // Get all the files in the project directory from git
                using var process                  = new Process();
                process.StartInfo.FileName         = "git";
                process.StartInfo.Arguments        = "ls-files";
                process.StartInfo.WorkingDirectory = test_dir;
                var stdout = new MemoryLog()
                {
                    Timestamp = false
                };
                var result = await processManager.RunAsync(process, log, stdout, stdout, timeout : TimeSpan.FromSeconds(15));

                if (!result.Succeeded)
                {
                    throw new Exception($"Failed to list the files in the directory {test_dir} (TimedOut: {result.TimedOut} ExitCode: {result.ExitCode}):\n{stdout}");
                }

                var files = stdout.ToString().Split('\n');
                foreach (var file in files)
                {
                    var ext          = System.IO.Path.GetExtension(file);
                    var full_path    = System.IO.Path.Combine(test_dir, file);
                    var windows_file = full_path.Replace('/', '\\');

                    if (file.Contains(".xcasset"))
                    {
                        doc.AddInclude("ImageAsset", file, windows_file, true);
                        continue;
                    }

                    switch (ext.ToLowerInvariant())
                    {
                    case ".cs":
                        doc.AddInclude("Compile", file, windows_file, true);
                        break;

                    case ".plist":
                        doc.AddInclude("None", file, windows_file, true);
                        break;

                    case ".storyboard":
                        doc.AddInclude("InterfaceDefinition", file, windows_file, true);
                        break;

                    case ".gitignore":
                    case ".csproj":
                    case ".props":               // Directory.Build.props
                    case "":                     // Makefile
                        break;                   // ignore these files

                    default:
                        Console.WriteLine($"Unknown file: {file} (extension: {ext}). There might be a default inclusion behavior for this file.");
                        break;
                    }
                }

                // The global.json and NuGet.config files make sure we use the locally built packages.
                var dotnet_test_dir  = System.IO.Path.Combine(test.RootDirectory, "dotnet");
                var global_json      = System.IO.Path.Combine(dotnet_test_dir, "global.json");
                var nuget_config     = System.IO.Path.Combine(dotnet_test_dir, "NuGet.config");
                var target_directory = directory;
                File.Copy(global_json, System.IO.Path.Combine(target_directory, System.IO.Path.GetFileName(global_json)), true);
                log.WriteLine($"Copied {global_json} to {target_directory}");
                File.Copy(nuget_config, System.IO.Path.Combine(target_directory, System.IO.Path.GetFileName(nuget_config)), true);
                log.WriteLine($"Copied {nuget_config} to {target_directory}");
            }

            var projectReferences = new List <TestProject> ();

            foreach (var pr in doc.GetProjectReferences())
            {
                var tp = new TestProject(pr.Replace('\\', '/'));
                await tp.CreateCopyAsync(log, processManager, test);

                doc.SetProjectReferenceInclude(pr, tp.Path.Replace('/', '\\'));
                projectReferences.Add(tp);
            }
            this.ProjectReferences = projectReferences;

            doc.Save(Path);
        }
        public async Task TimedOutQueueIsClearedWhenTooBig()
        {
            var logger  = new MemoryLog();
            var timeout = TimeSpan.FromMilliseconds(1);

            var endpoint = TestConfig.ServerEndpoint();

            using (var server = new TcpServer(endpoint.Ip.Port, TestConfig.Log))
                using (var conn = new Connection(endpoint, new ConnectionConfiguration(requestTimeout: timeout), logger)) {
                    await Task.WhenAny(server.ClientConnected, Task.Delay(TimeSpan.FromSeconds(3)));

                    await AssertAsync.Throws <TimeoutException>(() => Task.WhenAll(Enumerable.Range(0, 201).Select(i => conn.SendAsync(new MetadataRequest(), CancellationToken.None))));

                    await AssertAsync.ThatEventually(() => logger.LogEvents.Any(e => e.Item1 == LogLevel.Debug && e.Item2.Message.StartsWith("Clearing timed out requests to avoid overflow")), () => logger.ToString(LogLevel.Debug));
                }
        }
        public async Task MessagesStillLogWhenSendTimesOut()
        {
            var logger   = new MemoryLog();
            var received = 0;
            var timeout  = TimeSpan.FromMilliseconds(100);

            var endpoint = TestConfig.ServerEndpoint();

            using (var server = new TcpServer(endpoint.Ip.Port, TestConfig.Log))
                using (var conn = new Connection(endpoint, new ConnectionConfiguration(requestTimeout: timeout, onRead: (e, read, elapsed) => Interlocked.Increment(ref received)), logger))
                {
                    await Task.WhenAny(server.ClientConnected, Task.Delay(TimeSpan.FromSeconds(3)));

                    server.OnReceivedAsync = async data => {
                        var context = KafkaDecoder.DecodeHeader(data.Skip(Request.IntegerByteSize));
                        await Task.Delay(timeout);

                        await server.SendDataAsync(KafkaDecoder.EncodeResponseBytes(context, new MetadataResponse()));
                    };

                    await AssertAsync.Throws <TimeoutException>(() => conn.SendAsync(new MetadataRequest(), CancellationToken.None));

                    await AssertAsync.ThatEventually(() => received > 0, () => $"received {received}");

                    await AssertAsync.ThatEventually(() => logger.LogEvents.Any(e => e.Item1 == LogLevel.Debug && e.Item2.Message.StartsWith("Timed out -----> (timed out or otherwise errored in client)")), () => logger.ToString(LogLevel.Debug));
                }
        }
        public async Task ShouldNotFinishPartiallyReadMessage()
        {
            var log       = new MemoryLog();
            var bytesRead = 0;

            var config = new ConnectionConfiguration(onReadBytes: (e, attempted, actual, elapsed) => Interlocked.Add(ref bytesRead, actual));

            var endpoint = TestConfig.ServerEndpoint();

            using (var server = new TcpServer(endpoint.Ip.Port, TestConfig.Log))
                using (new Connection(endpoint, config, log))
                {
                    // send size
                    var size = 200;
                    await server.SendDataAsync(new ArraySegment <byte>(size.ToBytes()));

                    var bytes = new byte[99];
                    new Random(42).NextBytes(bytes);
                    var offset        = 0;
                    var correlationId = 200;
                    foreach (var b in correlationId.ToBytes())
                    {
                        bytes[offset++] = b;
                    }

                    // send half of payload
                    await server.SendDataAsync(new ArraySegment <byte>(bytes, 0, 99));

                    await AssertAsync.ThatEventually(() => bytesRead >= bytes.Length, () => $"read {bytesRead}, length {bytes.Length}");

                    Assert.That(log.LogEvents.Count(e => e.Item1 == LogLevel.Warn && e.Item2.Message.StartsWith($"Unexpected response (id {correlationId}, {size}? bytes) from")), Is.EqualTo(1), log.ToString());
                    Assert.That(log.LogEvents.Count(e => e.Item1 == LogLevel.Debug && e.Item2.Message.StartsWith($"Received {size} bytes (id {correlationId})")), Is.EqualTo(0));

                    server.DropConnection();

                    await AssertAsync.ThatEventually(() => log.LogEvents.Count(e => e.Item1 == LogLevel.Warn && e.Item2.Message.StartsWith("Socket has been re-established, so recovery of the remaining of the current message is uncertain at best")) == 1, log.ToString);
                }
        }
        public async Task ShouldFinishPartiallyReadMessage()
        {
            var log         = new MemoryLog();
            var bytesRead   = 0;
            var firstLength = 99;

            var config = new ConnectionConfiguration(onReadBytes: (e, attempted, actual, elapsed) => {
                if (Interlocked.Add(ref bytesRead, actual) == firstLength)
                {
                    throw new OutOfMemoryException();
                }
            });

            var endpoint = TestConfig.ServerEndpoint();

            using (var server = new TcpServer(endpoint.Ip.Port, TestConfig.Log))
                using (new Connection(endpoint, config, log))
                {
                    // send size
                    var size = 200;
                    await server.SendDataAsync(new ArraySegment <byte>(size.ToBytes()));

                    var bytes = new byte[size];
                    new Random(42).NextBytes(bytes);
                    var offset        = 0;
                    var correlationId = 200;
                    foreach (var b in correlationId.ToBytes())
                    {
                        bytes[offset++] = b;
                    }

                    // send half of payload
                    await server.SendDataAsync(new ArraySegment <byte>(bytes, 0, firstLength));

                    await AssertAsync.ThatEventually(() => bytesRead >= firstLength, () => $"read {bytesRead}, length {bytes.Length}");

                    Assert.That(log.LogEvents.Count(e => e.Item1 == LogLevel.Warn && e.Item2.Message.StartsWith($"Unexpected response (id {correlationId}, {size}? bytes) from")), Is.EqualTo(1), log.ToString());
                    Assert.That(log.LogEvents.Count(e => e.Item1 == LogLevel.Debug && e.Item2.Message.StartsWith($"Received {size} bytes (id {correlationId})")), Is.EqualTo(0));

                    // send half of payload should be skipped
                    while (!await server.SendDataAsync(new ArraySegment <byte>(bytes, firstLength, size - firstLength)))
                    {
                        // repeat until the connection is all up and working ...
                    }
                    await AssertAsync.ThatEventually(() => bytesRead >= size, () => $"read {bytesRead}, size {size}");

                    await AssertAsync.ThatEventually(() => log.LogEvents.Count(e => e.Item1 == LogLevel.Info && e.Item2.Message.StartsWith($"Received {size} bytes (id {correlationId})")) == 1, log.ToString);
                }
        }
        public async Task ShouldLogDisconnectAndRecover()
        {
            var log = new MemoryLog();
            var clientDisconnected = 0;
            var clientConnected    = 0;
            var serverConnected    = 0;

            var config = new ConnectionConfiguration(
                onDisconnected: (e, exception) => {
                Interlocked.Increment(ref clientDisconnected);
            },
                onConnected: (e, attempt, elapsed) => {
                Interlocked.Increment(ref clientConnected);
            });

            var endpoint = TestConfig.ServerEndpoint();

            using (var server = new TcpServer(endpoint.Ip.Port, TestConfig.Log)
            {
                OnConnected = () => Interlocked.Increment(ref serverConnected)
            })
                using (new Connection(endpoint, config, log: log))
                {
                    for (var connectionAttempt = 1; connectionAttempt <= 4; connectionAttempt++)
                    {
                        var currentAttempt = connectionAttempt;
                        await AssertAsync.ThatEventually(() => serverConnected == currentAttempt, () => $"server {serverConnected}, attempt {currentAttempt}");

                        await server.SendDataAsync(new ArraySegment <byte>(CreateCorrelationMessage(connectionAttempt)));

                        TestConfig.Log.Write(LogLevel.Info, () => LogEvent.Create($"Sent CONNECTION attempt {currentAttempt}"));
                        await AssertAsync.ThatEventually(() => clientConnected == currentAttempt, TimeSpan.FromMilliseconds(200), () => $"client {clientConnected}, attempt {currentAttempt}");

                        await AssertAsync.ThatEventually(() => log.LogEvents.Count(e => e.Item1 == LogLevel.Info && e.Item2.Message.StartsWith("Received 4 bytes (id ")) == currentAttempt, () => $"attempt {currentAttempt}\n" + log.ToString(LogLevel.Info));

                        TestConfig.Log.Write(LogLevel.Info, () => LogEvent.Create($"Dropping CONNECTION attempt {currentAttempt}"));
                        server.DropConnection();
                        await AssertAsync.ThatEventually(() => clientDisconnected == currentAttempt, () => $"client {clientDisconnected}, attempt {currentAttempt}");

                        Assert.That(log.LogEvents.Count(e => e.Item1 == LogLevel.Info && e.Item2.Message.StartsWith("Disposing transport to")), Is.AtLeast(currentAttempt));
                    }
                }
        }
        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);
        }
        // Retrieves a property from an MSBuild project file by executing MSBuild and getting MSBuild to print the property.
        // We do this by creating a custom MSBuild file which:
        // * Will import the project file we're inspecting
        // * Has a target that will print a given property
        // and then executing MSBuild on this custom MSBuild file.
        private async Task <string> GetPropertyByMSBuildEvaluationAsync(XmlDocument csproj, string projectPath, string evaluateProperty, string dependsOnTargets = "", Dictionary <string, string> properties = null)
        {
            var xml =
                @"<Project DefaultTargets='WriteProperty' xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
	<!-- Import the project we want to inspect -->
	<Import Project='$(ProjectFile)' Condition=""'$(ProjectFile)' != ''"" />
	<!-- Target to write out the property we want -->
	<Target Name='WriteProperty' DependsOnTargets='%DEPENDSONTARGETS%'>
		<PropertyGroup>
			<_Properties>$(%PROPERTY%)</_Properties>
		</PropertyGroup>
		<Error Text='The ProjectFile variable must be set.' Condition=""'$(ProjectFile)' == ''"" />
		<Error Text='The OutputFile variable must be set.' Condition=""'$(OutputFile)' == ''"" />
		<WriteLinesToFile File='$(OutputFile)' Lines='$(_Properties)' Overwrite='true' />
	</Target>
</Project>
";

            var dir       = Path.GetDirectoryName(projectPath);
            var inspector = Path.Combine(dir, "PropertyInspector.csproj");
            var output    = Path.Combine(dir, "PropertyInspector.txt");

            try {
                File.WriteAllText(inspector, xml.Replace("%PROPERTY%", evaluateProperty).Replace("%DEPENDSONTARGETS%", dependsOnTargets));
                using (var proc = new Process()) {
                    var isDotNetProject = csproj.IsDotNetProject();

                    proc.StartInfo.FileName = isDotNetProject ? GetDotNetExecutable(projectPath) : msBuildPath;
                    var args = new List <string> ();

                    if (isDotNetProject)
                    {
                        args.Add("build");
                    }

                    args.Add("/p:ProjectFile=" + projectPath);
                    args.Add("/p:OutputFile=" + output);

                    foreach (var prop in properties)
                    {
                        args.Add($"/p:{prop.Key}={prop.Value}");
                    }

                    args.Add(inspector);

                    var env = new Dictionary <string, string> {
                        { "MSBUILD_EXE_PATH", null },
                    };

                    proc.StartInfo.Arguments        = StringUtils.FormatArguments(args);
                    proc.StartInfo.WorkingDirectory = dir;

                    // Don't evaluate in parallel on multiple threads to avoid overloading the mac.
                    var acquired = await evaluate_semaphore.WaitAsync(TimeSpan.FromMinutes(5));

                    try {
                        var log        = getLog() ?? new ConsoleLog();
                        var memoryLog  = new MemoryLog();
                        var aggregated = Log.CreateAggregatedLog(memoryLog, log);
                        if (!acquired)
                        {
                            aggregated.WriteLine("Unable to acquire lock to evaluate MSBuild property in 5 minutes; will try to evaluate anyway.");
                        }
                        var rv = await processManager.RunAsync(proc, aggregated, environmentVariables : env, timeout : TimeSpan.FromMinutes(5));

                        if (!rv.Succeeded)
                        {
                            var msg = $"Unable to evaluate the property {evaluateProperty} in {projectPath}, build failed with exit code {rv.ExitCode}. Timed out: {rv.TimedOut}";
                            Console.WriteLine(msg + " Output: \n" + memoryLog.ToString());
                            throw new Exception(msg);
                        }
                    } finally {
                        if (acquired)
                        {
                            evaluate_semaphore.Release();
                        }
                    }

                    return(File.ReadAllText(output).Trim());
                }
            } finally {
                File.Delete(inspector);
                File.Delete(output);
            }
        }