This commit is contained in:
@ -13,9 +13,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/ProcessOps.hpp>
|
||||
|
||||
#include <IACore/IATest.hpp>
|
||||
#include <IACore/ProcessOps.hpp>
|
||||
|
||||
using namespace IACore;
|
||||
|
||||
@ -23,13 +22,13 @@ using namespace IACore;
|
||||
// Platform Abstraction for Test Commands
|
||||
// -----------------------------------------------------------------------------
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
# define CMD_ECHO_EXE "cmd.exe"
|
||||
# define CMD_ARG_PREFIX "/c echo"
|
||||
# define NULL_DEVICE "NUL"
|
||||
#define CMD_ECHO_EXE "cmd.exe"
|
||||
#define CMD_ARG_PREFIX "/c echo"
|
||||
#define NULL_DEVICE "NUL"
|
||||
#else
|
||||
# define CMD_ECHO_EXE "/bin/echo"
|
||||
# define CMD_ARG_PREFIX ""
|
||||
# define NULL_DEVICE "/dev/null"
|
||||
#define CMD_ECHO_EXE "/bin/echo"
|
||||
#define CMD_ARG_PREFIX ""
|
||||
#define NULL_DEVICE "/dev/null"
|
||||
#endif
|
||||
|
||||
IAT_BEGIN_BLOCK(Core, ProcessOps)
|
||||
@ -37,217 +36,228 @@ IAT_BEGIN_BLOCK(Core, ProcessOps)
|
||||
// -------------------------------------------------------------------------
|
||||
// 1. Basic Execution (Exit Code 0)
|
||||
// -------------------------------------------------------------------------
|
||||
bool TestBasicRun()
|
||||
{
|
||||
// Simple "echo hello"
|
||||
String captured;
|
||||
auto test_basic_run() -> bool {
|
||||
// Simple "echo hello"
|
||||
String captured;
|
||||
|
||||
auto result = ProcessOps::SpawnProcessSync(CMD_ECHO_EXE, CMD_ARG_PREFIX " HelloIA",
|
||||
[&](StringView line) { captured = line; });
|
||||
const auto result =
|
||||
ProcessOps::spawn_process_sync(CMD_ECHO_EXE, CMD_ARG_PREFIX " HelloIA",
|
||||
[&](StringView line) { captured = line; });
|
||||
|
||||
IAT_CHECK(result.has_value());
|
||||
IAT_CHECK_EQ(*result, 0); // Exit code 0
|
||||
IAT_CHECK(result.has_value());
|
||||
IAT_CHECK_EQ(*result, 0); // Exit code 0
|
||||
|
||||
// We check if "HelloIA" is contained or equal.
|
||||
IAT_CHECK(captured.find("HelloIA") != String::npos);
|
||||
// We check if "HelloIA" is contained or equal.
|
||||
IAT_CHECK(captured.find("HelloIA") != String::npos);
|
||||
|
||||
return TRUE;
|
||||
return true;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 2. Argument Parsing
|
||||
// -------------------------------------------------------------------------
|
||||
bool TestArguments()
|
||||
{
|
||||
Vec<String> lines;
|
||||
auto test_arguments() -> bool {
|
||||
Vec<String> lines;
|
||||
|
||||
// Echo two distinct words.
|
||||
// Windows: cmd.exe /c echo one two
|
||||
// Linux: /bin/echo one two
|
||||
String args = String(CMD_ARG_PREFIX) + " one two";
|
||||
if (args[0] == ' ')
|
||||
args.erase(0, 1); // cleanup space if prefix empty
|
||||
// Echo two distinct words.
|
||||
// Windows: cmd.exe /c echo one two
|
||||
// Linux: /bin/echo one two
|
||||
String args = String(CMD_ARG_PREFIX) + " one two";
|
||||
if (!args.empty() && args[0] == ' ') {
|
||||
args.erase(0, 1); // cleanup space if prefix empty
|
||||
}
|
||||
|
||||
auto result =
|
||||
ProcessOps::SpawnProcessSync(CMD_ECHO_EXE, args, [&](StringView line) { lines.push_back(String(line)); });
|
||||
const auto result =
|
||||
ProcessOps::spawn_process_sync(CMD_ECHO_EXE, args, [&](StringView line) {
|
||||
lines.push_back(String(line));
|
||||
});
|
||||
|
||||
IAT_CHECK_EQ(*result, 0);
|
||||
IAT_CHECK(lines.size() > 0);
|
||||
IAT_CHECK_EQ(*result, 0);
|
||||
IAT_CHECK(lines.size() > 0);
|
||||
|
||||
// Output should contain "one two"
|
||||
IAT_CHECK(lines[0].find("one two") != String::npos);
|
||||
// Output should contain "one two"
|
||||
IAT_CHECK(lines[0].find("one two") != String::npos);
|
||||
|
||||
return TRUE;
|
||||
return true;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 3. Error / Non-Zero Exit Codes
|
||||
// -------------------------------------------------------------------------
|
||||
bool TestExitCodes()
|
||||
{
|
||||
// We need a command that returns non-zero.
|
||||
// Windows: cmd /c exit 1
|
||||
// Linux: /bin/sh -c "exit 1"
|
||||
auto test_exit_codes() -> bool {
|
||||
// We need a command that returns non-zero.
|
||||
// Windows: cmd /c exit 1
|
||||
// Linux: /bin/sh -c "exit 1"
|
||||
|
||||
String cmd, arg;
|
||||
String cmd;
|
||||
String arg;
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
cmd = "cmd.exe";
|
||||
arg = "/c exit 42";
|
||||
cmd = "cmd.exe";
|
||||
arg = "/c exit 42";
|
||||
#else
|
||||
cmd = "/bin/sh";
|
||||
arg = "-c \"exit 42\""; // quotes needed for sh -c
|
||||
cmd = "/bin/sh";
|
||||
arg = "-c \"exit 42\""; // quotes needed for sh -c
|
||||
#endif
|
||||
|
||||
auto result = ProcessOps::SpawnProcessSync(cmd, arg, [](StringView) {});
|
||||
const auto result =
|
||||
ProcessOps::spawn_process_sync(cmd, arg, [](StringView) {});
|
||||
|
||||
IAT_CHECK(result.has_value());
|
||||
IAT_CHECK_EQ(*result, 42);
|
||||
IAT_CHECK(result.has_value());
|
||||
IAT_CHECK_EQ(*result, 42);
|
||||
|
||||
return TRUE;
|
||||
return true;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 4. Missing Executable Handling
|
||||
// -------------------------------------------------------------------------
|
||||
bool TestMissingExe()
|
||||
{
|
||||
// Try to run a random string
|
||||
auto result = ProcessOps::SpawnProcessSync("sdflkjghsdflkjg", "", [](StringView) {});
|
||||
auto test_missing_exe() -> bool {
|
||||
// Try to run a random string
|
||||
const auto result =
|
||||
ProcessOps::spawn_process_sync("sdflkjghsdflkjg", "", [](StringView) {});
|
||||
|
||||
// Windows: CreateProcess usually fails -> returns unexpected
|
||||
// Linux: execvp fails inside child, returns 127 via waitpid
|
||||
// Windows: CreateProcess usually fails -> returns unexpected
|
||||
// Linux: execvp fails inside child, returns 127 via waitpid
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
IAT_CHECK_NOT(result.has_value()); // Should be an error string
|
||||
IAT_CHECK_NOT(result.has_value()); // Should be an error string
|
||||
#else
|
||||
// Linux fork succeeds, but execvp fails, returning 127
|
||||
IAT_CHECK(result.has_value());
|
||||
IAT_CHECK_EQ(*result, 127);
|
||||
// Linux fork succeeds, but execvp fails, returning 127
|
||||
IAT_CHECK(result.has_value());
|
||||
IAT_CHECK_EQ(*result, 127);
|
||||
#endif
|
||||
|
||||
return TRUE;
|
||||
return true;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 5. Line Buffer Logic (The 4096 split test)
|
||||
// -------------------------------------------------------------------------
|
||||
bool TestLargeOutput()
|
||||
{
|
||||
// Need to generate output larger than the internal 4096 buffer
|
||||
// to ensure the "partial line" logic works when a line crosses a buffer boundary.
|
||||
auto test_large_output() -> bool {
|
||||
// Need to generate output larger than the internal 4096 buffer
|
||||
// to ensure the "partial line" logic works when a line crosses a buffer
|
||||
// boundary.
|
||||
|
||||
String massiveString;
|
||||
massiveString.reserve(5000);
|
||||
for (int i = 0; i < 500; ++i)
|
||||
massiveString += "1234567890"; // 5000 chars
|
||||
String massive_string;
|
||||
massive_string.reserve(5000);
|
||||
for (i32 i = 0; i < 500; ++i) {
|
||||
massive_string += "1234567890"; // 5000 chars
|
||||
}
|
||||
|
||||
String cmd, arg;
|
||||
String cmd;
|
||||
String arg;
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
cmd = "cmd.exe";
|
||||
// Windows has command line length limits (~8k), 5k is safe.
|
||||
arg = "/c echo " + massiveString;
|
||||
cmd = "cmd.exe";
|
||||
// Windows has command line length limits (~8k), 5k is safe.
|
||||
arg = "/c echo " + massive_string;
|
||||
#else
|
||||
cmd = "/bin/echo";
|
||||
arg = massiveString;
|
||||
cmd = "/bin/echo";
|
||||
arg = massive_string;
|
||||
#endif
|
||||
|
||||
String captured;
|
||||
auto result = ProcessOps::SpawnProcessSync(cmd, arg, [&](StringView line) { captured += line; });
|
||||
String captured;
|
||||
const auto result = ProcessOps::spawn_process_sync(
|
||||
cmd, arg, [&](StringView line) { captured += line; });
|
||||
|
||||
IAT_CHECK(result.has_value());
|
||||
IAT_CHECK_EQ(*result, 0);
|
||||
IAT_CHECK(result.has_value());
|
||||
IAT_CHECK_EQ(*result, 0);
|
||||
|
||||
// If the LineBuffer failed to stitch chunks, the length wouldn't match
|
||||
// or we would get multiple callbacks if we expected 1 line.
|
||||
IAT_CHECK_EQ(captured.length(), massiveString.length());
|
||||
// If the LineBuffer failed to stitch chunks, the length wouldn't match
|
||||
// or we would get multiple callbacks if we expected 1 line.
|
||||
IAT_CHECK_EQ(captured.length(), massive_string.length());
|
||||
|
||||
return TRUE;
|
||||
return true;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 6. Multi-Line Handling
|
||||
// -------------------------------------------------------------------------
|
||||
bool TestMultiLine()
|
||||
{
|
||||
// Windows: cmd /c "echo A && echo B"
|
||||
// Linux: /bin/sh -c "echo A; echo B"
|
||||
auto test_multi_line() -> bool {
|
||||
// Windows: cmd /c "echo A && echo B"
|
||||
// Linux: /bin/sh -c "echo A; echo B"
|
||||
|
||||
String cmd, arg;
|
||||
String cmd;
|
||||
String arg;
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
cmd = "cmd.exe";
|
||||
arg = "/c \"echo LineA && echo LineB\"";
|
||||
cmd = "cmd.exe";
|
||||
arg = "/c \"echo LineA && echo LineB\"";
|
||||
#else
|
||||
cmd = "/bin/sh";
|
||||
arg = "-c \"echo LineA; echo LineB\"";
|
||||
cmd = "/bin/sh";
|
||||
arg = "-c \"echo LineA; echo LineB\"";
|
||||
#endif
|
||||
|
||||
int lineCount = 0;
|
||||
bool foundA = false;
|
||||
bool foundB = false;
|
||||
i32 line_count = 0;
|
||||
bool found_a = false;
|
||||
bool found_b = false;
|
||||
|
||||
UNUSED(ProcessOps::SpawnProcessSync(cmd, arg, [&](StringView line) {
|
||||
lineCount++;
|
||||
if (line.find("LineA") != String::npos)
|
||||
foundA = true;
|
||||
if (line.find("LineB") != String::npos)
|
||||
foundB = true;
|
||||
}));
|
||||
IA_UNUSED const auto res =
|
||||
ProcessOps::spawn_process_sync(cmd, arg, [&](StringView line) {
|
||||
line_count++;
|
||||
if (line.find("LineA") != String::npos) {
|
||||
found_a = true;
|
||||
}
|
||||
if (line.find("LineB") != String::npos) {
|
||||
found_b = true;
|
||||
}
|
||||
});
|
||||
|
||||
IAT_CHECK(foundA);
|
||||
IAT_CHECK(foundB);
|
||||
// We expect at least 2 lines.
|
||||
// (Windows sometimes echoes the command itself depending on echo settings, but we check contents)
|
||||
IAT_CHECK(lineCount >= 2);
|
||||
IAT_CHECK(found_a);
|
||||
IAT_CHECK(found_b);
|
||||
// We expect at least 2 lines.
|
||||
// (Windows sometimes echoes the command itself depending on echo settings,
|
||||
// but we check contents)
|
||||
IAT_CHECK(line_count >= 2);
|
||||
|
||||
return TRUE;
|
||||
return true;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 6. Complex Command Line Arguments Handling
|
||||
// -------------------------------------------------------------------------
|
||||
bool TestComplexArguments()
|
||||
{
|
||||
// Should parse as 3 arguments:
|
||||
// 1. -DDEFINED_MSG="Hello World"
|
||||
// 2. -v
|
||||
// 3. path/to/file
|
||||
String complexArgs = "-DDEFINED_MSG=\\\"Hello World\\\" -v path/to/file";
|
||||
auto test_complex_arguments() -> bool {
|
||||
// Should parse as 3 arguments:
|
||||
// 1. -DDEFINED_MSG="Hello World"
|
||||
// 2. -v
|
||||
// 3. path/to/file
|
||||
const String complex_args =
|
||||
"-DDEFINED_MSG=\\\"Hello World\\\" -v path/to/file";
|
||||
|
||||
String cmd = CMD_ECHO_EXE;
|
||||
const String cmd = CMD_ECHO_EXE;
|
||||
|
||||
String finalArgs;
|
||||
String final_args;
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
finalArgs = "/c echo " + complexArgs;
|
||||
final_args = "/c echo " + complex_args;
|
||||
#else
|
||||
finalArgs = complexArgs;
|
||||
final_args = complex_args;
|
||||
#endif
|
||||
|
||||
String captured;
|
||||
auto result = ProcessOps::SpawnProcessSync(cmd, finalArgs, [&](StringView line) { captured += line; });
|
||||
String captured;
|
||||
const auto result = ProcessOps::spawn_process_sync(
|
||||
cmd, final_args, [&](StringView line) { captured += line; });
|
||||
|
||||
IAT_CHECK(result.has_value());
|
||||
IAT_CHECK_EQ(*result, 0);
|
||||
IAT_CHECK(result.has_value());
|
||||
IAT_CHECK_EQ(*result, 0);
|
||||
|
||||
// Verify the quotes were preserved in the output
|
||||
IAT_CHECK(captured.find("Hello World") != String::npos);
|
||||
return TRUE;
|
||||
// Verify the quotes were preserved in the output
|
||||
IAT_CHECK(captured.find("Hello World") != String::npos);
|
||||
return true;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Registration
|
||||
// -------------------------------------------------------------------------
|
||||
IAT_BEGIN_TEST_LIST()
|
||||
IAT_ADD_TEST(TestBasicRun);
|
||||
IAT_ADD_TEST(TestArguments);
|
||||
IAT_ADD_TEST(TestExitCodes);
|
||||
IAT_ADD_TEST(TestMissingExe);
|
||||
IAT_ADD_TEST(TestLargeOutput);
|
||||
IAT_ADD_TEST(TestMultiLine);
|
||||
IAT_ADD_TEST(TestComplexArguments);
|
||||
IAT_ADD_TEST(test_basic_run);
|
||||
IAT_ADD_TEST(test_arguments);
|
||||
IAT_ADD_TEST(test_exit_codes);
|
||||
IAT_ADD_TEST(test_missing_exe);
|
||||
IAT_ADD_TEST(test_large_output);
|
||||
IAT_ADD_TEST(test_multi_line);
|
||||
IAT_ADD_TEST(test_complex_arguments);
|
||||
IAT_END_TEST_LIST()
|
||||
|
||||
IAT_END_BLOCK()
|
||||
|
||||
IAT_REGISTER_ENTRY(Core, ProcessOps)
|
||||
IAT_REGISTER_ENTRY(Core, ProcessOps)
|
||||
Reference in New Issue
Block a user