#include "nix/util/closure.hh"
#include <gtest/gtest.h>

namespace nix {

using namespace std;

map<string, set<string>> testGraph = {
    {"A", {"B", "C", "G"}},
    {"B", {"A"}}, // Loops back to A
    {"C", {"F"}}, // Indirect reference
    {"D", {"A"}}, // Not reachable, but has backreferences
    {"E", {}},    // Just not reachable
    {"F", {}},
    {"G", {"G"}}, // Self reference
};

TEST(closure, correctClosure)
{
    set<string> aClosure;
    set<string> expectedClosure = {"A", "B", "C", "F", "G"};
    computeClosure<string>(
        {"A"}, aClosure, [&](const string currentNode, function<void(promise<set<string>> &)> processEdges) {
            promise<set<string>> promisedNodes;
            promisedNodes.set_value(testGraph[currentNode]);
            processEdges(promisedNodes);
        });

    ASSERT_EQ(aClosure, expectedClosure);
}

TEST(closure, properlyHandlesDirectExceptions)
{
    struct TestExn
    {};

    set<string> aClosure;
    EXPECT_THROW(
        computeClosure<string>(
            {"A"},
            aClosure,
            [&](const string currentNode, function<void(promise<set<string>> &)> processEdges) { throw TestExn(); }),
        TestExn);
}

TEST(closure, properlyHandlesExceptionsInPromise)
{
    struct TestExn
    {};

    set<string> aClosure;
    EXPECT_THROW(
        computeClosure<string>(
            {"A"},
            aClosure,
            [&](const string currentNode, function<void(promise<set<string>> &)> processEdges) {
                promise<set<string>> promise;
                try {
                    throw TestExn();
                } catch (...) {
                    promise.set_exception(std::current_exception());
                }
                processEdges(promise);
            }),
        TestExn);
}

} // namespace nix
