Example #1
0
        private void WaitUntilSiteStarted()
        {
            ServiceController serviceController = new ServiceController("w3svc");

            Logger.LogInformation("W3SVC status " + serviceController.Status);

            if (serviceController.Status != ServiceControllerStatus.Running &&
                serviceController.Status != ServiceControllerStatus.StartPending)
            {
                Logger.LogInformation("Starting W3SVC");

                serviceController.Start();
                serviceController.WaitForStatus(ServiceControllerStatus.Running, _timeout);
            }

            RetryServerManagerAction(serverManager =>
            {
                var site    = serverManager.Sites.Single();
                var appPool = serverManager.ApplicationPools.Single();

                if (appPool.State != ObjectState.Started && appPool.State != ObjectState.Starting)
                {
                    var state = appPool.Start();
                    Logger.LogInformation($"Starting pool, state: {state.ToString()}");
                }

                if (site.State != ObjectState.Started && site.State != ObjectState.Starting)
                {
                    var state = site.Start();
                    Logger.LogInformation($"Starting site, state: {state.ToString()}");
                }

                if (site.State != ObjectState.Started)
                {
                    throw new InvalidOperationException("Site not started yet");
                }

                var workerProcess = appPool.WorkerProcesses.SingleOrDefault();
                if (workerProcess == null)
                {
                    throw new InvalidOperationException("Site is started but no worked process found");
                }

                HostProcess = Process.GetProcessById(workerProcess.ProcessId);

                // Ensure w3wp.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("Site has started.");
            });
        }
        private async Task <(Uri url, CancellationToken hostExitToken)> StartIISExpressAsync(Uri uri, string contentRoot)
        {
            using (Logger.BeginScope("StartIISExpress"))
            {
                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);

                var iisExpressPath = GetIISExpressPath();

                for (var attempt = 0; attempt < MaximumAttempts; attempt++)
                {
                    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);
            }
        }