/// <summary> /// Executes a program entry point synchronously, streaming some text as standard input, /// passing arguments and returning the result. /// </summary> /// <param name="main">The program entry point.</param> /// <param name="inputText">The text to be passed as standard input.</param> /// <param name="args">The arguments.</param> /// <returns>The <see cref="ExecuteResponse"/> returned by the simulated program run.</returns> public ExecuteResponse ExecuteWithInput(ProgramEntrypoint main, string inputText, params string[] args) { Covenant.Requires <ArgumentNullException>(main != null, nameof(main)); Covenant.Requires <ArgumentNullException>(inputText != null); return(ExecuteWithInput(main, Encoding.UTF8.GetBytes(inputText), args)); }
/// <summary> /// Executes a program entry point synchronously, streaming some bytes as standard input, /// passing arguments and returning the result. /// </summary> /// <param name="main">The program entry point.</param> /// <param name="inputBytes">The bytes to be passed as standard input.</param> /// <param name="args">The arguments.</param> /// <returns>The <see cref="ExecuteResponse"/> returned by the simulated program run.</returns> public ExecuteResponse ExecuteWithInput(ProgramEntrypoint main, byte[] inputBytes, params string[] args) { Covenant.Requires <ArgumentNullException>(main != null, nameof(main)); Covenant.Requires <ArgumentNullException>(inputBytes != null, nameof(inputBytes)); this.inputBytes = inputBytes; if (programThread != null) { throw new InvalidOperationException("Only one simulated [program] can run at a time."); } var orgSTDOUT = Console.Out; var orgSTDERR = Console.Error; try { // Capture standard output and error and stream the input // text as STDIN. var sbOut = new StringBuilder(); var sbErr = new StringBuilder(); using (var stdOutCapture = new StringWriter(sbOut)) { using (var stdErrCapture = new StringWriter(sbErr)) { var exitCode = 0; Console.SetOut(stdOutCapture); Console.SetError(stdErrCapture); // Simulate executing the program. programThread = new Thread(new ThreadStart(() => exitCode = main(args))); programThread.Start(); programThread.Join(); programThread = null; return(new ExecuteResponse() { ExitCode = exitCode, OutputText = sbOut.ToString(), ErrorText = sbErr.ToString() }); } } } finally { // Restore the standard files. Console.SetOut(orgSTDOUT); Console.SetError(orgSTDERR); } }
/// <summary> /// <para> /// Executes a program entry point asynchronously, without waiting for the command to complete. /// This is useful for commands that don't terminate by themselves. Call <see cref="TerminateFork()"/> /// to kill the running command. /// </para> /// <note> /// <b>IMPORTANT:</b> The <paramref name="main"/> simulated entry point must call /// <see cref="WaitForExit()"/>. This will block until the <see cref="TerminateFork"/> /// is called, returning when the program is expected to terminate itself. /// </note> /// </summary> /// <param name="main">The program entry point.</param> /// <param name="args">The arguments.</param> public void Fork(ProgramEntrypoint main, params string[] args) { Covenant.Requires <ArgumentNullException>(main != null, nameof(main)); if (programThread != null) { throw new InvalidOperationException("Only one simulated [program] can run at a time."); } programIsReady = false; programExitBeforeReady = false; programThread = new Thread( new ThreadStart( () => { programExitCode = main(args); if (!programIsReady) { programExitBeforeReady = true; } })); programThread.Name = "program-runner"; programThread.IsBackground = true; programThread.Start(); // We need to give the program enough time to do enough initialization // so the tests can succeed. We're going to rely on the program to // signal this by calling [ProgramReady()] which will set the event // we'll listen on. if (!programReadyEvent.WaitOne(forkTimeout)) { throw new TimeoutException($"The program runner timed out before the application called [{nameof(ProgramReady)}]."); } if (programExitBeforeReady) { throw new InvalidOperationException($"The program returned with [exitcode={programExitCode}] before calling [{nameof(ProgramReady)}]."); } }