Skip to content

Commit

Permalink
Process: Cleanup tests and improve documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
Pagghiu committed Mar 9, 2024
1 parent 6f7addb commit 771300e
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 118 deletions.
44 changes: 14 additions & 30 deletions Libraries/Process/Process.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,13 @@ struct SC::ProcessID
int32_t pid = 0;
};

/// @brief Execute a child process.
///
/// @brief Execute a child process with standard file descriptors redirection.
/// @n
/// Features:
/// - Redirecting standard in/out/err of a child process to a Pipe
///
/// - Redirect standard in/out/err of a child process to a Pipe
/// - Inherit child process file descriptors from parent process
/// - Ignore (silence) child process standard file descriptor
/// - Wait for the child process exit code
///
/// Example: execute child process (launch and wait for it to fully execute)
Expand Down Expand Up @@ -237,33 +240,14 @@ struct SC::Process
/// SC::PipeDescriptor from [File](@ref library_file) library is used to chain read / write endpoints of different
/// processes together.
///
/// @n
/**
* Example: print result `ls ~ | grep desktop` in current terminal
* @code{.cpp}
ProcessChain chain;
Process p1, p2;
SC_TRY(chain.pipe(p1, {"ls", "~"}));
SC_TRY(chain.pipe(p2, {"grep", "Desktop"}));
SC_TRY(chain.launch());
SC_TRY(chain.waitForExitSync());
* @endcode
*
* Example: read result of `ls ~ | grep desktop` into a String
* @code{.cpp}
ProcessChain chain;
Process p1, p2;
SC_TRY(chain.pipe(p1, {"ls", "~"}));
SC_TRY(chain.pipe(p2, {"grep", "Desktop"}));
ProcessChain::Options options;
options.pipeSTDOUT = true;
SC_TRY(chain.launch(options));
String output(StringEncoding::Ascii);
SC_TRY(chain.readStdOutUntilEOFSync(output));
SC_TRY(chain.waitForExitSync());
// ... Do something with the 'output' string
* @endcode
* */
/// Example: Inherit stdout file descriptor
/// \snippet Libraries/Process/Tests/ProcessTest.cpp processChainInheritDualSnippet
///
/// Example: Read stderr and stdout into a string
/// \snippet Libraries/Process/Tests/ProcessTest.cpp processChainPipeSingleSnippet
///
/// Example: Read standard output into a string using a Pipe
/// \snippet Libraries/Process/Tests/ProcessTest.cpp processChainPipeDualSnippet
struct SC::ProcessChain
{
/// @brief Add a process to the chain, with given arguments
Expand Down
255 changes: 167 additions & 88 deletions Libraries/Process/Tests/ProcessTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,120 +22,199 @@ struct SC::ProcessTest : public SC::TestCase
}
if (test_section("Process inherit"))
{
// Launches a process that does exists
Process process;
#if SC_PLATFORM_WINDOWS
SC_TEST_EXPECT(process.exec({"where", "where.exe"}));
#else
SC_TEST_EXPECT(process.exec({"which", "sudo"}));
#endif
// Launches a process that does exists, inheriting its standard output
switch (HostPlatform)
{
case Platform::Windows: {
SC_TEST_EXPECT(Process().exec({"where", "where.exe"}));
}
break;
default: { // Posix
SC_TEST_EXPECT(Process().exec({"which", "sudo"}));
}
break;
}

// Will print either /usr/bin/sudo or C:\Windows\System32\where.exe to parent console
}
if (test_section("Process ignore"))
{
// Launches a process and ignore its stdoutput
Process process;
#if SC_PLATFORM_WINDOWS
SC_TEST_EXPECT(process.exec({"where", "where.exe"}, Process::StdOut::Ignore()));
#else
SC_TEST_EXPECT(process.exec({"which", "sudo"}, Process::StdOut::Ignore()));
#endif
// Launches a process ignoring its standard output
switch (HostPlatform)
{
case Platform::Windows: {
SC_TEST_EXPECT(Process().exec({"where", "where.exe"}, Process::StdOut::Ignore()));
}
break;
default: { // Posix
SC_TEST_EXPECT(Process().exec({"which", "sudo"}, Process::StdOut::Ignore()));
}
break;
}
// Nothing will be printed on the parent stdout (console / file)
}
if (test_section("Process redirect output"))
{
// Launches a process and read its stdout into a String
SmallString<255> output = StringEncoding::Ascii;
#if SC_PLATFORM_WINDOWS
SC_TEST_EXPECT(Process().exec({"where", "where.exe"}, output));
SC_TEST_EXPECT(output.view() == "C:\\Windows\\System32\\where.exe\r\n");
#else
SC_TEST_EXPECT(Process().exec({"which", "sudo"}, output));
SC_TEST_EXPECT(output.view() == "/usr/bin/sudo\n");
#endif
switch (HostPlatform)
{
case Platform::Windows: {
SC_TEST_EXPECT(Process().exec({"where", "where.exe"}, output));
SC_TEST_EXPECT(output.view() == "C:\\Windows\\System32\\where.exe\r\n");
}
break;
default: { // Posix
SC_TEST_EXPECT(Process().exec({"which", "sudo"}, output));
SC_TEST_EXPECT(output.view() == "/usr/bin/sudo\n");
}
break;
}
}
if (test_section("ProcessChain inherit single"))
{
// Creates a process chain with a single process
Process p1;
ProcessChain chain;
#if SC_PLATFORM_WINDOWS
SC_TEST_EXPECT(chain.pipe(p1, {"where", "where.exe"}));
#else
SC_TEST_EXPECT(chain.pipe(p1, {"echo", "DOCTORI"}));
#endif
SC_TEST_EXPECT(chain.exec());
processChainInheritSingle();
}
if (test_section("ProcessChain inherit dual"))
{
// Executes two processes piping output of process p1 to input of process p2.
// Then reads the output of the last process in the chain and check its correctness.
ProcessChain chain;
Process p1, p2;
// Print "Salve\nDoctori" on Windows and Posix and then grep for "Doc" (that returns "Doctori")
#if SC_PLATFORM_WINDOWS
StringView expectedOutput = "Doctori\r\n";
SC_TEST_EXPECT(chain.pipe(p1, {"cmd", "/C", "echo", "Salve", "&", "echo", "Doctori"}));
SC_TEST_EXPECT(chain.pipe(p2, {"findstr", "Doc"}));
#else

StringView expectedOutput = "Doctori\n";
SC_TEST_EXPECT(chain.pipe(p1, {"echo", "Salve\nDoctori"}));
SC_TEST_EXPECT(chain.pipe(p2, {"grep", "Doc"}));
#endif
String output;
SC_TEST_EXPECT(chain.exec(output));
SC_TEST_EXPECT(output == expectedOutput);
processChainInheritDual();
}
if (test_section("ProcessChain pipe single"))
{
// Executes two processes piping output of process p1 to input of process p2.
// Reads p2 stdout and stderr into a pair of Strings.
ProcessChain chain;
Process p1;
#if SC_PLATFORM_WINDOWS
StringView expectedOutput = "C:\\Windows\\System32\\where.exe\r\n";
SC_TEST_EXPECT(chain.pipe(p1, {"where", "where.exe"}));
#else
StringView expectedOutput = "DOCTORI\n";
SC_TEST_EXPECT(chain.pipe(p1, {"echo", "DOCTORI"}));
#endif
String stdOut(StringEncoding::Ascii);
String stdErr(StringEncoding::Ascii);
SC_TEST_EXPECT(chain.exec(stdOut, Process::StdIn::Inherit(), stdErr));
SC_TEST_EXPECT(stdOut == expectedOutput);
SC_TEST_EXPECT(stdErr.isEmpty());
processChainPipeSingle();
}
if (test_section("ProcessChain pipe dual"))
{
// Chain two processes and read the stdout of the last one into a String using a PipeDescriptor
ProcessChain chain;
String output(StringEncoding::Ascii);
Process p1, p2;
#if SC_PLATFORM_WINDOWS
StringView expectedOutput = "WHERE [/R dir] [/Q] [/F] [/T] pattern...\r\n";
SC_TEST_EXPECT(chain.pipe(p1, {"where", "/?"}));
SC_TEST_EXPECT(chain.pipe(p2, {"findstr", "dir]"}));
#else
StringView expectedOutput = "sbin\n";
SC_TEST_EXPECT(chain.pipe(p1, {"ls", "/"}));
SC_TEST_EXPECT(chain.pipe(p2, {"grep", "sbin"}));
#endif
PipeDescriptor outputPipe;
SC_TEST_EXPECT(chain.launch(outputPipe));
SC_TEST_EXPECT(outputPipe.readPipe.readUntilEOF(output));
SC_TEST_EXPECT(chain.waitForExitSync());
SC_TEST_EXPECT(output == expectedOutput);
processChainPipeDual();
}
}

