From 360a9858e33fc83763b0d82d16a2e2b331df5550 Mon Sep 17 00:00:00 2001 From: Wolfgang Thaller Date: Sun, 1 Oct 2017 02:42:02 +0200 Subject: [PATCH] Automated tests! --- AutomatedTests/CMakeLists.txt | 20 ++++ AutomatedTests/Empty.c | 4 + AutomatedTests/File.c | 6 + AutomatedTests/ReallyEmpty.c | 4 + AutomatedTests/Test.c | 6 + AutomatedTests/Test.h | 40 +++++++ AutomatedTests/Timeout.c | 11 ++ CMakeLists.txt | 4 + LaunchAPPL/CMakeLists.txt | 7 ++ LaunchAPPL/LaunchAPPL.cc | 215 ++++++++++++++++++++++++++++++++++ 10 files changed, 317 insertions(+) create mode 100644 AutomatedTests/CMakeLists.txt create mode 100644 AutomatedTests/Empty.c create mode 100644 AutomatedTests/File.c create mode 100644 AutomatedTests/ReallyEmpty.c create mode 100644 AutomatedTests/Test.c create mode 100644 AutomatedTests/Test.h create mode 100644 AutomatedTests/Timeout.c create mode 100644 LaunchAPPL/CMakeLists.txt create mode 100644 LaunchAPPL/LaunchAPPL.cc diff --git a/AutomatedTests/CMakeLists.txt b/AutomatedTests/CMakeLists.txt new file mode 100644 index 0000000000..4378cddb18 --- /dev/null +++ b/AutomatedTests/CMakeLists.txt @@ -0,0 +1,20 @@ +enable_testing() + +add_application(ReallyEmpty ReallyEmpty.c) +add_test(NAME ReallyEmpty COMMAND LaunchAPPL -e ReallyEmpty.bin) + +add_application(Empty Empty.c) +add_test(NAME Empty COMMAND LaunchAPPL -e Empty.bin) + +add_application(File File.c) +add_test(NAME File + COMMAND LaunchAPPL -e File.bin --logfile=out -t5 + ) +set_tests_properties(File PROPERTIES PASS_REGULAR_EXPRESSION "OK") + +add_application(Timeout Timeout.c) +add_test(NAME Timeout + COMMAND sh -c "! LaunchAPPL -e Timeout.bin --logfile=out -t2" + ) +set_tests_properties(Timeout PROPERTIES PASS_REGULAR_EXPRESSION "One;Two" + FAIL_REGULAR_EXPRESSION "Three") diff --git a/AutomatedTests/Empty.c b/AutomatedTests/Empty.c new file mode 100644 index 0000000000..a46866d92e --- /dev/null +++ b/AutomatedTests/Empty.c @@ -0,0 +1,4 @@ +int main() +{ + return 0; +} diff --git a/AutomatedTests/File.c b/AutomatedTests/File.c new file mode 100644 index 0000000000..1c2595a4b5 --- /dev/null +++ b/AutomatedTests/File.c @@ -0,0 +1,6 @@ +#include "Test.h" + +int main() +{ + TEST_LOG_SIZED("OK", 2); +} diff --git a/AutomatedTests/ReallyEmpty.c b/AutomatedTests/ReallyEmpty.c new file mode 100644 index 0000000000..81e5f841aa --- /dev/null +++ b/AutomatedTests/ReallyEmpty.c @@ -0,0 +1,4 @@ +void _start() +{ +} + diff --git a/AutomatedTests/Test.c b/AutomatedTests/Test.c new file mode 100644 index 0000000000..5b25106471 --- /dev/null +++ b/AutomatedTests/Test.c @@ -0,0 +1,6 @@ +#include "Test.h" + +void TestLog(const char *str) +{ + TEST_LOG_SIZED(str, strlen(str)); +} diff --git a/AutomatedTests/Test.h b/AutomatedTests/Test.h new file mode 100644 index 0000000000..f8970d0f4d --- /dev/null +++ b/AutomatedTests/Test.h @@ -0,0 +1,40 @@ +#ifndef TEST_H +#define TEST_H + +#include +#include + +#define TEST_LOG_SIZED(str, size) \ + do { \ + HParamBlockRec _hpb; \ + \ + unsigned char _fileName[4]; \ + _fileName[0] = 3; \ + _fileName[1] = 'o'; \ + _fileName[2] = 'u'; \ + _fileName[3] = 't'; \ + \ + _hpb.ioParam.ioCompletion = NULL; \ + _hpb.ioParam.ioNamePtr = (StringPtr)_fileName; \ + _hpb.ioParam.ioVRefNum = 0; \ + _hpb.fileParam.ioDirID = 0; \ + _hpb.ioParam.ioPermssn = fsRdWrPerm; \ + PBHOpenSync(&_hpb); \ + \ + _hpb.ioParam.ioBuffer = str; \ + _hpb.ioParam.ioReqCount = size; \ + _hpb.ioParam.ioPosMode = fsFromLEOF; \ + _hpb.ioParam.ioPosOffset = 0; \ + PBWriteSync((void*)&_hpb); \ + char _newline = '\n'; \ + _hpb.ioParam.ioBuffer = &_newline; \ + _hpb.ioParam.ioReqCount = 1; \ + _hpb.ioParam.ioPosMode = fsFromLEOF; \ + _hpb.ioParam.ioPosOffset = 0; \ + PBWriteSync((void*)&_hpb); \ + PBCloseSync((void*)&_hpb); \ + } while(0); + +void TestLog(const char *str); + +#endif // TEST_H diff --git a/AutomatedTests/Timeout.c b/AutomatedTests/Timeout.c new file mode 100644 index 0000000000..ec6624054c --- /dev/null +++ b/AutomatedTests/Timeout.c @@ -0,0 +1,11 @@ +#include "Test.h" + +int main() +{ + TEST_LOG_SIZED("One",3); + TEST_LOG_SIZED("Two",3); + for(;;) + ; + TEST_LOG_SIZED("Three",5); + return 0; +} diff --git a/CMakeLists.txt b/CMakeLists.txt index b65711f06c..d5514ae520 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,6 +48,9 @@ add_subdirectory(Samples/Launcher) add_subdirectory(Samples/SystemExtension) endif() +enable_testing() +add_subdirectory(AutomatedTests) + else() set(RETRO68_ROOT ${CMAKE_INSTALL_PREFIX}) @@ -62,4 +65,5 @@ add_subdirectory(Rez) add_subdirectory(ConvertObj) add_subdirectory(PEFTools) add_subdirectory(Elf2Mac) +add_subdirectory(LaunchAPPL) endif() diff --git a/LaunchAPPL/CMakeLists.txt b/LaunchAPPL/CMakeLists.txt new file mode 100644 index 0000000000..1e98c88d02 --- /dev/null +++ b/LaunchAPPL/CMakeLists.txt @@ -0,0 +1,7 @@ +find_package(Boost COMPONENTS filesystem program_options) + +add_executable(LaunchAPPL LaunchAPPL.cc) +target_include_directories(LaunchAPPL PRIVATE ${CMAKE_INSTALL_PREFIX}/include ${Boost_INCLUDE_DIR}) +target_link_libraries(LaunchAPPL ResourceFiles ${Boost_LIBRARIES}) + +install(TARGETS LaunchAPPL RUNTIME DESTINATION bin) diff --git a/LaunchAPPL/LaunchAPPL.cc b/LaunchAPPL/LaunchAPPL.cc new file mode 100644 index 0000000000..626a3ee75f --- /dev/null +++ b/LaunchAPPL/LaunchAPPL.cc @@ -0,0 +1,215 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +#include "ResourceFork.h" +#include "ResourceFile.h" + +namespace po = boost::program_options; +namespace fs = boost::filesystem; + +using std::string; +using std::vector; + +static po::options_description desc; +po::variables_map options; + +static void usage() +{ + std::cerr << "Usage: " << "LaunchAPPL [options] appl-file\n"; + std::cerr << desc << std::endl; +} + + +int ChildProcess(string program, vector args) +{ + std::vector argv; + argv.push_back(program.c_str()); + for(string& s : args) + argv.push_back(s.c_str()); + argv.push_back(NULL); + + int timeout = options.count("timeout") ? options["timeout"].as() : 0; + + pid_t pid = fork(); + if(pid < 0) + { + perror("unable to fork"); + return 1; + } + else if(pid == 0) + { + pid_t worker_pid = timeout ? fork() : 0; + if(worker_pid < 0) + { + perror("unable to fork"); + _exit(1); + } + if(worker_pid == 0) + { + execvp(argv[0], const_cast (argv.data())); + perror("exec failed"); + _exit(1); + } + + pid_t timeout_pid = fork(); + if(timeout_pid < 0) + { + perror("unable to fork"); + _exit(1); + } + if(timeout_pid == 0) + { + sleep(timeout); + _exit(0); + } + int wstatus; + pid_t exited_pid = wait(&wstatus); + if(exited_pid == worker_pid) + { + kill(timeout_pid, SIGKILL); + wait(NULL); + if(!WIFEXITED(wstatus)) + { + return 1; + } + else + { + int exitcode = WEXITSTATUS(wstatus); + _exit(exitcode); + } + } + else + { + kill(worker_pid, SIGKILL); + wait(NULL); + _exit(1); + } + } + else + { + int wstatus; + int result = 0; + do + { + result = waitpid(pid, &wstatus, 0); + } while(result == -1 && errno == EINTR); + + if(!WIFEXITED(wstatus)) + { + return 1; + } + else + { + int exitcode = WEXITSTATUS(wstatus); + return exitcode; + } + } + +} + +int main(int argc, char *argv[]) +{ + desc.add_options() + ("help,h", "show this help message") + ("executor-path", po::value()->default_value("executor"),"path to executor") + ("executor,e", "run using executor") + ("timeout,t", po::value(),"abort after timeout") + ("logfile", po::value(), "read log file") + ("result,r", "TEST 128") + ; + po::options_description hidden, alldesc; + hidden.add_options() + ("application,a", po::value(), "application" ) + ; + alldesc.add(desc).add(hidden); + + try + { + auto parsed = po::command_line_parser(argc, argv) + .options(alldesc) + .positional(po::positional_options_description().add("application", -1)) + .style(po::command_line_style::default_style) + .run(); + + po::store(parsed, options); + } + catch(po::error& e) + { + std::cerr << "ERROR: " << e.what() << std::endl << std::endl; + usage(); + return 1; + } + + po::notify(options); + + if(options.count("help") || !options.count("application")) + { + usage(); + return 0; + } + + ResourceFile app(options["application"].as()); + if(!app.read()) + { + std::cerr << "Could not read application file.\n"; + return 1; + } + + + + if(options.count("executor")) + { + fs::path tempDir = fs::unique_path(); + std::cerr << "Unique path: " << tempDir.string() << std::endl; + fs::create_directories(tempDir); + + fs::path appPath = tempDir / "Application"; + + app.assign(appPath.string(), ResourceFile::Format::percent_appledouble); + if(!app.write()) + { + std::cerr << "Could not write application file.\n"; + return 1; + } + + if(options.count("logfile")) + { + fs::ofstream out(tempDir/options["logfile"].as()); + } + + int result = ChildProcess(options["executor-path"].as(), { appPath.string() }); + + if(options.count("logfile")) + { + fs::ifstream in(tempDir/options["logfile"].as()); + std::cout << in.rdbuf(); + } + + + if(result == 0 && options.count("result")) + { + app.read(); + auto& resmap = app.resources.resources; + auto p = resmap.find(ResRef("TEST", 128)); + if(p == resmap.end()) + return 1; + + std::cout << p->second.getData(); + } + + fs::remove_all(tempDir); + + return result; + } + + return 0; +}