// ============================================================ //
//                                                              //
//   File      : test_runtool.h                                 //
//   Purpose   : provides tests for external tools (CLI)        //
//                                                              //
//   Coded by Ralf Westram (coder@reallysoft.de) in June 2017   //
//   http://www.arb-home.de/                                    //
//                                                              //
// ============================================================ //

#ifndef TEST_RUNTOOL_H
#define TEST_RUNTOOL_H

#ifndef UT_VALGRINDED_H
#include <ut_valgrinded.h>
#endif
#ifndef ARB_STRARRAY_H
#include <arb_strarray.h>
#endif
#ifndef ARB_STRBUF_H
#include <arb_strbuf.h>
#endif

namespace test_runtool {

    inline char *make_checked_piped_command(const char *piped_command, int pipeSymbolCount) {
        // append pipe-check to 'piped_command'
        // ('piped_command' has to contain 'pipeSymbolCount' pipe symbols)
        // - wrap command in call to bash (PIPESTATUS is bash-specific)

        arb_assert(pipeSymbolCount>0);

        GBS_strstruct cmd_list(500);
        cmd_list.cat(piped_command);
        // append bash-expression to check result of each part of the pipe
        cmd_list.cat(";exit $((");
        for (int i = 0; i<=pipeSymbolCount; ++i) {
            if (i) cmd_list.cat(" | ");
            cmd_list.nprintf(30, "${PIPESTATUS[%i]}", i);
        }
        cmd_list.cat("))");

        // explicitely use bash to execute:
        char *quotedCommand = GBK_singlequote(cmd_list.get_data());
        char *checked_cmd   = GBS_global_string_copy("bash -c %s", quotedCommand);
        free(quotedCommand);
        return checked_cmd;
    }

    inline void add_valgrinded_cmd(StrArray& array, char *cmd) {
        make_valgrinded_call(cmd);
        array.put(cmd);
    }

    inline char *valgrinded_pipe_command(const char *piped_command, const char *first_pipe) {
        if (will_valgrind_calls()) {
            StrArray pipe_cmds;

            while (first_pipe) {
                add_valgrinded_cmd(pipe_cmds, ARB_strpartdup(piped_command, first_pipe-1));
                piped_command = first_pipe+1;
                first_pipe    = find_pipe_symbol(piped_command);
            }
            add_valgrinded_cmd(pipe_cmds, strdup(piped_command)); // rest

            char *repiped_cmd         = GBT_join_strings(pipe_cmds, '|');
            char *checked_repiped_cmd = make_checked_piped_command(repiped_cmd, pipe_cmds.size()-1);
            free(repiped_cmd);

            return checked_repiped_cmd;
        }

        int pipeCount = 0;
        while (first_pipe) {
            ++pipeCount;
            first_pipe = find_pipe_symbol(first_pipe+1);
        }
        return make_checked_piped_command(piped_command, pipeCount);
    }

    inline GB_ERROR valgrinded_system(const char *cmdline) { // @@@ rename later (does more)
        // basically does two things:
        // - if current binary runs valgrinded => use valgrind to run 'cmdline' (can handle pipes)
        // - if 'cmdline' is a pipe => check exitcodes of all parts of the pipe

        char       *cmddup     = NULp;
        const char *first_pipe = find_pipe_symbol(cmdline);

        if (first_pipe) {
            cmddup = valgrinded_pipe_command(cmdline, first_pipe);
        }
        else {
            cmddup = ARB_strdup(cmdline);
            make_valgrinded_call(cmddup);
        }

        GB_ERROR error = GBK_system(cmddup);
        free(cmddup);
        return error;
    }

};

#define RUN_TOOL(cmdline)                test_runtool::valgrinded_system(cmdline)
#define RUN_TOOL_NEVER_VALGRIND(cmdline) GBK_system(cmdline)

// expect success or failure running CLI-tool:

#define TEST_RUN_TOOL(cmdline)                TEST_EXPECT_NO_ERROR(RUN_TOOL(cmdline))
#define TEST_RUN_TOOL_NEVER_VALGRIND(cmdline) TEST_EXPECT_NO_ERROR(RUN_TOOL_NEVER_VALGRIND(cmdline))
#define TEST_RUN_TOOL_FAILS(cmdline)          TEST_EXPECT_ERROR_CONTAINS(RUN_TOOL(cmdline), "System call failed")

// Note: more detailed tests are available using TEST_OUTPUT_CONTAINS, TEST_FAILURE_OUTPUT_CONTAINS and related macros

#else
#error test_runtool.h included twice
#endif // TEST_RUNTOOL_H