SC::Result processSnippet1();
SC::Result processSnippet2();
SC::Result processSnippet3();
SC::Result processSnippet4();
SC::Result processSnippet5();
void processChainInheritSingle();
void processChainInheritDual();
void processChainPipeSingle();
void processChainPipeDual();

Result processSnippet1();
Result processSnippet2();
Result processSnippet3();
Result processSnippet4();
Result processSnippet5();
};

void SC::ProcessTest::processChainInheritSingle()
{
//! [processChainInheritSingleSnippet]
// Creates a process chain with a single process
Process p1;
ProcessChain chain;
switch (HostPlatform)
{
case Platform::Windows: {
SC_TEST_EXPECT(chain.pipe(p1, {"where", "where.exe"}));
}
break;
default: { // Posix
SC_TEST_EXPECT(chain.pipe(p1, {"echo", "DOCTORI"}));
}
break;
}
SC_TEST_EXPECT(chain.exec());
//! [processChainInheritSingleSnippet]
}

void SC::ProcessTest::processChainInheritDual()
{
//! [processChainInheritDualSnippet]
// Executes two processes piping output of process p1 to input of process p2.
// Then reads the output of the last process in the chain and check its correctness.
ProcessChain chain;
Process p1, p2;
// Print "Salve\nDoctori" on Windows and Posix and then grep for "Doc"
StringView expectedOutput;
switch (HostPlatform)
{
case Platform::Windows: {
expectedOutput = "Doctori\r\n";
SC_TEST_EXPECT(chain.pipe(p1, {"cmd", "/C", "echo", "Salve", "&", "echo", "Doctori"}));
SC_TEST_EXPECT(chain.pipe(p2, {"findstr", "Doc"}));
}
break;
default: { // Posix
expectedOutput = "Doctori\n";
SC_TEST_EXPECT(chain.pipe(p1, {"echo", "Salve\nDoctori"}));
SC_TEST_EXPECT(chain.pipe(p2, {"grep", "Doc"}));
}
break;
}
String output;
SC_TEST_EXPECT(chain.exec(output));
SC_TEST_EXPECT(output == expectedOutput);
//! [processChainInheritDualSnippet]
}

