// IACore-OSS; The Core Library for All IA Open Source Projects // Copyright (C) 2025 IAS (ias@iasoft.dev) // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include #include using namespace IACore; // ----------------------------------------------------------------------------- // Platform Abstraction for Test Commands // ----------------------------------------------------------------------------- #if IA_PLATFORM_WINDOWS #define CMD_ECHO_EXE "cmd.exe" // Windows requires /c to run a command string #define CMD_ARG_PREFIX "/c" #define NULL_DEVICE "NUL" #else #define CMD_ECHO_EXE "/bin/echo" #define CMD_ARG_PREFIX "" #define NULL_DEVICE "/dev/null" #endif IAT_BEGIN_BLOCK(Core, Process) // ------------------------------------------------------------------------- // 1. Basic Execution (Exit Code 0) // ------------------------------------------------------------------------- BOOL TestBasicRun() { // Simple "echo hello" String captured; auto result = ProcessOps::SpawnProcessSync(CMD_ECHO_EXE, CMD_ARG_PREFIX " HelloIA", [&](StringView line) { captured = line; } ); IAT_CHECK(result.has_value()); IAT_CHECK_EQ(*result, 0); // Exit code 0 // Note: Echo might add newline, but your LineBuffer trims/handles it. // We check if "HelloIA" is contained or equal. IAT_CHECK(captured.find("HelloIA") != String::npos); return TRUE; } // ------------------------------------------------------------------------- // 2. Argument Parsing // ------------------------------------------------------------------------- BOOL TestArguments() { Vector 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 auto result = ProcessOps::SpawnProcessSync(CMD_ECHO_EXE, args, [&](StringView line) { lines.push_back(String(line)); } ); IAT_CHECK_EQ(*result, 0); IAT_CHECK(lines.size() > 0); // Output should contain "one two" IAT_CHECK(lines[0].find("one two") != String::npos); 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" String cmd, arg; #if IA_PLATFORM_WINDOWS cmd = "cmd.exe"; arg = "/c exit 42"; #else cmd = "/bin/sh"; arg = "-c \"exit 42\""; // quotes needed for sh -c #endif auto result = ProcessOps::SpawnProcessSync(cmd, arg, [](StringView){}); IAT_CHECK(result.has_value()); IAT_CHECK_EQ(*result, 42); return TRUE; } // ------------------------------------------------------------------------- // 4. Missing Executable Handling // ------------------------------------------------------------------------- BOOL TestMissingExe() { // Try to run a random string auto result = ProcessOps::SpawnProcessSync("sdflkjghsdflkjg", "", [](StringView){}); // 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 #else // Linux fork succeeds, but execvp fails, returning 127 IAT_CHECK(result.has_value()); IAT_CHECK_EQ(*result, 127); #endif return TRUE; } // ------------------------------------------------------------------------- // 5. Line Buffer Logic (The 4096 split test) // ------------------------------------------------------------------------- BOOL TestLargeOutput() { // We need to generate output larger than the internal 4096 buffer // to ensure the "partial line" logic works when a line crosses a buffer boundary. // We will construct a python script or shell command to print a massive line. // Cross platform approach: Use Python if available, or just a long echo. // Let's assume 'python3' or 'python' is in path, otherwise skip? // Safer: Use pure shell loop if possible, or just a massive command line arg. String massiveString; massiveString.reserve(5000); for(int i=0; i<500; ++i) massiveString += "1234567890"; // 5000 chars String cmd, arg; #if IA_PLATFORM_WINDOWS cmd = "cmd.exe"; // Windows has command line length limits (~8k), 5k is safe. arg = "/c echo " + massiveString; #else cmd = "/bin/echo"; arg = massiveString; #endif String captured; auto result = ProcessOps::SpawnProcessSync(cmd, arg, [&](StringView line) { captured += line; } ); 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()); return TRUE; } // ------------------------------------------------------------------------- // 6. Multi-Line Handling // ------------------------------------------------------------------------- BOOL TestMultiLine() { // Windows: cmd /c "echo A && echo B" // Linux: /bin/sh -c "echo A; echo B" String cmd, arg; #if IA_PLATFORM_WINDOWS cmd = "cmd.exe"; arg = "/c \"echo LineA && echo LineB\""; #else cmd = "/bin/sh"; arg = "-c \"echo LineA; echo LineB\""; #endif int lineCount = 0; bool foundA = false; bool foundB = 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; })); 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); 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_END_TEST_LIST() IAT_END_BLOCK() int main(int argc, char* argv[]) { UNUSED(argc); UNUSED(argv); // Define runner (StopOnFail=false, Verbose=true) ia::iatest::runner testRunner; // Run the BinaryReader block testRunner.testBlock(); return 0; }