/// <summary>
        /// Setup a NamedEventWaitHandle that will be used to signal the end of
        /// all tests and the need to shutdown all launched servers.
        /// </summary>
        public LauncherFixture()
        {
            string ewhAllSuspendName = Guid.NewGuid().ToString();
            var    args = new string[] { $"ewhAllSuspend={ewhAllSuspendName}" };

            ewh = new NamedEventWaitHandle(false, EventResetMode.ManualReset, ewhAllSuspendName);
            new TLauncher().Launch(args, false, false);
        }
        /// <summary>
        /// Builds a dictionary of Launchable objects, which contains all of the relevant
        /// information for launching the web applications
        /// </summary>
        /// <param name="args">global arguments</param>
        /// <param name="programMains">an array of Program.Main methods to invoke</param>
        /// <param name="dirs">a dictionary of project locations, keyed by the project names</param>
        /// <returns></returns>
        private static Dictionary <string, Launchable> InitializeLaunchables(string[] args,
                                                                             Action <string[]>[] programMains, Dictionary <string, string> dirs)
        {
            var launchables = new Dictionary <string, Launchable>();

            var kvpArgs = args
                          .Where(a => a.Contains("="))
                          .Select(a => new KeyValuePair <string, string>(a.Split('=')[0], a.Split('=')[1]))
                          .ToDictionary(x => x.Key, x => x.Value);

            NamedEventWaitHandle ewhReady      = null;
            NamedEventWaitHandle ewhAllSuspend = null;

            //used with pinging -- may not be needed
            ewhReady = new NamedEventWaitHandle(false, EventResetMode.ManualReset);

            //used to suspend all web application from the caller (typically xunit runner/test host)
            if (kvpArgs.TryGetValue("ewhAllSuspend", out string ewhAllSuspendName))
            {
                ewhAllSuspend = new NamedEventWaitHandle(false, EventResetMode.ManualReset, ewhAllSuspendName);
            }

            //loop over all Program.Main methods, building a launchable for each method
            foreach (var programMain in programMains)
            {
                var projectName = programMain.Method.DeclaringType.Assembly.GetName().Name;
                var launchable  = new Launchable {
                    ProgramMain     = programMain,
                    LaunchProfile   = new LaunchProfile(),
                    ReadyEvent      = ewhReady,
                    AllSuspendEvent = ewhAllSuspend
                };

                //the full launch profile will be retrieved and populated later.
                //For now, just store the requested profile name.
                if (kvpArgs.TryGetValue(projectName, out string requestedProfile))
                {
                    launchable.LaunchProfile.Name = requestedProfile;
                }
                else
                {
                    throw new ArgumentException($"Launch profile name not supplied for {projectName}.  Launcher requires a command-line argument like {projectName}=MyLaunchProfile with a valid launch profile name.  This also applies to other launched projects.  They must have a launch profile as a commandline argument, keyed by the project name.");
                }

                //set the project directory.
                if (dirs.TryGetValue(projectName, out string dir))
                {
                    launchable.ProjectDirectory = dir;
                }
                else
                {
                    throw new ArgumentException($"{projectName} not a referenced project in Launcher's .csproj");
                }

                launchables.Add(projectName, launchable);
            }
            return(launchables);
        }