void SC::ProcessTest::processChainPipeSingle()
{
//! [processChainPipeSingleSnippet]
// Executes two processes piping output of process p1 to input of process p2.
// Reads p2 stdout and stderr into a pair of Strings.
ProcessChain chain;
Process p1;
StringView expectedOutput;
switch (HostPlatform)
{
case Platform::Windows: {
expectedOutput = "C:\\Windows\\System32\\where.exe\r\n";
SC_TEST_EXPECT(chain.pipe(p1, {"where", "where.exe"}));
}
break;
default: { // Posix
expectedOutput = "DOCTORI\n";
SC_TEST_EXPECT(chain.pipe(p1, {"echo", "DOCTORI"}));
}
break;
}

String stdOut(StringEncoding::Ascii);
String stdErr(StringEncoding::Ascii);
SC_TEST_EXPECT(chain.exec(stdOut, Process::StdIn::Inherit(), stdErr));
SC_TEST_EXPECT(stdOut == expectedOutput);
SC_TEST_EXPECT(stdErr.isEmpty());
//! [processChainPipeSingleSnippet]
}

void SC::ProcessTest::processChainPipeDual()
{
//! [processChainPipeDualSnippet]
// Chain two processes and read the last stdout into a String (using a pipe)
ProcessChain chain;

String output(StringEncoding::Ascii);
Process p1, p2;

StringView expectedOutput;
switch (HostPlatform)
{
case Platform::Windows: {
expectedOutput = "WHERE [/R dir] [/Q] [/F] [/T] pattern...\r\n";
SC_TEST_EXPECT(chain.pipe(p1, {"where", "/?"}));
SC_TEST_EXPECT(chain.pipe(p2, {"findstr", "dir]"}));
}
break;
default: { // Posix
expectedOutput = "sbin\n";
SC_TEST_EXPECT(chain.pipe(p1, {"ls", "/"}));
SC_TEST_EXPECT(chain.pipe(p2, {"grep", "sbin"}));
}
break;
}
PipeDescriptor outputPipe;
SC_TEST_EXPECT(chain.launch(outputPipe));
SC_TEST_EXPECT(outputPipe.readPipe.readUntilEOF(output));
SC_TEST_EXPECT(chain.waitForExitSync());
SC_TEST_EXPECT(output == expectedOutput);
//! [processChainPipeDualSnippet]
}

SC::Result SC::ProcessTest::processSnippet1()
{
//! [ProcessSnippet1]
Expand Down Expand Up @@ -177,7 +256,7 @@ SC::Result SC::ProcessTest::processSnippet4()
// Example: execute child process, filling its stdin with a StringView
// This is equivalent of shell command:
// `echo "child process" | grep process`
SC_TRY(Process().exec({"grep", "process"}, Process::StdOut::Inherit(), "child process"));
SC_TRY(Process().exec({"grep", "process"}, Process::StdOut::Inherit(), "child proc"));
//! [ProcessSnippet4]
return Result(true);
}
Expand Down

0 comments on commit 771300e

Please sign in to comment.