Uncouple some of the logic from Nix library and add basic tests

dblsaiko
Mar 25, 2024, 12:57 AM
DYNXZQZVGIOW26Y6VPP4TIBOJEMK6T6Y7HU5ZJYH4GIWFFRIJSIQC

Dependencies

  • [2] FYYWB54C Fix conflicts
  • [3] H5RKFV7Y Don't directly execute Pijul from fetcher
  • [4] XGCRPWKL Move repo interaction into separate source file
  • [5] U5AKEHEQ Fix build for Nix 2.19 to 2.21
  • [6] 3KEFKH5F Import existing code
  • [*] SGQQ6LNZ Attach Boost and SYSTEM def to Nix target instead of plugin
  • [*] XXCRSVXO
  • [*] 7YS2X7JJ Fix compilation against older Nix versions (tested with 2.13.6)

Change contents

  • edit in src/repo.h at line 3
    [4.96]
    [4.96]
    #include "compat.h"
  • edit in src/repo.h at line 7
    [4.117]
    [4.117]
    #include <set>
  • edit in src/repo.h at line 10
    [4.141][4.141:161]()
    #include <types.hh>
  • replacement in src/repo.h at line 20
    [3.1][3.1:241]()
    std::string clone(const std::string_view &repoUrl,
    const nix::PathView &repoDir,
    const std::optional<std::string_view> &channel = {},
    const std::optional<std::string_view> &state = {});
    [3.1]
    [4.288]
    void clone(const std::string_view &repoUrl,
    const Path &repoDir,
    const std::optional<std::string_view> &channel = {},
    const std::optional<std::string_view> &state = {});
  • replacement in src/repo.h at line 25
    [4.289][3.242:371]()
    std::string record(const std::string_view &message, const nix::PathView &repoDir, const std::vector<nix::PathView> &paths = {});
    [4.289]
    [3.371]
    void record(const std::string_view &message, const Path &repoDir, const std::vector<Path> &paths = {});
  • replacement in src/repo.h at line 27
    [3.372][3.372:420]()
    bool isRepoDirty(const nix::PathView &repoDir);
    [3.372]
    [3.420]
    bool isRepoDirty(const Path &repoDir);
  • replacement in src/repo.h at line 29
    [3.421][3.421:490]()
    std::set<std::string> getTrackedFiles(const nix::PathView &repoDir);
    [3.421]
    [4.434]
    std::set<Path> getTrackedFiles(const Path &repoDir);
  • replacement in src/repo.h at line 31
    [4.435][4.435:492]()
    RepoStatus getRepoStatus(const nix::PathView &repoPath);
    [4.435]
    [4.492]
    RepoStatus getRepoStatus(const Path &repoPath);
  • replacement in src/repo.h at line 33
    [4.493][4.493:567]()
    std::pair<std::string, uint64_t> getState(const nix::PathView &repoPath);
    [4.493]
    [4.567]
    std::pair<std::string, uint64_t> getState(const Path &repoPath);
  • replacement in src/repo.h at line 35
    [4.568][4.568:627]()
    std::string getRepoChannel(const nix::PathView &repoPath);
    [4.568]
    [4.627]
    std::string getRepoChannel(const Path &repoPath);
  • edit in src/repo.cpp at line 2
    [4.800][4.800:840]()
    #include <types.hh>
    #include <util.hh>
  • edit in src/repo.cpp at line 3
    [4.841][4.841:901]()
    #if NIX_VERSION >= 0x021900
    #include <processes.hh>
    #endif
  • edit in src/repo.cpp at line 8
    [4.954][4.954:976]()
    using namespace nix;
  • replacement in src/repo.cpp at line 14
    [4.1086][3.511:626]()
    std::string runPijul(Strings args, PathView chdir, std::optional<std::string> input = {}, bool isInteractive = {})
    [4.1086]
    [4.1202]
    void runPijul(const StringList &args, const std::optional<Path> &chdir, OutputMode mode = OutputMode::Standard)
  • replacement in src/repo.cpp at line 16
    [4.1204][4.1204:1375](),[4.1375][3.627:657](),[3.657][4.1410:1649](),[4.1410][4.1410:1649]()
    auto program = "pijul"sv;
    auto res = runProgram(RunOptions{
    .program = std::string(program),
    .searchPath = true,
    .args = std::move(args),
    .chdir = Path(chdir),
    .input = std::move(input),
    .isInteractive = isInteractive,
    });
    if (!statusOk(res.first)) {
    throw ExecError(res.first, "program '%1%' %2%", program, statusToString(res.first));
    }
    return res.second;
    [4.1204]
    [3.658]
    runProcess("pijul", args, chdir ? chdir->c_str() : nullptr, mode);
  • replacement in src/repo.cpp at line 19
    [3.661][3.661:780]()
    std::string record(const std::string_view &message, const PathView &chdir, const std::vector<std::string_view> &paths)
    [3.661]
    [3.780]
    void record(const std::string_view &message, const Path &chdir, const std::vector<Path> &paths)
  • replacement in src/repo.cpp at line 21
    [3.782][3.782:823]()
    Strings args{"record", "--message"};
    [3.782]
    [3.823]
    StringList args{"record", "--message"};
  • replacement in src/repo.cpp at line 30
    [3.951][3.951:995]()
    return runPijul(args, chdir, {}, true);
    [3.951]
    [3.995]
    runPijul(args, chdir, OutputMode::Interactive);
  • replacement in src/repo.cpp at line 33
    [3.998][3.998:1230]()
    std::string clone(const std::string_view &repoUrl,
    const std::string_view &repoDir,
    const std::optional<std::string_view> &channel,
    const std::optional<std::string_view> &state)
    [3.998]
    [3.1230]
    void clone(const std::string_view &repoUrl,
    const Path &repoDir,
    const std::optional<std::string_view> &channel,
    const std::optional<std::string_view> &state)
  • replacement in src/repo.cpp at line 38
    [3.1232][3.1232:1260]()
    Strings args{"clone"s};
    [3.1232]
    [3.1260]
    StringList args{"clone"s};
  • replacement in src/repo.cpp at line 53
    [3.1522][3.1522:1563]()
    return runPijul(args, {}, {}, true);
    [3.1522]
    [4.1649]
    runPijul(args, {}, OutputMode::Interactive);
  • replacement in src/repo.cpp at line 56
    [4.1652][3.1564:1609]()
    bool isRepoDirty(const PathView &chdir = {})
    [4.1652]
    [3.1609]
    bool isRepoDirty(const Path &chdir = {})
  • replacement in src/repo.cpp at line 58
    [3.1611][3.1611:1684]()
    const std::string &diffOutput = runPijul({"diff", "--json"}, chdir);
    [3.1611]
    [3.1684]
    std::string diffOutput;
    runPijul({"diff", "--json"}, chdir, OutputMode::WithOutput{diffOutput});
  • replacement in src/repo.cpp at line 74
    [3.1897][3.1897:1965]()
    std::set<std::string> getTrackedFiles(const nix::PathView &repoDir)
    [3.1897]
    [3.1965]
    std::set<Path> getTrackedFiles(const Path &repoDir)
  • replacement in src/repo.cpp at line 76
    [3.1967][3.1967:2014]()
    auto output = runPijul({"list"}, repoDir);
    [3.1967]
    [3.2014]
    std::string output;
    runPijul({"list"}, repoDir, OutputMode::WithOutput{output});
    size_t pos = 0;
    size_t next;
    std::set<Path> out;
    while ((next = output.find_first_of("\n\r", pos)) != std::string::npos) {
    out.emplace(output.substr(pos, next - pos));
    pos = next + 1;
    }
  • replacement in src/repo.cpp at line 89
    [3.2015][3.2015:2081]()
    return tokenizeString<std::set<std::string>>(output, "\n\r");
    [3.2015]
    [3.2081]
    return out;
  • replacement in src/repo.cpp at line 92
    [3.2084][4.1652:1703](),[4.1652][4.1652:1703]()
    RepoStatus getRepoStatus(const PathView &repoPath)
    [3.2084]
    [4.1703]
    RepoStatus getRepoStatus(const Path &repoPath)
  • replacement in src/repo.cpp at line 104
    [4.1949][4.1949:2017]()
    std::pair<std::string, uint64_t> getState(const PathView &repoPath)
    [4.1949]
    [4.2017]
    std::pair<std::string, uint64_t> getState(const Path &repoPath)
  • replacement in src/repo.cpp at line 106
    [4.2019][4.2019:2133]()
    const auto &output = runPijul({"log", "--output-format", "json", "--state", "--limit", "1"}, Path(repoPath));
    [4.2019]
    [4.2133]
    std::string output;
    runPijul({"log", "--output-format", "json", "--state", "--limit", "1"}, Path(repoPath), OutputMode::WithOutput{output});
  • replacement in src/repo.cpp at line 120
    [4.2527][4.2527:2580]()
    std::string getRepoChannel(const PathView &repoPath)
    [4.2527]
    [4.2580]
    std::string getRepoChannel(const Path &repoPath)
  • replacement in src/repo.cpp at line 122
    [4.2582][4.2582:2646]()
    const auto &output = runPijul({"channel"}, Path(repoPath));
    [4.2582]
    [4.2646]
    std::string output;
    runPijul({"channel"}, Path(repoPath), OutputMode::WithOutput{output});
  • replacement in src/repo.cpp at line 142
    [4.3028][4.3028:3081]()
    throw Error("could not parse current channel"s);
    [4.3028]
    [4.3081]
    fatal("could not parse current channel");
  • edit in src/fetcher.cpp at line 1
    [4.53]
    [4.3464]
    #ifdef NIX_VERSION
  • edit in src/fetcher.cpp at line 11
    [4.169][4.0:60]()
    #if NIX_VERSION >= 0x021900
    #include <processes.hh>
    #endif
  • edit in src/fetcher.cpp at line 25
    [4.3553]
    [3.2085]
    using nixpluginpijul::getTrackedFiles;
  • edit in src/fetcher.cpp at line 28
    [3.2150][3.2150:2189]()
    using nixpluginpijul::getTrackedFiles;
  • replacement in src/fetcher.cpp at line 165
    [4.965][3.2190:2239]()
    record(*commitMsg, *root, {path.rel()});
    [4.965]
    [4.1146]
    record(*commitMsg, *root, {Path(path.rel())});
  • replacement in src/fetcher.cpp at line 173
    [4.4135][3.2240:2283]()
    record(*commitMsg, *root, {file});
    [4.4135]
    [4.1370]
    record(*commitMsg, *root, {std::string(file)});
  • replacement in src/fetcher.cpp at line 321
    [4.8102][4.8102:8228]()
    static std::pair<StorePath, Input> fetchLocal(const ref<Store> &store, const Input &_input, const std::string_view &path)
    [4.8102]
    [4.8228]
    static std::pair<StorePath, Input> fetchLocal(const ref<Store> &store, const Input &_input, const Path &path)
  • edit in src/fetcher.cpp at line 413
    [4.12979]
    #endif
  • file addition: compat.h (----------)
    [4.15]
    #ifndef NIX_PLUGIN_PIJUL_COMPAT_H
    #define NIX_PLUGIN_PIJUL_COMPAT_H
    #ifdef NIX_VERSION
    #include <error.hh>
    #include <types.hh>
    #else
    #include <filesystem>
    #include <string>
    #include <vector>
    #endif
    #include <variant>
    namespace nixpluginpijul
    {
    #ifdef NIX_VERSION
    using StringList = nix::Strings;
    using Path = nix::Path;
    #else
    using StringList = std::vector<std::string>;
    using Path = std::filesystem::path;
    #endif
    struct OutputMode final {
    enum Units {
    Standard,
    Interactive,
    };
    struct WithOutput {
    std::string &output;
    };
    OutputMode(Units data)
    : m_data(data)
    {
    }
    OutputMode(WithOutput data)
    : m_data(data)
    {
    }
    bool interactive()
    {
    if (auto *t = get_if<Units>(&m_data)) {
    return *t == Interactive;
    } else {
    return false;
    }
    }
    std::string *output()
    {
    if (auto *t = get_if<WithOutput>(&m_data)) {
    return &t->output;
    } else {
    return nullptr;
    }
    }
    private:
    std::variant<Units, WithOutput> m_data;
    };
    void runProcess(const char *path, const StringList &args, const char *chdir = nullptr, OutputMode mode = OutputMode::Standard);
    [[noreturn]] void fatal(const char *text);
    }
    #endif // NIX_PLUGIN_PIJUL_COMPAT_H
  • file addition: compat.cpp (----------)
    [4.15]
    #include "compat.h"
    #ifdef NIX_VERSION
    #if NIX_VERSION >= 0x021900
    #include <processes.hh>
    #else
    #include <util.hh>
    #endif
    #else
    #include <cstdio>
    #include <fcntl.h>
    #include <format>
    #include <spawn.h>
    #include <sys/wait.h>
    #include <sysexits.h>
    #endif
    using namespace std::string_literals;
    using namespace std::string_view_literals;
    namespace nixpluginpijul
    {
    void runProcess(const char *p, const StringList &args, const char *chdir, OutputMode mode)
    {
    #ifdef NIX_VERSION
    auto res = nix::runProgram(nix::RunOptions{
    .program = std::string(p),
    .searchPath = true,
    .args = args,
    .chdir = Path(chdir),
    .isInteractive = mode.interactive(),
    });
    if (!nix::statusOk(res.first)) {
    throw nix::ExecError(res.first, "program '%1%' %2%", p, nix::statusToString(res.first));
    }
    if (mode.output()) {
    *mode.output() = std::move(res.second);
    }
    #else
    int fds[2];
    posix_spawn_file_actions_t fa;
    posix_spawn_file_actions_init(&fa);
    if (mode.output()) {
    if (pipe(fds) != 0) {
    perror("failed to create pipe");
    exit(EX_OSERR);
    }
    posix_spawn_file_actions_adddup2(&fa, fds[1], 0);
    posix_spawn_file_actions_adddup2(&fa, fds[1], 1);
    }
    if (!mode.interactive()) {
    posix_spawn_file_actions_addclose(&fa, 2);
    }
    if (chdir) {
    posix_spawn_file_actions_addchdir_np(&fa, chdir);
    }
    std::vector<const char *> argv;
    argv.push_back(p);
    for (const auto &el : args) {
    argv.push_back(el.c_str());
    }
    pid_t pid;
    if (posix_spawnp(&pid, p, &fa, nullptr, const_cast<char *const *const>(argv.data()), nullptr) != 0) {
    perror("failed to spawn process");
    exit(EX_OSERR);
    }
    close(fds[1]);
    posix_spawn_file_actions_destroy(&fa);
    fd_set set;
    FD_ZERO(&set);
    int nfds = 0;
    if (mode.output()) {
    FD_SET(fds[0], &set);
    fcntl(fds[0], F_SETFL, fcntl(fds[0], F_GETFL) | O_NONBLOCK);
    nfds = 1;
    }
    sigset_t sigs;
    sigfillset(&sigs);
    sigdelset(&sigs, SIGCHLD);
    timespec timeout{.tv_sec = 1};
    bool running = true;
    int status;
    while (running) {
    // FIXME: this never catches the SIGCHLD
    int ret = pselect(nfds, &set, nullptr, nullptr, &timeout, &sigs);
    printf("%d\n", ret);
    if (ret == -1) {
    if (errno != EINTR) {
    perror("pselect");
    exit(EX_OSERR);
    }
    }
    ret = waitpid(pid, &status, WNOHANG);
    if (ret == -1) {
    perror("waitpid");
    exit(EX_OSERR);
    } else if (ret == pid) {
    running = false;
    ret = 1; // get the last data out of the pipe
    }
    if (ret > 0 && mode.output()) {
    char buf[4096];
    ssize_t len;
    do {
    len = read(fds[0], buf, sizeof(buf));
    if (len < 0 && errno != EWOULDBLOCK) {
    perror("read");
    exit(EX_OSERR);
    }
    if (len > 1) {
    mode.output()->append(buf, len);
    }
    } while (len > 0);
    }
    }
    if (mode.output()) {
    close(fds[0]);
    }
    if (status != 0) {
    fatal(std::format("process `{}' exited with status {}", p, WEXITSTATUS(status)).c_str());
    }
    #endif
    }
    void fatal(const char *text)
    {
    #ifdef NIX_VERSION
    throw nix::Error("%1%", text);
    #else
    fprintf(stderr, "fatal: %s\n", text);
    exit(EX_SOFTWARE);
    #endif
    }
    }
  • edit in src/CMakeLists.txt at line 2
    [4.13046]
    [4.13046]
    add_library(PijulTesting STATIC)
  • replacement in src/CMakeLists.txt at line 4
    [4.13047][4.13047:13076]()
    target_sources(pijul PRIVATE
    [4.13047]
    [4.3589]
    set(SRC
    compat.cpp
    compat.h
  • edit in src/CMakeLists.txt at line 11
    [4.13098]
    [8.213]
    target_sources(pijul PRIVATE ${SRC})
    target_sources(PijulTesting PRIVATE ${SRC})
    target_include_directories(PijulTesting PUBLIC ${CMAKE_CURRENT_LIST_DIR})
    target_compile_options(PijulTesting PUBLIC -O1 -ggdb)
  • file addition: autotests (d--r------)
    [9.1]
  • file addition: repotest.cpp (----------)
    [0.7869]
    #include "repo.h"
    using namespace nixpluginpijul;
    int main()
    {
    clone("https://nest.pijul.com/dblsaiko/nix-plugin-pijul", "nix-plugin-pijul");
    const std::set<Path> &set = getTrackedFiles("nix-plugin-pijul");
    for (const auto &item : set) {
    printf("- %s\n", item.c_str());
    }
    }
  • file addition: datetest.cpp (----------)
    [0.7869]
    #include "repo.h"
    #include <format>
    using namespace nixpluginpijul;
    template<typename T>
    void assert_eq(const T &left, const T &right)
    {
    if (left != right) {
    const std::string &string = std::format("assertion failed: {} == {}", left, right);
    fprintf(stderr, "%s\n", string.c_str());
    exit(1);
    }
    }
    template<typename F>
    void assert_throws(F op)
    {
    try {
    op();
    } catch (...) {
    return;
    }
    fprintf(stderr, "%s\n", "assertion failed: `op' didn't throw");
    exit(1);
    }
    int main()
    {
    auto point = parseRFC3339("1970-01-01T00:00:00.000Z");
    assert_eq(0L, point.time_since_epoch().count());
    point = parseRFC3339("1970-01-01T00:00:00Z");
    assert_eq(0L, point.time_since_epoch().count());
    point = parseRFC3339("1970-01-01T00:30:00+00:30");
    assert_eq(0L, point.time_since_epoch().count());
    point = parseRFC3339("1970-01-01T00:00:00.000-01:00");
    assert_eq(3600000L, point.time_since_epoch().count());
    assert_throws([] {
    parseRFC3339("garbage");
    });
    assert_throws([] {
    parseRFC3339("1");
    });
    assert_throws([] {
    parseRFC3339("1970-01-01T00:00:00");
    });
    // FIXME: this should be an invalid timestamp (at least, I think so) because
    // it has both +00:30 and 'Z' but it parses
    assert_throws([] {
    parseRFC3339("1970-01-01T00:30:00+00:30Z");
    });
    }
  • file addition: CMakeLists.txt (----------)
    [0.7869]
    add_compile_options(-O1 -ggdb)
    add_executable(DateTest datetest.cpp)
    target_link_libraries(DateTest PUBLIC PijulTesting)
    add_test(NAME DateTest COMMAND DateTest)
    add_executable(RepoTest repotest.cpp)
    target_link_libraries(RepoTest PUBLIC PijulTesting)
    add_test(NAME RepoTest COMMAND RepoTest)
  • edit in CMakeLists.txt at line 15
    [4.14161]
    [4.14161]
    include(CTest)
  • edit in CMakeLists.txt at line 41
    [2.66]
    [10.677]
    add_compile_options(-Wall -Werror)
  • edit in CMakeLists.txt at line 44
    [10.678]
    [4.14335]
    add_subdirectory(autotests)