Exemplo n.º 1
0
        private async Task <(Uri url, CancellationToken hostExitToken)> StartIISExpressAsync(string contentRoot)
        {
            using (Logger.BeginScope("StartIISExpress"))
            {
                var iisExpressPath = GetIISExpressPath();

                for (var attempt = 0; attempt < MaximumAttempts; attempt++)
                {
                    var uri  = TestUriHelper.BuildTestUri(ServerType.IISExpress, DeploymentParameters.ApplicationBaseUriHint);
                    var port = uri.Port;
                    if (port == 0)
                    {
                        port = (uri.Scheme == "https") ? TestPortHelper.GetNextSSLPort() : TestPortHelper.GetNextPort();
                    }

                    Logger.LogInformation("Attempting to start IIS Express on port: {port}", port);
                    PrepareConfig(contentRoot, port);

                    var parameters = string.IsNullOrEmpty(DeploymentParameters.ServerConfigLocation) ?
                                     string.Format("/port:{0} /path:\"{1}\" /trace:error /systray:false", uri.Port, contentRoot) :
                                     string.Format("/site:{0} /config:{1} /trace:error /systray:false", DeploymentParameters.SiteName, DeploymentParameters.ServerConfigLocation);

                    Logger.LogInformation("Executing command : {iisExpress} {parameters}", iisExpressPath, parameters);

                    var startInfo = new ProcessStartInfo
                    {
                        FileName               = iisExpressPath,
                        Arguments              = parameters,
                        UseShellExecute        = false,
                        CreateNoWindow         = true,
                        RedirectStandardError  = true,
                        RedirectStandardOutput = true,
                        // VS sets current directory to C:\Program Files\IIS Express
                        WorkingDirectory = Path.GetDirectoryName(iisExpressPath)
                    };

                    AddEnvironmentVariablesToProcess(startInfo, DeploymentParameters.EnvironmentVariables);

                    Uri url     = null;
                    var started = new TaskCompletionSource <bool>();

                    var process = new Process()
                    {
                        StartInfo = startInfo
                    };
                    process.OutputDataReceived += (sender, dataArgs) =>
                    {
                        if (string.Equals(dataArgs.Data, UnableToStartIISExpressMessage))
                        {
                            // We completely failed to start and we don't really know why
                            started.TrySetException(new InvalidOperationException("Failed to start IIS Express"));
                        }
                        else if (string.Equals(dataArgs.Data, FailedToInitializeBindingsMessage))
                        {
                            started.TrySetResult(false);
                        }
                        else if (string.Equals(dataArgs.Data, IISExpressRunningMessage))
                        {
                            started.TrySetResult(true);
                        }
                        else if (!string.IsNullOrEmpty(dataArgs.Data))
                        {
                            var m = UrlDetectorRegex.Match(dataArgs.Data);
                            if (m.Success)
                            {
                                url = new Uri(m.Groups["url"].Value);
                            }
                        }
                    };

                    process.EnableRaisingEvents = true;
                    var hostExitTokenSource = new CancellationTokenSource();
                    process.Exited += (sender, e) =>
                    {
                        Logger.LogInformation("iisexpress Process {pid} shut down", process.Id);

                        // If TrySetResult was called above, this will just silently fail to set the new state, which is what we want
                        started.TrySetException(new Exception($"Command exited unexpectedly with exit code: {process.ExitCode}"));

                        TriggerHostShutdown(hostExitTokenSource);
                    };
                    process.StartAndCaptureOutAndErrToLogger("iisexpress", Logger);
                    Logger.LogInformation("iisexpress Process {pid} started", process.Id);

                    if (process.HasExited)
                    {
                        Logger.LogError("Host process {processName} {pid} exited with code {exitCode} or failed to start.", startInfo.FileName, process.Id, process.ExitCode);
                        throw new Exception("Failed to start host");
                    }

                    // Wait for the app to start
                    // The timeout here is large, because we don't know how long the test could need
                    // We cover a lot of error cases above, but I want to make sure we eventually give up and don't hang the build
                    // just in case we missed one -anurse
                    if (!await started.Task.TimeoutAfter(TimeSpan.FromMinutes(10)))
                    {
                        Logger.LogInformation("iisexpress Process {pid} failed to bind to port {port}, trying again", process.Id, port);

                        // Wait for the process to exit and try again
                        process.WaitForExit(30 * 1000);
                        await Task.Delay(1000); // Wait a second to make sure the socket is completely cleaned up
                    }
                    else
                    {
                        _hostProcess = process;

                        // Ensure iisexpress.exe is killed if test process termination is non-graceful.
                        // Prevents locked files when stop debugging unit test.
                        ProcessTracker.Add(_hostProcess);

                        // cache the process start time for verifying log file name.
                        var _ = _hostProcess.StartTime;

                        Logger.LogInformation("Started iisexpress successfully. Process Id : {processId}, Port: {port}", _hostProcess.Id, port);
                        return(url : url, hostExitToken : hostExitTokenSource.Token);
                    }
                }

                var message = $"Failed to initialize IIS Express after {MaximumAttempts} attempts to select a port";
                Logger.LogError(message);
                throw new TimeoutException(message);
            }
        }