Edit

kc3-lang/angle/util/posix/test_utils_posix.cpp

Branch :

  • Show log

    Commit

  • Author : Shahbaz Youssefi
    Date : 2021-06-21 09:59:21
    Hash : 72d2bd0c
    Message : Allow capturing process stdout and stderr interleaved The test utils are enhanced to allow redirecting stderr to stdout. This is in preparation for a change that makes the test runner capture stderr together with stdout. Currently, on failure logs originating from UNIMPLEMENTED() and other such macros are not captured. Bug: angleproject:6077 Change-Id: I7a3c6c4732a59dac3ff0cc20a7835d5ed6f0f22e Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2976183 Reviewed-by: Jamie Madill <jmadill@chromium.org> Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>

  • util/posix/test_utils_posix.cpp
  • //
    // Copyright 2015 The ANGLE Project Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style license that can be
    // found in the LICENSE file.
    //
    
    // test_utils_posix.cpp: Implementation of OS-specific functions for Posix systems
    
    #include "util/test_utils.h"
    
    #include <dlfcn.h>
    #include <errno.h>
    #include <fcntl.h>
    #include <sched.h>
    #include <signal.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <time.h>
    #include <unistd.h>
    #include <cstdarg>
    #include <cstring>
    #include <iostream>
    
    #include "common/debug.h"
    #include "common/platform.h"
    #include "common/system_utils.h"
    
    #if !defined(ANGLE_PLATFORM_FUCHSIA)
    #    include <sys/resource.h>
    #endif
    
    #if defined(ANGLE_PLATFORM_MACOS)
    #    include <crt_externs.h>
    #endif
    
    namespace angle
    {
    namespace
    {
    
    #if defined(ANGLE_PLATFORM_MACOS)
    // Argument to skip the file hooking step. Might be automatically added by InitMetalFileAPIHooking()
    constexpr char kSkipFileHookingArg[] = "--skip-file-hooking";
    #endif
    
    struct ScopedPipe
    {
        ~ScopedPipe()
        {
            closeEndPoint(0);
            closeEndPoint(1);
        }
    
        void closeEndPoint(int index)
        {
            if (fds[index] >= 0)
            {
                close(fds[index]);
                fds[index] = -1;
            }
        }
    
        bool valid() const { return fds[0] != -1 || fds[1] != -1; }
    
        int fds[2] = {
            -1,
            -1,
        };
    };
    
    enum class ReadResult
    {
        NoData,
        GotData,
    };
    
    ReadResult ReadFromFile(int fd, std::string *out)
    {
        constexpr size_t kBufSize = 2048;
        char buffer[kBufSize];
        ssize_t bytesRead = read(fd, buffer, sizeof(buffer));
    
        if (bytesRead < 0 && errno == EINTR)
        {
            return ReadResult::GotData;
        }
    
        if (bytesRead <= 0)
        {
            return ReadResult::NoData;
        }
    
        out->append(buffer, bytesRead);
        return ReadResult::GotData;
    }
    
    void ReadEntireFile(int fd, std::string *out)
    {
        while (ReadFromFile(fd, out) == ReadResult::GotData)
        {
        }
    }
    
    class PosixProcess : public Process
    {
      public:
        PosixProcess(const std::vector<const char *> &commandLineArgs,
                     ProcessOutputCapture captureOutput)
        {
            if (commandLineArgs.empty())
            {
                return;
            }
    
            const bool captureStdout = captureOutput != ProcessOutputCapture::Nothing;
            const bool captureStderr =
                captureOutput == ProcessOutputCapture::StdoutAndStderrInterleaved ||
                captureOutput == ProcessOutputCapture::StdoutAndStderrSeparately;
            const bool pipeStderrToStdout =
                captureOutput == ProcessOutputCapture::StdoutAndStderrInterleaved;
    
            // Create pipes for stdout and stderr.
            if (captureStdout)
            {
                if (pipe(mStdoutPipe.fds) != 0)
                {
                    std::cerr << "Error calling pipe: " << errno << "\n";
                    return;
                }
                if (fcntl(mStdoutPipe.fds[0], F_SETFL, O_NONBLOCK) == -1)
                {
                    std::cerr << "Error calling fcntl: " << errno << "\n";
                    return;
                }
            }
            if (captureStderr && !pipeStderrToStdout)
            {
                if (pipe(mStderrPipe.fds) != 0)
                {
                    std::cerr << "Error calling pipe: " << errno << "\n";
                    return;
                }
                if (fcntl(mStderrPipe.fds[0], F_SETFL, O_NONBLOCK) == -1)
                {
                    std::cerr << "Error calling fcntl: " << errno << "\n";
                    return;
                }
            }
    
            mPID = fork();
            if (mPID < 0)
            {
                return;
            }
    
            mStarted = true;
            mTimer.start();
    
            if (mPID == 0)
            {
                // Child.  Execute the application.
    
                // Redirect stdout and stderr to the pipe fds.
                if (captureStdout)
                {
                    if (dup2(mStdoutPipe.fds[1], STDOUT_FILENO) < 0)
                    {
                        _exit(errno);
                    }
                    mStdoutPipe.closeEndPoint(1);
                }
                if (pipeStderrToStdout)
                {
                    if (dup2(STDOUT_FILENO, STDERR_FILENO) < 0)
                    {
                        _exit(errno);
                    }
                }
                else if (captureStderr)
                {
                    if (dup2(mStderrPipe.fds[1], STDERR_FILENO) < 0)
                    {
                        _exit(errno);
                    }
                    mStderrPipe.closeEndPoint(1);
                }
    
                // Execute the application, which doesn't return unless failed.  Note: execv takes argv
                // as `char * const *` for historical reasons.  It is safe to const_cast it:
                //
                // http://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html
                //
                // > The statement about argv[] and envp[] being constants is included to make explicit
                // to future writers of language bindings that these objects are completely constant.
                // Due to a limitation of the ISO C standard, it is not possible to state that idea in
                // standard C. Specifying two levels of const- qualification for the argv[] and envp[]
                // parameters for the exec functions may seem to be the natural choice, given that these
                // functions do not modify either the array of pointers or the characters to which the
                // function points, but this would disallow existing correct code. Instead, only the
                // array of pointers is noted as constant.
                std::vector<char *> args;
                for (const char *arg : commandLineArgs)
                {
                    args.push_back(const_cast<char *>(arg));
                }
                args.push_back(nullptr);
    
                execv(commandLineArgs[0], args.data());
                std::cerr << "Error calling evecv: " << errno;
                _exit(errno);
            }
            // Parent continues execution.
            mStdoutPipe.closeEndPoint(1);
            mStderrPipe.closeEndPoint(1);
        }
    
        ~PosixProcess() override {}
    
        bool started() override { return mStarted; }
    
        bool finish() override
        {
            if (!mStarted)
            {
                return false;
            }
    
            if (mFinished)
            {
                return true;
            }
    
            while (!finished())
            {
                angle::Sleep(1);
            }
    
            return true;
        }
    
        bool finished() override
        {
            if (!mStarted)
            {
                return false;
            }
    
            if (mFinished)
            {
                return true;
            }
    
            int status        = 0;
            pid_t returnedPID = ::waitpid(mPID, &status, WNOHANG);
    
            if (returnedPID == -1 && errno != ECHILD)
            {
                std::cerr << "Error calling waitpid: " << ::strerror(errno) << "\n";
                return true;
            }
    
            if (returnedPID == mPID)
            {
                mFinished = true;
                mTimer.stop();
                readPipes();
                mExitCode = WEXITSTATUS(status);
                return true;
            }
    
            if (mStdoutPipe.valid())
            {
                ReadEntireFile(mStdoutPipe.fds[0], &mStdout);
            }
    
            if (mStderrPipe.valid())
            {
                ReadEntireFile(mStderrPipe.fds[0], &mStderr);
            }
    
            return false;
        }
    
        int getExitCode() override { return mExitCode; }
    
        bool kill() override
        {
            if (!mStarted)
            {
                return false;
            }
    
            if (finished())
            {
                return true;
            }
    
            return (::kill(mPID, SIGTERM) == 0);
        }
    
      private:
        void readPipes()
        {
            // Close the write end of the pipes, so EOF can be generated when child exits.
            // Then read back the output of the child.
            if (mStdoutPipe.valid())
            {
                ReadEntireFile(mStdoutPipe.fds[0], &mStdout);
            }
            if (mStderrPipe.valid())
            {
                ReadEntireFile(mStderrPipe.fds[0], &mStderr);
            }
        }
    
        bool mStarted  = false;
        bool mFinished = false;
        ScopedPipe mStdoutPipe;
        ScopedPipe mStderrPipe;
        int mExitCode = 0;
        pid_t mPID    = -1;
    };
    
    std::string TempFileName()
    {
        return std::string(".angle.XXXXXX");
    }
    }  // anonymous namespace
    
    void Sleep(unsigned int milliseconds)
    {
        // On Windows Sleep(0) yields while it isn't guaranteed by Posix's sleep
        // so we replicate Windows' behavior with an explicit yield.
        if (milliseconds == 0)
        {
            sched_yield();
        }
        else
        {
            long milliseconds_long = milliseconds;
            timespec sleepTime     = {
                .tv_sec  = milliseconds_long / 1000,
                .tv_nsec = (milliseconds_long % 1000) * 1000000,
            };
    
            nanosleep(&sleepTime, nullptr);
        }
    }
    
    void SetLowPriorityProcess()
    {
    #if !defined(ANGLE_PLATFORM_FUCHSIA)
        setpriority(PRIO_PROCESS, getpid(), 10);
    #endif
    }
    
    void WriteDebugMessage(const char *format, ...)
    {
        va_list vararg;
        va_start(vararg, format);
        vfprintf(stderr, format, vararg);
        va_end(vararg);
    }
    
    bool StabilizeCPUForBenchmarking()
    {
    #if !defined(ANGLE_PLATFORM_FUCHSIA)
        bool success = true;
        errno        = 0;
        setpriority(PRIO_PROCESS, getpid(), -20);
        if (errno)
        {
            // A friendly warning in case the test was run without appropriate permission.
            perror(
                "Warning: setpriority failed in StabilizeCPUForBenchmarking. Process will retain "
                "default priority");
            success = false;
        }
    #    if ANGLE_PLATFORM_LINUX
        cpu_set_t affinity;
        CPU_SET(0, &affinity);
        errno = 0;
        if (sched_setaffinity(getpid(), sizeof(affinity), &affinity))
        {
            perror(
                "Warning: sched_setaffinity failed in StabilizeCPUForBenchmarking. Process will retain "
                "default affinity");
            success = false;
        }
    #    else
        // TODO(jmadill): Implement for non-linux. http://anglebug.com/2923
    #    endif
    
        return success;
    #else  // defined(ANGLE_PLATFORM_FUCHSIA)
        return false;
    #endif
    }
    
    bool GetTempDir(char *tempDirOut, uint32_t maxDirNameLen)
    {
        const char *tmp = getenv("TMPDIR");
        if (tmp)
        {
            strncpy(tempDirOut, tmp, maxDirNameLen);
            return true;
        }
    
    #if defined(ANGLE_PLATFORM_ANDROID)
        // Not used right now in the ANGLE test runner.
        // return PathService::Get(DIR_CACHE, path);
        return false;
    #else
        strncpy(tempDirOut, "/tmp", maxDirNameLen);
        return true;
    #endif
    }
    
    bool CreateTemporaryFileInDir(const char *dir, char *tempFileNameOut, uint32_t maxFileNameLen)
    {
        std::string tempFile = TempFileName();
        sprintf(tempFileNameOut, "%s/%s", dir, tempFile.c_str());
        int fd = mkstemp(tempFileNameOut);
        close(fd);
        return fd != -1;
    }
    
    bool DeleteFile(const char *path)
    {
        return unlink(path) == 0;
    }
    
    Process *LaunchProcess(const std::vector<const char *> &args, ProcessOutputCapture captureOutput)
    {
        return new PosixProcess(args, captureOutput);
    }
    
    int NumberOfProcessors()
    {
        // sysconf returns the number of "logical" (not "physical") processors on both
        // Mac and Linux.  So we get the number of max available "logical" processors.
        //
        // Note that the number of "currently online" processors may be fewer than the
        // returned value of NumberOfProcessors(). On some platforms, the kernel may
        // make some processors offline intermittently, to save power when system
        // loading is low.
        //
        // One common use case that needs to know the processor count is to create
        // optimal number of threads for optimization. It should make plan according
        // to the number of "max available" processors instead of "currently online"
        // ones. The kernel should be smart enough to make all processors online when
        // it has sufficient number of threads waiting to run.
        long res = sysconf(_SC_NPROCESSORS_CONF);
        if (res == -1)
        {
            return 1;
        }
    
        return static_cast<int>(res);
    }
    
    const char *GetNativeEGLLibraryNameWithExtension()
    {
    #if defined(ANGLE_PLATFORM_ANDROID)
        return "libEGL.so";
    #elif defined(ANGLE_PLATFORM_LINUX)
        return "libEGL.so.1";
    #else
        return "unknown_libegl";
    #endif
    }
    
    #if defined(ANGLE_PLATFORM_MACOS)
    void InitMetalFileAPIHooking(int argc, char **argv)
    {
        if (argc < 1)
        {
            return;
        }
    
        for (int i = 0; i < argc; ++i)
        {
            if (strncmp(argv[i], kSkipFileHookingArg, strlen(kSkipFileHookingArg)) == 0)
            {
                return;
            }
        }
    
        constexpr char kInjectLibVarName[]    = "DYLD_INSERT_LIBRARIES";
        constexpr size_t kInjectLibVarNameLen = sizeof(kInjectLibVarName) - 1;
    
        std::string exeDir = GetExecutableDirectory();
        if (!exeDir.empty() && exeDir.back() != '/')
        {
            exeDir += "/";
        }
    
        // Intercept Metal shader cache access and return as if the cache doesn't exist.
        // This is to avoid slow shader cache mechanism that caused the test timeout in the past.
        // In order to do that, we need to hook the file API functions by making sure
        // libmetal_shader_cache_file_hooking.dylib library is loaded first before any other libraries.
        std::string injectLibsVar =
            std::string(kInjectLibVarName) + "=" + exeDir + "libmetal_shader_cache_file_hooking.dylib";
    
        char skipHookOption[sizeof(kSkipFileHookingArg)];
        memcpy(skipHookOption, kSkipFileHookingArg, sizeof(kSkipFileHookingArg));
    
        // Construct environment variables
        std::vector<char *> newEnv;
        char **environ = *_NSGetEnviron();
        for (int i = 0; environ[i]; ++i)
        {
            if (strncmp(environ[i], kInjectLibVarName, kInjectLibVarNameLen) == 0)
            {
                injectLibsVar += ':';
                injectLibsVar += environ[i] + kInjectLibVarNameLen + 1;
            }
            else
            {
                newEnv.push_back(environ[i]);
            }
        }
        newEnv.push_back(strdup(injectLibsVar.data()));
        newEnv.push_back(nullptr);
    
        // Construct arguments with kSkipFileHookingArg flag to skip the hooking after re-launching.
        std::vector<char *> newArgs;
        newArgs.push_back(argv[0]);
        newArgs.push_back(skipHookOption);
        for (int i = 1; i < argc; ++i)
        {
            newArgs.push_back(argv[i]);
        }
        newArgs.push_back(nullptr);
    
        // Re-launch the app with file API hooked.
        ASSERT(-1 != execve(argv[0], newArgs.data(), newEnv.data()));
    }
    #endif
    
    }  // namespace angle