1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
#include "lix/libutil/pool.hh"
#include "lix/libutil/result.hh"
#include <exception>
#include <gtest/gtest.h>
#include <kj/async.h>

namespace nix {

    struct TestResource
    {

        TestResource() {
            static int counter = 0;
            num = counter++;
        }

        int dummyValue = 1;
        bool good = true;
        int num;
    };

    class PoolTest : public testing::Test
    {
    public:
        kj::EventLoop loop;
        kj::WaitScope ws{loop};

        ~PoolTest() noexcept(true) = default;
    };

    /* ----------------------------------------------------------------------------
     * Pool
     * --------------------------------------------------------------------------*/

    TEST_F(PoolTest, freshPoolHasZeroCountAndSpecifiedCapacity) {
        auto isGood = [](const ref<TestResource> & r) { return r->good; };
        auto createResource = []() -> kj::Promise<Result<ref<TestResource>>> {
            return {result::success(make_ref<TestResource>())};
        };

        Pool<TestResource> pool = Pool<TestResource>((size_t)1, createResource, isGood);

        ASSERT_EQ(pool.count(), 0);
        ASSERT_EQ(pool.capacity(), 1);
    }

    TEST_F(PoolTest, freshPoolCanGetAResource) {
        auto isGood = [](const ref<TestResource> & r) { return r->good; };
        auto createResource = []() -> kj::Promise<Result<ref<TestResource>>> {
            return {result::success(make_ref<TestResource>())};
        };

        Pool<TestResource> pool = Pool<TestResource>((size_t)1, createResource, isGood);
        ASSERT_EQ(pool.count(), 0);

        TestResource r = *(pool.get().wait(ws).value());

        ASSERT_EQ(pool.count(), 1);
        ASSERT_EQ(pool.capacity(), 1);
        ASSERT_EQ(r.dummyValue, 1);
        ASSERT_EQ(r.good, true);
    }

    TEST_F(PoolTest, capacityCanBeIncremented) {
        auto isGood = [](const ref<TestResource> & r) { return r->good; };
        auto createResource = []() -> kj::Promise<Result<ref<TestResource>>> {
            return {result::success(make_ref<TestResource>())};
        };

        Pool<TestResource> pool = Pool<TestResource>((size_t)1, createResource, isGood);
        ASSERT_EQ(pool.capacity(), 1);
        pool.incCapacity();
        ASSERT_EQ(pool.capacity(), 2);
    }

    TEST_F(PoolTest, capacityCanBeDecremented) {
        auto isGood = [](const ref<TestResource> & r) { return r->good; };
        auto createResource = []() -> kj::Promise<Result<ref<TestResource>>> {
            return {result::success(make_ref<TestResource>())};
        };

        Pool<TestResource> pool = Pool<TestResource>((size_t)1, createResource, isGood);
        ASSERT_EQ(pool.capacity(), 1);
        pool.decCapacity();
        ASSERT_EQ(pool.capacity(), 0);
    }

    // Test that the resources we allocate are being reused when they are still good.
    TEST_F(PoolTest, reuseResource) {
        auto isGood = [](const ref<TestResource> & r) { return true; };
        auto createResource = []() -> kj::Promise<Result<ref<TestResource>>> {
            return {result::success(make_ref<TestResource>())};
        };

        Pool<TestResource> pool = Pool<TestResource>((size_t)1, createResource, isGood);

        // Compare the instance counter between the two handles. We expect them to be equal
        // as the pool should hand out the same (still) good one again.
        int counter = -1;
        {
            Pool<TestResource>::Handle h = pool.get().wait(ws).value();
            counter = h->num;
        } // the first handle goes out of scope

        { // the second handle should contain the same resource (with the same counter value)
            Pool<TestResource>::Handle h = pool.get().wait(ws).value();
            ASSERT_EQ(h->num, counter);
        }
    }

    // Test that the resources we allocate are being thrown away when they are no longer good.
    TEST_F(PoolTest, badResourceIsNotReused) {
        auto isGood = [](const ref<TestResource> & r) { return false; };
        auto createResource = []() -> kj::Promise<Result<ref<TestResource>>> {
            return {result::success(make_ref<TestResource>())};
        };

        Pool<TestResource> pool = Pool<TestResource>((size_t)1, createResource, isGood);

        // Compare the instance counter between the two handles. We expect them
        // to *not* be equal as the pool should hand out a new instance after
        // the first one was returned.
        int counter = -1;
        {
            Pool<TestResource>::Handle h = pool.get().wait(ws).value();
            counter = h->num;
        } // the first handle goes out of scope

        {
          // the second handle should contain a different resource (with a
          //different counter value)
            Pool<TestResource>::Handle h = pool.get().wait(ws).value();
            ASSERT_NE(h->num, counter);
        }
    }

    TEST_F(PoolTest, factoryCancel)
    {
        // resource factories failing must not crash if a promise is cancelled.
        // c.f. https://git.lix.systems/lix-project/lix/issues/1041

        {
            Pool<TestResource> pool(1);
            (void) pool.get();
        }

        {
            auto createResource = []() -> kj::Promise<Result<ref<TestResource>>> {
                return {result::failure(std::make_exception_ptr(Error("failure")))};
            };

            Pool<TestResource> pool(1, createResource);
            (void) pool.get();
        }
    }

    TEST_F(PoolTest, factoryFailureNormal)
    {
        auto createResource = []() -> kj::Promise<Result<ref<TestResource>>> {
            return {result::failure(std::make_exception_ptr(Error("failure")))};
        };

        Pool<TestResource> pool(1, createResource);

        ASSERT_THROW(pool.get().wait(ws).value(), Error);
    }
}