diff --git a/CMakeLists.txt b/CMakeLists.txt index 21b997134b..83875686bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -319,6 +319,15 @@ if (TEST_SUITE_USER_MODE_EMULATION) set(FPCMP fpcmp) endif() +# Shortcut for the path to the not executable +set(NOT_TOOL "$") +if (TEST_SUITE_USER_MODE_EMULATION AND TEST_SUITE_RUN_UNDER) + # Because 'not' spawns a subprocess for its argument, it needs to know about + # the emulator when running in user-mode emulation. This is not needed when + # using 'not' to verify outputs, only when using it to execute a test binary. + set(NOT_TOOL "$" "--run-under" ${TEST_SUITE_RUN_UNDER} "--") +endif() + add_subdirectory(litsupport) option(TEST_SUITE_COLLECT_COMPILE_TIME diff --git a/Fortran/gfortran/CMakeLists.txt b/Fortran/gfortran/CMakeLists.txt index b2896db621..3b770631c7 100644 --- a/Fortran/gfortran/CMakeLists.txt +++ b/Fortran/gfortran/CMakeLists.txt @@ -719,7 +719,7 @@ function(gfortran_add_execute_test expect_error main others fflags ldflags) llvm_test_executable_no_test(${target} ${main} ${others}) if (expect_error) llvm_test_run( - EXECUTABLE "%b/not --crash %S/${target}" + EXECUTABLE "%b/${NOT_TOOL} --crash %S/${target}" WORKDIR "%S/${working_dir_name}") else () llvm_test_run(WORKDIR "%S/${working_dir_name}") diff --git a/tools/not.cpp b/tools/not.cpp index 31721f9473..e6d788ad23 100644 --- a/tools/not.cpp +++ b/tools/not.cpp @@ -10,6 +10,14 @@ // Will return true if cmd doesn't crash and returns false. // not --crash cmd // Will return true if cmd crashes (e.g. for testing crash reporting). +// not --run-under ...... -- cmd +// Will prepend the emulator and its arguments to the spawn of the +// subcommand. If --crash is used as well, it has to come after the +// '--run-under <...> ...' arguments. The double-dash is used to separate +// emulator arguments from cmd arguments. This order makes it easier to +// use the not tool in the test-suite. The emulator is expected to exit +// with the same exit status/signal than the emulated binary in case of +// a crash, and the --crash flag will behave the same way. // This file is a stripped down version of not.cpp from llvm/utils. This does // not depend on any LLVM library. @@ -17,6 +25,7 @@ #include #include #include +#include #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN @@ -35,11 +44,43 @@ int main(int argc, char* const* argv) { ++argv; --argc; - if (argc > 0 && std::string(argv[0]) == "--crash") { + // If necessary, prepend the command and arguments for a user-mode + // emulator such as QEMU to the command line that will be used + // to then spawn a subcommand. + std::vector argvbuf; + if (argc > 0 && std::string(argv[0]) == "--run-under") { + ++argv; + --argc; + while (argc > 0) { + --argc; + if (std::string(argv[0]) == "--") { + ++argv; + break; + } + + argvbuf.push_back(argv[0]); + ++argv; + } + + // If present, the crash flag is between the emulator arguments and cmd. + if (argc > 0 && std::string(argv[0]) == "--crash") { + ++argv; + --argc; + expectCrash = true; + } + + for (char *const *argp = argv; *argp != NULL; ++argp) + argvbuf.push_back(*argp); + argvbuf.push_back(NULL); + argv = argvbuf.data(); + argc = argvbuf.size() - 1; + } else if (argc > 0 && std::string(argv[0]) == "--crash") { ++argv; --argc; expectCrash = true; + } + if (expectCrash) { // Crash is expected, so disable crash report and symbolization to reduce // output and avoid potentially slow symbolization. #ifdef _WIN32 diff --git a/tools/test/CMakeLists.txt b/tools/test/CMakeLists.txt index a724da14fe..b755609117 100644 --- a/tools/test/CMakeLists.txt +++ b/tools/test/CMakeLists.txt @@ -1,8 +1,3 @@ -# Copy these files to the build directory so that the tests can be run even -# without the source directory. -configure_file(test_not.py test_not.py - COPYONLY) - llvm_test_executable_no_test(ret1 ret1.c) add_dependencies(ret1 not) llvm_test_run(EXECUTABLE "$" "$") @@ -13,15 +8,23 @@ add_dependencies(ret0 not) llvm_test_run(EXECUTABLE "$" "$" "$") llvm_add_test_for_target(ret0) -# Check that expected crashes are handled correctly. +# Check that expected crashes are handled correctly under user-mode emulation. llvm_test_executable_no_test(abrt abort.c) add_dependencies(abrt not) -llvm_test_run(EXECUTABLE "$" "--crash" "$") +llvm_test_run(EXECUTABLE ${NOT_TOOL} "--crash" "$") llvm_add_test_for_target(abrt) # Check that not passes environment variables to the called executable. -find_package(Python COMPONENTS Interpreter) +llvm_test_executable_no_test(test_not test_not.cpp) llvm_test_executable_no_test(check_env check_env.c) -add_dependencies(check_env not) -llvm_test_run(EXECUTABLE ${Python_EXECUTABLE} "%b/test/test_not.py" "$" "$") +add_dependencies(check_env not test_not) +if(TEST_SUITE_USER_MODE_EMULATION AND TEST_SUITE_RUN_UNDER) + # ${TEST_SUITE_RUN_UNDER} is needed here because test_not is not itself + # user-mode emulation aware, unlike the not tool. + llvm_test_run(EXECUTABLE "$" ${TEST_SUITE_RUN_UNDER} + ${NOT_TOOL} "$") +else() + llvm_test_run(EXECUTABLE "$" ${NOT_TOOL} + "$") +endif() llvm_add_test_For_target(check_env) diff --git a/tools/test/test_not.cpp b/tools/test/test_not.cpp new file mode 100644 index 0000000000..e6fd817bc5 --- /dev/null +++ b/tools/test/test_not.cpp @@ -0,0 +1,88 @@ +//===- test_not.cpp - Test for enviroment variables in the not tool -------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include +#include +#include + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#endif + +#if defined(__unix__) || defined(__APPLE__) +#include +#include +#endif + +int main(int argc, char *const *argv) { + ++argv; + --argc; + if (argc == 0) + return EXIT_FAILURE; + +#ifdef _WIN32 + SetEnvironmentVariableA("SET_IN_PARENT", "something"); +#else + setenv("SET_IN_PARENT", "something", 0); +#endif + + int result; + +#if defined(__unix__) || defined(__APPLE__) + pid_t pid; + extern char **environ; + if (posix_spawn(&pid, argv[0], NULL, NULL, argv, environ)) + return EXIT_FAILURE; + if (waitpid(pid, &result, WUNTRACED | WCONTINUED) == -1) + return EXIT_FAILURE; +#else + std::stringstream ss; + ss << argv[0]; + for (int i = 1; i < argc; ++i) + ss << " " << argv[i]; + std::string cmd = ss.str(); + result = std::system(cmd.c_str()); +#endif + + int retcode = 0; + int signal = 0; + +#ifdef _WIN32 + // Handle abort() in msvcrt -- It has exit code as 3. abort(), aka + // unreachable, should be recognized as a crash. However, some binaries use + // exit code 3 on non-crash failure paths, so only do this if we expect a + // crash. + if (errno) { + // If the command interpreter was not found, errno will be set and 0 will + // be returned. It is unlikely that this will happen in our use case, but + // check anyway. + retcode = 1; + signal = 1; + } else { + // On Windows, result is the exit code, except for the special case above. + retcode = result; + signal = 0; + } +#elif defined(WIFEXITED) && defined(WEXITSTATUS) && defined(WIFSIGNALED) && \ + defined(WTERMSIG) + // On POSIX systems and Solaris, result is a composite value of the exit code + // and, potentially, the signal that caused termination of the command. + if (WIFEXITED(result)) + retcode = WEXITSTATUS(result); + if (WIFSIGNALED(result)) + signal = WTERMSIG(result); +#else +#error "Unsupported system" +#endif + + if (!signal && retcode == EXIT_SUCCESS) + return EXIT_SUCCESS; + return EXIT_FAILURE; +} diff --git a/tools/test/test_not.py b/tools/test/test_not.py deleted file mode 100644 index 8eafa53683..0000000000 --- a/tools/test/test_not.py +++ /dev/null @@ -1,7 +0,0 @@ -import os -import subprocess -import sys - -os.environ["SET_IN_PARENT"] = "something" -out = subprocess.run([sys.argv[1], sys.argv[2]]) -sys.exit(out.returncode)