247 lines
8.2 KiB
C++
247 lines
8.2 KiB
C++
// 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 <https://www.gnu.org/licenses/>.
|
|
|
|
#include <IACore/ProcessOps.hpp>
|
|
|
|
#include <IACore/IATest.hpp>
|
|
|
|
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<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
|
|
|
|
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<false, true> testRunner;
|
|
|
|
// Run the BinaryReader block
|
|
testRunner.testBlock<Core_Process>();
|
|
|
|
return 0;
|
|
} |