text:
//---------------------------------------------------------------------------//
// Copyright (c) 2018-2021 Mikhail Komarov <nemo@nil.foundation>
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//---------------------------------------------------------------------------//

#include <nil/actor/testing/test_case.hh>

#include <nil/actor/core/reactor.hh>
#include <nil/actor/core/shared_ptr.hh>
#include <nil/actor/core/future-util.hh>
#include <nil/actor/core/sleep.hh>
#include <nil/actor/core/stream.hh>
#include <nil/actor/detail/backtrace.hh>
#include <nil/actor/core/do_with.hh>
#include <nil/actor/core/shared_future.hh>
#include <nil/actor/core/manual_clock.hh>
#include <nil/actor/core/thread.hh>
#include <nil/actor/core/print.hh>
#include <nil/actor/core/gate.hh>
#include <nil/actor/detail/log.hh>
#include <boost/iterator/counting_iterator.hpp>
#include <nil/actor/testing/thread_test_case.hh>

#include <boost/range/iterator_range.hpp>
#include <boost/range/irange.hpp>

#include <nil/actor/core/detail/api-level.hh>

using namespace nil::actor;
using namespace std::chrono_literals;

static_assert(std::is_nothrow_default_constructible_v<gate>, "nil::actor::gate constructor must not throw");
static_assert(std::is_nothrow_move_constructible_v<gate>, "nil::actor::gate move constructor must not throw");

static_assert(std::is_nothrow_default_constructible_v<shared_future<>>);
static_assert(std::is_nothrow_copy_constructible_v<shared_future<>>);
static_assert(std::is_nothrow_move_constructible_v<shared_future<>>);

static_assert(std::is_nothrow_move_constructible_v<shared_promise<>>);

class expected_exception : public std::runtime_error {
public:
    expected_exception() : runtime_error("expected") {
    }
};

#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wself-move"
#endif
ACTOR_TEST_CASE(test_self_move) {
    future_state<std::tuple<std::unique_ptr<int>>> s1;
    s1.set(std::make_unique<int>(42));
    s1 = std::move(s1);    // no crash, but the value of s1 is not defined.

#if ACTOR_API_LEVEL < 5
    future_state<std::tuple<std::unique_ptr<int>>> s2;
#else
    future_state<std::unique_ptr<int>> s2;
#endif
    s2.set(std::make_unique<int>(42));
    std::swap(s2, s2);
    BOOST_REQUIRE_EQUAL(*std::move(s2).get0(), 42);

    promise<std::unique_ptr<int>> p1;
    p1.set_value(std::make_unique<int>(42));
    p1 = std::move(p1);    // no crash, but the value of p1 is not defined.

    promise<std::unique_ptr<int>> p2;
    p2.set_value(std::make_unique<int>(42));
    std::swap(p2, p2);
    BOOST_REQUIRE_EQUAL(*p2.get_future().get0(), 42);

    auto f1 = make_ready_future<std::unique_ptr<int>>(std::make_unique<int>(42));
    f1 = std::move(f1);    // no crash, but the value of f1 is not defined.

    auto f2 = make_ready_future<std::unique_ptr<int>>(std::make_unique<int>(42));
    std::swap(f2, f2);
    BOOST_REQUIRE_EQUAL(*f2.get0(), 42);

    return make_ready_future<>();
}
#ifdef __clang__
#pragma clang diagnostic pop
#endif

static subscription<int> get_empty_subscription(std::function<future<>(int)> func) {
    stream<int> s;
    auto ret = s.listen(func);
    s.close();
    return ret;
}

ACTOR_TEST_CASE(test_stream) {
    auto sub = get_empty_subscription([](int x) { return make_ready_future<>(); });
    return sub.done();
}

ACTOR_TEST_CASE(test_stream_drop_sub) {
    auto s = make_lw_shared<stream<int>>();
    boost::optional<future<>> ret;
    {
        auto sub = s->listen([](int x) { return make_ready_future<>(); });
        ret = sub.done();
        // It is ok to drop the subscription when we only want the competition future.
    }
    return s->produce(42).then([ret = std::move(*ret), s]() mutable {
        s->close();
        return std::move(ret);
    });
}

ACTOR_TEST_CASE(test_reference) {
    int a = 42;
    future<int &> orig = make_ready_future<int &>(a);
    future<int &> fut = std::move(orig);
    int &r = fut.get0();
    r = 43;
    BOOST_REQUIRE_EQUAL(a, 43);
    return make_ready_future<>();
}

ACTOR_TEST_CASE(test_set_future_state_with_tuple) {
    future_state<std::tuple<int>> s1;
    promise<int> p1;
    const std::tuple<int> v1(42);
    s1.set(v1);
    p1.set_value(v1);

    return make_ready_future<>();
}

ACTOR_THREAD_TEST_CASE(test_set_value_make_exception_in_copy) {
    struct throw_in_copy {
        throw_in_copy() noexcept = default;
        throw_in_copy(throw_in_copy &&x) noexcept {
        }
        throw_in_copy(const throw_in_copy &x) {
            throw 42;
        }
    };
    promise<throw_in_copy> p1;
    throw_in_copy v;
    p1.set_value(v);
    BOOST_REQUIRE_THROW(p1.get_future().get(), int);
}

ACTOR_THREAD_TEST_CASE(test_set_exception_in_constructor) {
    struct throw_in_constructor {
        throw_in_constructor() {
            throw 42;
        }
    };
    future<throw_in_constructor> f = make_ready_future<throw_in_constructor>();
    BOOST_REQUIRE(f.failed());
    BOOST_REQUIRE_THROW(f.get(), int);
}

ACTOR_TEST_CASE(test_finally_is_called_on_success_and_failure) {
    auto finally1 = make_shared<bool>();
    auto finally2 = make_shared<bool>();

    return make_ready_future()
        .then([] {})
        .finally([=] { *finally1 = true; })
        .then([] { throw std::runtime_error(""); })
        .finally([=] { *finally2 = true; })
        .then_wrapped([=](auto &&f) {
            BOOST_REQUIRE(*finally1);
            BOOST_REQUIRE(*finally2);

            // Should be failed.
            try {
                f.get();
                BOOST_REQUIRE(false);
            } catch (...) {
            }
        });
}

ACTOR_TEST_CASE(test_get_on_promise) {
    auto p = promise<uint32_t>();
    p.set_value(10);
    BOOST_REQUIRE_EQUAL(10u, p.get_future().get0());
    return make_ready_future();
}

// An exception class with a controlled what() overload
class test_exception : public std::exception {
    sstring _what;

public:
    explicit test_exception(sstring what) : _what(std::move(what)) {
    }
    virtual const char *what() const noexcept override {
        return _what.c_str();
    }
};

static void check_finally_exception(std::exception_ptr ex) {
    BOOST_REQUIRE_EQUAL(
        fmt::format("{}", ex),
        "nil::actor::nested_exception: test_exception (bar) (while cleaning up after test_exception (foo))");
    try {
        // convert to the concrete type nested_exception
        std::rethrow_exception(ex);
    } catch (nil::actor::nested_exception &ex) {
        try {
            std::rethrow_exception(ex.inner);
        } catch (test_exception &inner) {
            BOOST_REQUIRE_EQUAL(inner.what(), "bar");
        }
        try {
            ex.rethrow_nested();
        } catch (test_exception &outer) {
            BOOST_REQUIRE_EQUAL(outer.what(), "foo");
        }
    }
}

ACTOR_TEST_CASE(test_finally_exception) {
    return make_ready_future<>()
        .then([] { throw test_exception("foo"); })
        .finally([] { throw test_exception("bar"); })
        .handle_exception(check_finally_exception);
}

ACTOR_TEST_CASE(test_finally_exceptional_future) {
    return make_ready_future<>()
        .then([] { throw test_exception("foo"); })
        .finally([] { return make_exception_future<>(test_exception("bar")); })
        .handle_exception(check_finally_exception);
}

ACTOR_TEST_CASE(test_finally_waits_for_inner) {
    auto finally = make_shared<bool>();
    auto p = make_shared<promise<>>();

    auto f = make_ready_future()
                 .then([] {})
                 .finally([=] { return p->get_future().then([=] { *finally = true; }); })
                 .then([=] { BOOST_REQUIRE(*finally); });
    BOOST_REQUIRE(!*finally);
    p->set_value();
    return f;
}

ACTOR_TEST_CASE(test_finally_is_called_on_success_and_failure__not_ready_to_armed) {
    auto finally1 = make_shared<bool>();
    auto finally2 = make_shared<bool>();

    promise<> p;
    auto f = p.get_future()
                 .finally([=] { *finally1 = true; })
                 .then([] { throw std::runtime_error(""); })
                 .finally([=] { *finally2 = true; })
                 .then_wrapped([=](auto &&f) {
                     BOOST_REQUIRE(*finally1);
                     BOOST_REQUIRE(*finally2);
                     try {
                         f.get();
                     } catch (...) {
                     }    // silence exceptional future ignored messages
                 });

    p.set_value();
    return f;
}

ACTOR_TEST_CASE(test_exception_from_finally_fails_the_target) {
    promise<> pr;
    auto f = pr.get_future()
                 .finally([=] { throw std::runtime_error(""); })
                 .then([] { BOOST_REQUIRE(false); })
                 .then_wrapped([](auto &&f) {
                     try {
                         f.get();
                     } catch (...) {
                     }    // silence exceptional future ignored messages
                 });

    pr.set_value();
    return f;
}

ACTOR_TEST_CASE(test_exception_from_finally_fails_the_target_on_already_resolved) {
    return make_ready_future()
        .finally([=] { throw std::runtime_error(""); })
        .then([] { BOOST_REQUIRE(false); })
        .then_wrapped([](auto &&f) {
            try {
                f.get();
            } catch (...) {
            }    // silence exceptional future ignored messages
        });
}

ACTOR_TEST_CASE(test_exception_thrown_from_then_wrapped_causes_future_to_fail) {
    return make_ready_future().then_wrapped([](auto &&f) { throw std::runtime_error(""); }).then_wrapped([](auto &&f) {
        try {
            f.get();
            BOOST_REQUIRE(false);
        } catch (...) {
        }
    });
}

ACTOR_TEST_CASE(test_exception_thrown_from_then_wrapped_causes_future_to_fail__async_case) {
    promise<> p;

    auto f = p.get_future().then_wrapped([](auto &&f) { throw std::runtime_error(""); }).then_wrapped([](auto &&f) {
        try {
            f.get();
            BOOST_REQUIRE(false);
        } catch (...) {
        }
    });

    p.set_value();

    return f;
}

ACTOR_TEST_CASE(test_failing_intermediate_promise_should_fail_the_master_future) {
    promise<> p1;
    promise<> p2;

    auto f = p1.get_future().then([f = p2.get_future()]() mutable { return std::move(f); }).then([] {
        BOOST_REQUIRE(false);
    });

    p1.set_value();
    p2.set_exception(std::runtime_error("boom"));

    return std::move(f).then_wrapped([](auto &&f) {
        try {
            f.get();
            BOOST_REQUIRE(false);
        } catch (...) {
        }
    });
}

ACTOR_TEST_CASE(test_future_forwarding__not_ready_to_unarmed) {
    promise<> p1;
    promise<> p2;

    auto f1 = p1.get_future();
    auto f2 = p2.get_future();

    f1.forward_to(std::move(p2));

    BOOST_REQUIRE(!f2.available());

    auto called = f2.then([] {});

    p1.set_value();
    return called;
}

ACTOR_TEST_CASE(test_future_forwarding__not_ready_to_armed) {
    promise<> p1;
    promise<> p2;

    auto f1 = p1.get_future();
    auto f2 = p2.get_future();

    auto called = f2.then([] {});

    f1.forward_to(std::move(p2));

    BOOST_REQUIRE(!f2.available());

    p1.set_value();

    return called;
}

ACTOR_TEST_CASE(test_future_forwarding__ready_to_unarmed) {
    promise<> p2;

    auto f1 = make_ready_future<>();
    auto f2 = p2.get_future();

    std::move(f1).forward_to(std::move(p2));
    BOOST_REQUIRE(f2.available());

    return std::move(f2).then_wrapped([](future<> f) { BOOST_REQUIRE(!f.failed()); });
}

ACTOR_TEST_CASE(test_future_forwarding__ready_to_armed) {
    promise<> p2;

    auto f1 = make_ready_future<>();
    auto f2 = p2.get_future();

    auto called = std::move(f2).then([] {});

    BOOST_REQUIRE(f1.available());

    f1.forward_to(std::move(p2));
    return called;
}

static void forward_dead_unarmed_promise_with_dead_future_to(promise<> &p) {
    promise<> p2;
    p.get_future().forward_to(std::move(p2));
}

ACTOR_TEST_CASE(test_future_forwarding__ready_to_unarmed_soon_to_be_dead) {
    promise<> p1;
    forward_dead_unarmed_promise_with_dead_future_to(p1);
    make_ready_future<>().forward_to(std::move(p1));
    return make_ready_future<>();
}

ACTOR_TEST_CASE(test_exception_can_be_thrown_from_do_until_body) {
    return do_until([] { return false; },
                    [] {
                        throw expected_exception();
                        return now();
                    })
        .then_wrapped([](auto &&f) {
            try {
                f.get();
                BOOST_FAIL("should have failed");
            } catch (const expected_exception &e) {
                // expected
            }
        });
}

ACTOR_TEST_CASE(test_exception_can_be_thrown_from_do_until_condition) {
    return do_until(
               [] {
                   throw expected_exception();
                   return false;
               },
               [] { return now(); })
        .then_wrapped([](auto &&f) {
            try {
                f.get();
                BOOST_FAIL("should have failed");
            } catch (const expected_exception &e) {
                // expected
            }
        });
}

ACTOR_TEST_CASE(test_bare_value_can_be_returned_from_callback) {
    return now().then([] { return 3; }).then([](int x) { BOOST_REQUIRE(x == 3); });
}

ACTOR_TEST_CASE(test_when_all_iterator_range) {
    std::vector<future<size_t>> futures;
    for (size_t i = 0; i != 1000000; ++i) {
        // Use a mix of available and unavailable futures to exercise
        // both paths in when_all().
        auto fut = (i % 2) == 0 ? make_ready_future<>() : later();
        futures.push_back(fut.then([i] { return i; }));
    }
    // Verify the above statement is correct
    BOOST_REQUIRE(!std::all_of(futures.begin(), futures.end(), [](auto &f) { return f.available(); }));
    auto p = make_shared(std::move(futures));
    return when_all(p->begin(), p->end()).then([p](std::vector<future<size_t>> ret) {
        BOOST_REQUIRE(std::all_of(ret.begin(), ret.end(), [](auto &f) { return f.available(); }));
        BOOST_REQUIRE(
            std::all_of(ret.begin(), ret.end(), [&ret](auto &f) { return f.get0() == size_t(&f - ret.data()); }));
    });
}

ACTOR_TEST_CASE(test_map_reduce) {
    auto square = [](long x) { return make_ready_future<long>(x * x); };
    long n = 1000;
    return map_reduce(boost::make_counting_iterator<long>(0), boost::make_counting_iterator<long>(n), square, long(0),
                      std::plus<long>())
        .then([n](auto result) {
            auto m = n - 1;    // counting does not include upper bound
            BOOST_REQUIRE_EQUAL(result, (m * (m + 1) * (2 * m + 1)) / 6);
        });
}

ACTOR_TEST_CASE(test_map_reduce_simple) {
    return do_with(0L, [](auto &res) {
        long n = 10;
        return map_reduce(
                   boost::make_counting_iterator<long>(0), boost::make_counting_iterator<long>(n),
                   [](long x) { return x; }, [&res](long x) { res += x; })
            .then([n, &res] {
                long expected = (n * (n - 1)) / 2;
                BOOST_REQUIRE_EQUAL(res, expected);
            });
    });
}

ACTOR_TEST_CASE(test_map_reduce_tuple) {
    return do_with(0L, 0L, [](auto &res0, auto &res1) {
        long n = 10;
        return map_reduce(
                   boost::make_counting_iterator<long>(0), boost::make_counting_iterator<long>(n),
                   [](long x) { return std::tuple<long, long>(x, -x); },
                   [&res0, &res1](std::tuple<long, long> t) {
                       res0 += std::get<0>(t);
                       res1 += std::get<1>(t);
                   })
            .then([n, &res0, &res1] {
                long expected = (n * (n - 1)) / 2;
                BOOST_REQUIRE_EQUAL(res0, expected);
                BOOST_REQUIRE_EQUAL(res1, -expected);
            });
    });
}

// This test doesn't actually test anything - it just waits for the future
// returned by sleep to complete. However, a bug we had in sleep() caused
// this test to fail the sanitizer in the debug build, so this is a useful
// regression test.
ACTOR_TEST_CASE(test_sleep) {
    return sleep(std::chrono::milliseconds(100));
}

ACTOR_TEST_CASE(test_do_with_1) {
    return do_with(1, [](int &one) {
        BOOST_REQUIRE_EQUAL(one, 1);
        return make_ready_future<>();
    });
}

ACTOR_TEST_CASE(test_do_with_2) {
    return do_with(1, 2L, [](int &one, long two) {
        BOOST_REQUIRE_EQUAL(one, 1);
        BOOST_REQUIRE_EQUAL(two, 2);
        return make_ready_future<>();
    });
}

ACTOR_TEST_CASE(test_do_with_3) {
    return do_with(1, 2L, 3, [](int &one, long two, int three) {
        BOOST_REQUIRE_EQUAL(one, 1);
        BOOST_REQUIRE_EQUAL(two, 2);
        BOOST_REQUIRE_EQUAL(three, 3);
        return make_ready_future<>();
    });
}

ACTOR_TEST_CASE(test_do_with_4) {
    return do_with(1, 2L, 3, 4, [](int &one, long two, int three, int four) {
        BOOST_REQUIRE_EQUAL(one, 1);
        BOOST_REQUIRE_EQUAL(two, 2);
        BOOST_REQUIRE_EQUAL(three, 3);
        BOOST_REQUIRE_EQUAL(four, 4);
        return make_ready_future<>();
    });
}

ACTOR_TEST_CASE(test_do_with_5) {
    using func = noncopyable_function<void()>;
    return do_with(func([] {}), [](func &) { return make_ready_future<>(); });
}

ACTOR_TEST_CASE(test_do_with_6) {
    const int x = 42;
    return do_with(int(42), x, [](int &, int &) { return make_ready_future<>(); });
}

ACTOR_TEST_CASE(test_do_with_7) {
    const int x = 42;
    return do_with(x, [](int &) { return make_ready_future<>(); });
}

ACTOR_TEST_CASE(test_do_while_stopping_immediately) {
    return do_with(int(0), [](int &count) {
        return repeat([&count] {
                   ++count;
                   return stop_iteration::yes;
               })
            .then([&count] { BOOST_REQUIRE(count == 1); });
    });
}

ACTOR_TEST_CASE(test_do_while_stopping_after_two_iterations) {
    return do_with(int(0), [](int &count) {
        return repeat([&count] {
                   ++count;
                   return count == 2 ? stop_iteration::yes : stop_iteration::no;
               })
            .then([&count] { BOOST_REQUIRE(count == 2); });
    });
}

ACTOR_TEST_CASE(test_do_while_failing_in_the_first_step) {
    return repeat([] {
               throw expected_exception();
               return stop_iteration::no;
           })
        .then_wrapped([](auto &&f) {
            try {
                f.get();
                BOOST_FAIL("should not happen");
            } catch (const expected_exception &) {
                // expected
            }
        });
}

ACTOR_TEST_CASE(test_do_while_failing_in_the_second_step) {
    return do_with(int(0), [](int &count) {
        return repeat([&count] {
                   ++count;
                   if (count > 1) {
                       throw expected_exception();
                   }
                   return later().then([] { return stop_iteration::no; });
               })
            .then_wrapped([&count](auto &&f) {
                try {
                    f.get();
                    BOOST_FAIL("should not happen");
                } catch (const expected_exception &) {
                    BOOST_REQUIRE(count == 2);
                }
            });
    });
}

ACTOR_TEST_CASE(test_parallel_for_each) {
    return async([] {
        // empty
        parallel_for_each(std::vector<int>(), [](int) -> future<> {
            BOOST_FAIL("should not reach");
            abort();
        }).get();

        // immediate result
        auto range = boost::copy_range<std::vector<int>>(boost::irange(1, 6));
        auto sum = 0;
        parallel_for_each(range, [&sum](int v) {
            sum += v;
            return make_ready_future<>();
        }).get();
        BOOST_REQUIRE_EQUAL(sum, 15);

        // all suspend
        sum = 0;
        parallel_for_each(range, [&sum](int v) { return later().then([&sum, v] { sum += v; }); }).get();
        BOOST_REQUIRE_EQUAL(sum, 15);

        // throws immediately
        BOOST_CHECK_EXCEPTION(parallel_for_each(range, [](int) -> future<> { throw 5; }).get(), int,
                              [](int v) { return v == 5; });

        // throws after suspension
        BOOST_CHECK_EXCEPTION(parallel_for_each(range, [](int) { return later().then([] { throw 5; }); }).get(), int,
                              [](int v) { return v == 5; });
    });
}

ACTOR_TEST_CASE(test_parallel_for_each_early_failure) {
    return do_with(0, [](int &counter) {
        return parallel_for_each(boost::irange(0, 11000),
                                 [&counter](int i) {
                                     using namespace std::chrono_literals;
                                     // force scheduling
                                     return sleep((i % 31 + 1) * 1ms).then([&counter, i] {
                                         ++counter;
                                         if (i % 1777 == 1337) {
                                             return make_exception_future<>(i);
                                         }
                                         return make_ready_future<>();
                                     });
                                 })
            .then_wrapped([&counter](future<> f) {
                BOOST_REQUIRE_EQUAL(counter, 11000);
                BOOST_REQUIRE(f.failed());
                try {
                    f.get();
                    BOOST_FAIL("wanted an exception");
                } catch (int i) {
                    BOOST_REQUIRE(i % 1777 == 1337);
                } catch (...) {
                    BOOST_FAIL("bad exception type");
                }
            });
    });
}

ACTOR_TEST_CASE(test_parallel_for_each_waits_for_all_fibers_even_if_one_of_them_failed) {
    auto can_exit = make_lw_shared<bool>(false);
    return parallel_for_each(boost::irange(0, 2),
                             [can_exit](int i) {
                                 return later().then([i, can_exit] {
                                     if (i == 1) {
                                         throw expected_exception();
                                     } else {
                                         using namespace std::chrono_literals;
                                         return sleep(300ms).then([can_exit] { *can_exit = true; });
                                     }
                                 });
                             })
        .then_wrapped([can_exit](auto &&f) {
            try {
                f.get();
            } catch (...) {
                // expected
            }
            BOOST_REQUIRE(*can_exit);
        });
}

ACTOR_THREAD_TEST_CASE(test_parallel_for_each_broken_promise) {
    auto fut = [] {
        std::vector<promise<>> v(2);
        return parallel_for_each(v, [](promise<> &p) { return p.get_future(); });
    }();
    BOOST_CHECK_THROW(fut.get(), broken_promise);
}

ACTOR_THREAD_TEST_CASE(test_repeat_broken_promise) {
    auto get_fut = [] {
        promise<stop_iteration> pr;
        return pr.get_future();
    };

    future<> r = repeat([fut = get_fut()]() mutable { return std::move(fut); });

    BOOST_CHECK_THROW(r.get(), broken_promise);
}

#ifndef ACTOR_SHUFFLE_TASK_QUEUE
ACTOR_TEST_CASE(test_high_priority_task_runs_in_the_middle_of_loops) {
    auto counter = make_lw_shared<int>(0);
    auto flag = make_lw_shared<bool>(false);
    return repeat([counter, flag] {
        if (*counter == 1) {
            BOOST_REQUIRE(*flag);
            return stop_iteration::yes;
        }
        engine().add_high_priority_task(make_task([flag] { *flag = true; }));
        ++(*counter);
        return stop_iteration::no;
    });
}
#endif

ACTOR_TEST_CASE(futurize_invoke_val_exception) {
    return futurize_invoke(
               [](int arg) {
                   throw expected_exception();
                   return arg;
               },
               1)
        .then_wrapped([](future<int> f) {
            try {
                f.get();
                BOOST_FAIL("should have thrown");
            } catch (expected_exception &e) {
            }
        });
}

ACTOR_TEST_CASE(futurize_invoke_val_ok) {
    return futurize_invoke([](int arg) { return arg * 2; }, 2).then_wrapped([](future<int> f) {
        try {
            auto x = f.get0();
            BOOST_REQUIRE_EQUAL(x, 4);
        } catch (expected_exception &e) {
            BOOST_FAIL("should not have thrown");
        }
    });
}

ACTOR_TEST_CASE(futurize_invoke_val_future_exception) {
    return futurize_invoke(
               [](int a) {
                   return sleep(std::chrono::milliseconds(100)).then([] {
                       throw expected_exception();
                       return make_ready_future<int>(0);
                   });
               },
               0)
        .then_wrapped([](future<int> f) {
            try {
                f.get();
                BOOST_FAIL("should have thrown");
            } catch (expected_exception &e) {
            }
        });
}

ACTOR_TEST_CASE(futurize_invoke_val_future_ok) {
    return futurize_invoke(
               [](int a) {
                   return sleep(std::chrono::milliseconds(100)).then([a] { return make_ready_future<int>(a * 100); });
               },
               2)
        .then_wrapped([](future<int> f) {
            try {
                auto x = f.get0();
                BOOST_REQUIRE_EQUAL(x, 200);
            } catch (expected_exception &e) {
                BOOST_FAIL("should not have thrown");
            }
        });
}
ACTOR_TEST_CASE(futurize_invoke_void_exception) {
    return futurize_invoke([](auto arg) { throw expected_exception(); }, 0).then_wrapped([](future<> f) {
        try {
            f.get();
            BOOST_FAIL("should have thrown");
        } catch (expected_exception &e) {
        }
    });
}

ACTOR_TEST_CASE(futurize_invoke_void_ok) {
    return futurize_invoke([](auto arg) {}, 0).then_wrapped([](future<> f) {
        try {
            f.get();
        } catch (expected_exception &e) {
            BOOST_FAIL("should not have thrown");
        }
    });
}

ACTOR_TEST_CASE(futurize_invoke_void_future_exception) {
    return futurize_invoke(
               [](auto a) { return sleep(std::chrono::milliseconds(100)).then([] { throw expected_exception(); }); }, 0)
        .then_wrapped([](future<> f) {
            try {
                f.get();
                BOOST_FAIL("should have thrown");
            } catch (expected_exception &e) {
            }
        });
}

ACTOR_TEST_CASE(futurize_invoke_void_future_ok) {
    auto a = make_lw_shared<int>(1);
    return futurize_invoke([](int &a) { return sleep(std::chrono::milliseconds(100)).then([&a] { a *= 100; }); }, *a)
        .then_wrapped([a](future<> f) {
            try {
                f.get();
                BOOST_REQUIRE_EQUAL(*a, 100);
            } catch (expected_exception &e) {
                BOOST_FAIL("should not have thrown");
            }
        });
}

ACTOR_TEST_CASE(test_unused_shared_future_is_not_a_broken_future) {
    promise<> p;
    shared_future<> s(p.get_future());
    return make_ready_future<>();
}

ACTOR_TEST_CASE(test_shared_future_propagates_value_to_all) {
    return nil::actor::async([] {
        promise<shared_ptr<int>> p;    // shared_ptr<> to check it deals with emptyable types
        shared_future<shared_ptr<int>> f(p.get_future());

        auto f1 = f.get_future();
        auto f2 = f.get_future();

        p.set_value(make_shared<int>(1));
        BOOST_REQUIRE(*f1.get0() == 1);
        BOOST_REQUIRE(*f2.get0() == 1);
    });
}

template<typename... T>
void check_fails_with_expected(future<T...> f) {
    try {
        f.get();
        BOOST_FAIL("Should have failed");
    } catch (expected_exception &) {
        // expected
    }
}

ACTOR_TEST_CASE(test_shared_future_propagates_value_to_copies) {
    return nil::actor::async([] {
        promise<int> p;
        auto sf1 = shared_future<int>(p.get_future());
        auto sf2 = sf1;

        auto f1 = sf1.get_future();
        auto f2 = sf2.get_future();

        p.set_value(1);

        BOOST_REQUIRE(f1.get0() == 1);
        BOOST_REQUIRE(f2.get0() == 1);
    });
}

ACTOR_TEST_CASE(test_obtaining_future_from_shared_future_after_it_is_resolved) {
    promise<int> p1;
    promise<int> p2;
    auto sf1 = shared_future<int>(p1.get_future());
    auto sf2 = shared_future<int>(p2.get_future());
    p1.set_value(1);
    p2.set_exception(expected_exception());
    return sf2.get_future()
        .then_wrapped([f1 = sf1.get_future()](auto &&f) mutable {
            check_fails_with_expected(std::move(f));
            return std::move(f1);
        })
        .then_wrapped([](auto &&f) { BOOST_REQUIRE(f.get0() == 1); });
}

ACTOR_TEST_CASE(test_valueless_shared_future) {
    return nil::actor::async([] {
        promise<> p;
        shared_future<> f(p.get_future());

        auto f1 = f.get_future();
        auto f2 = f.get_future();

        p.set_value();

        f1.get();
        f2.get();
    });
}

ACTOR_TEST_CASE(test_shared_future_propagates_errors_to_all) {
    promise<int> p;
    shared_future<int> f(p.get_future());

    auto f1 = f.get_future();
    auto f2 = f.get_future();

    p.set_exception(expected_exception());

    return f1
        .then_wrapped([f2 = std::move(f2)](auto &&f) mutable {
            check_fails_with_expected(std::move(f));
            return std::move(f2);
        })
        .then_wrapped([](auto &&f) mutable { check_fails_with_expected(std::move(f)); });
}

ACTOR_TEST_CASE(test_ignored_future_warning) {
    // This doesn't warn:
    promise<> p;
    p.set_exception(expected_exception());
    future<> f = p.get_future();
    f.ignore_ready_future();

    // And by analogy, neither should this
    shared_promise<> p2;
    p2.set_exception(expected_exception());
    future<> f2 = p2.get_shared_future();
    f2.ignore_ready_future();
    return make_ready_future<>();
}

ACTOR_TEST_CASE(test_futurize_from_tuple) {
    std::tuple<int> v1 = std::make_tuple(3);
    std::tuple<> v2 = {};
    future<int> fut1 = futurize<int>::from_tuple(v1);
    future<> fut2 = futurize<void>::from_tuple(v2);
    BOOST_REQUIRE(fut1.get0() == std::get<0>(v1));
#if ACTOR_API_LEVEL < 5
    BOOST_REQUIRE(fut2.get() == v2);
#endif
    return make_ready_future<>();
}

ACTOR_TEST_CASE(test_repeat_until_value) {
    return do_with(int(), [](int &counter) {
        return repeat_until_value([&counter]() -> future<boost::optional<int>> {
                   if (counter == 10000) {
                       return make_ready_future<boost::optional<int>>(counter);
                   } else {
                       ++counter;
                       return make_ready_future<boost::optional<int>>(boost::none);
                   }
               })
            .then([&counter](int result) {
                BOOST_REQUIRE(counter == 10000);
                BOOST_REQUIRE(result == counter);
            });
    });
}

ACTOR_TEST_CASE(test_repeat_until_value_implicit_future) {
    // Same as above, but returning boost::optional<int> instead of future<boost::optional<int>>
    return do_with(int(), [](int &counter) {
        return repeat_until_value([&counter] {
                   if (counter == 10000) {
                       return boost::optional<int>(counter);
                   } else {
                       ++counter;
                       return boost::optional<int>(boost::none);
                   }
               })
            .then([&counter](int result) {
                BOOST_REQUIRE(counter == 10000);
                BOOST_REQUIRE(result == counter);
            });
    });
}

ACTOR_TEST_CASE(test_repeat_until_value_exception) {
    return repeat_until_value([] {
               throw expected_exception();
               return boost::optional<int>(43);
           })
        .then_wrapped([](future<int> f) { check_fails_with_expected(std::move(f)); });
}

ACTOR_TEST_CASE(test_when_allx) {
    return when_all(later(), later(), make_ready_future()).discard_result();
}

// A noncopyable and nonmovable struct
struct non_copy_non_move {
    non_copy_non_move() = default;
    non_copy_non_move(non_copy_non_move &&) = delete;
    non_copy_non_move(const non_copy_non_move &) = delete;
};

ACTOR_TEST_CASE(test_when_all_functions) {
    auto f = [x = non_copy_non_move()] {
        (void)x;
        return make_ready_future<int>(42);
    };
    return when_all(
               f,
               [] {
                   throw 42;
                   return make_ready_future<>();
               },
               later())
        .then([](std::tuple<future<int>, future<>, future<>> res) {
            BOOST_REQUIRE_EQUAL(std::get<0>(res).get0(), 42);

            BOOST_REQUIRE(std::get<1>(res).available());
            BOOST_REQUIRE(std::get<1>(res).failed());
            std::get<1>(res).ignore_ready_future();

            BOOST_REQUIRE(std::get<2>(res).available());
            BOOST_REQUIRE(!std::get<2>(res).failed());
            return make_ready_future<>();
        });
}

ACTOR_TEST_CASE(test_when_all_succeed_functions) {
    auto f = [x = non_copy_non_move()] {
        (void)x;
        return make_ready_future<int>(42);
    };
    return when_all_succeed(
               f,
               [] {
                   throw 42;
                   return make_ready_future<>();
               },
               later())
        .then_wrapped([](auto res) {    // type of `res` changes when SESTAR_API_LEVEL < 3
            BOOST_REQUIRE(res.available());
            BOOST_REQUIRE(res.failed());
            res.ignore_ready_future();
            return make_ready_future<>();
        });
}

template<typename E, typename... T>
static void check_failed_with(future<T...> &&f) {
    BOOST_REQUIRE(f.failed());
    try {
        f.get();
        BOOST_FAIL("exception expected");
    } catch (const E &e) {
        // expected
    } catch (...) {
        BOOST_FAIL(format("wrong exception: {}", std::current_exception()));
    }
}

template<typename... T>
static void check_timed_out(future<T...> &&f) {
    check_failed_with<timed_out_error>(std::move(f));
}

ACTOR_TEST_CASE(test_with_timeout_when_it_times_out) {
    return nil::actor::async([] {
        promise<> pr;
        auto f = with_timeout(manual_clock::now() + 2s, pr.get_future());

        BOOST_REQUIRE(!f.available());

        manual_clock::advance(1s);
        later().get();

        BOOST_REQUIRE(!f.available());

        manual_clock::advance(1s);
        later().get();

        check_timed_out(std::move(f));

        pr.set_value();
    });
}

ACTOR_THREAD_TEST_CASE(test_shared_future_get_future_after_timeout) {
    // This used to crash because shared_future checked if the list of
    // pending futures was empty to decide if it had already called
    // then_wrapped. If all pending futures timed out, it would call
    // it again.
    promise<> pr;
    shared_future<with_clock<manual_clock>> sfut(pr.get_future());
    future<> fut1 = sfut.get_future(manual_clock::now() + 1s);

    manual_clock::advance(1s);

    check_timed_out(std::move(fut1));

    future<> fut2 = sfut.get_future(manual_clock::now() + 1s);
    manual_clock::advance(1s);
    check_timed_out(std::move(fut2));

    future<> fut3 = sfut.get_future(manual_clock::now() + 1s);
    pr.set_value();
    fut3.get();
}

ACTOR_TEST_CASE(test_custom_exception_factory_in_with_timeout) {
    return nil::actor::async([] {
        class custom_error : public std::exception {
        public:
            virtual const char *what() const noexcept {
                return "timedout";
            }
        };
        struct my_exception_factory {
            static auto timeout() {
                return custom_error();
            }
        };
        promise<> pr;
        auto f = with_timeout<my_exception_factory>(manual_clock::now() + 1s, pr.get_future());

        manual_clock::advance(1s);
        later().get();

        check_failed_with<custom_error>(std::move(f));
    });
}

ACTOR_TEST_CASE(test_with_timeout_when_it_does_not_time_out) {
    return nil::actor::async([] {
        {
            promise<int> pr;
            auto f = with_timeout(manual_clock::now() + 1s, pr.get_future());

            pr.set_value(42);

            BOOST_REQUIRE_EQUAL(f.get0(), 42);
        }

        // Check that timer was indeed cancelled
        manual_clock::advance(1s);
        later().get();
    });
}

ACTOR_TEST_CASE(test_shared_future_with_timeout) {
    return nil::actor::async([] {
        shared_promise<with_clock<manual_clock>, int> pr;
        auto f1 = pr.get_shared_future(manual_clock::now() + 1s);
        auto f2 = pr.get_shared_future(manual_clock::now() + 2s);
        auto f3 = pr.get_shared_future();

        BOOST_REQUIRE(!f1.available());
        BOOST_REQUIRE(!f2.available());
        BOOST_REQUIRE(!f3.available());

        manual_clock::advance(1s);
        later().get();

        check_timed_out(std::move(f1));
        BOOST_REQUIRE(!f2.available());
        BOOST_REQUIRE(!f3.available());

        manual_clock::advance(1s);
        later().get();

        check_timed_out(std::move(f2));
        BOOST_REQUIRE(!f3.available());

        pr.set_value(42);

        BOOST_REQUIRE_EQUAL(42, f3.get0());
    });
}

#if ACTOR_API_LEVEL < 4
#define THEN_UNPACK then
#else
#define THEN_UNPACK then_unpack
#endif

ACTOR_TEST_CASE(test_when_all_succeed_tuples) {
    return nil::actor::when_all_succeed(make_ready_future<>(),
                                        make_ready_future<sstring>("hello world"),
                                        make_ready_future<int>(42),
                                        make_ready_future<>(),
                                        make_ready_future<std::tuple<int, sstring>>(std::make_tuple(84, "hi")),
                                        make_ready_future<bool>(true))
        .THEN_UNPACK([](sstring msg, int v, std::tuple<int, sstring> t, bool b) {
            BOOST_REQUIRE_EQUAL(msg, "hello world");
            BOOST_REQUIRE_EQUAL(v, 42);
            BOOST_REQUIRE_EQUAL(std::get<0>(t), 84);
            BOOST_REQUIRE_EQUAL(std::get<1>(t), "hi");
            BOOST_REQUIRE_EQUAL(b, true);

            return nil::actor::when_all_succeed(make_exception_future<>(42),
                                                make_ready_future<sstring>("hello world"),
                                                make_exception_future<int>(43),
                                                make_ready_future<>())
                .THEN_UNPACK([](sstring, int) {
                    BOOST_FAIL("shouldn't reach");
                    return false;
                })
                .handle_exception([](auto excp) {
                    try {
                        std::rethrow_exception(excp);
                    } catch (int v) {
                        BOOST_REQUIRE(v == 42 || v == 43);
                        return true;
                    } catch (...) {
                    }
                    return false;
                })
                .then([](auto ret) { BOOST_REQUIRE(ret); });
        });
}

ACTOR_TEST_CASE(test_when_all_succeed_vector) {
    std::vector<future<>> vecs;
    vecs.emplace_back(make_ready_future<>());
    vecs.emplace_back(make_ready_future<>());
    vecs.emplace_back(make_ready_future<>());
    vecs.emplace_back(make_ready_future<>());
    return nil::actor::when_all_succeed(vecs.begin(), vecs.end())
        .then([] {
            std::vector<future<>> vecs;
            vecs.emplace_back(make_ready_future<>());
            vecs.emplace_back(make_ready_future<>());
            vecs.emplace_back(make_exception_future<>(42));
            vecs.emplace_back(make_exception_future<>(43));
            return nil::actor::when_all_succeed(vecs.begin(), vecs.end());
        })
        .then([] {
            BOOST_FAIL("shouldn't reach");
            return false;
        })
        .handle_exception([](auto excp) {
            try {
                std::rethrow_exception(excp);
            } catch (int v) {
                BOOST_REQUIRE(v == 42 || v == 43);
                return true;
            } catch (...) {
            }
            return false;
        })
        .then([](auto ret) {
            BOOST_REQUIRE(ret);

            std::vector<future<int>> vecs;
            vecs.emplace_back(make_ready_future<int>(1));
            vecs.emplace_back(make_ready_future<int>(2));
            vecs.emplace_back(make_ready_future<int>(3));
            return nil::actor::when_all_succeed(vecs.begin(), vecs.end());
        })
        .then([](std::vector<int> vals) {
            BOOST_REQUIRE_EQUAL(vals.size(), 3u);
            BOOST_REQUIRE_EQUAL(vals[0], 1);
            BOOST_REQUIRE_EQUAL(vals[1], 2);
            BOOST_REQUIRE_EQUAL(vals[2], 3);

            std::vector<future<int>> vecs;
            vecs.emplace_back(make_ready_future<int>(1));
            vecs.emplace_back(make_ready_future<int>(2));
            vecs.emplace_back(make_exception_future<int>(42));
            vecs.emplace_back(make_exception_future<int>(43));
            return nil::actor::when_all_succeed(vecs.begin(), vecs.end());
        })
        .then([](std::vector<int>) {
            BOOST_FAIL("shouldn't reach");
            return false;
        })
        .handle_exception([](auto excp) {
            try {
                std::rethrow_exception(excp);
            } catch (int v) {
                BOOST_REQUIRE(v == 42 || v == 43);
                return true;
            } catch (...) {
            }
            return false;
        })
        .then([](auto ret) { BOOST_REQUIRE(ret); });
}

ACTOR_TEST_CASE(test_futurize_mutable) {
    int count = 0;
    return nil::actor::repeat([count]() mutable {
        ++count;
        if (count == 3) {
            return nil::actor::stop_iteration::yes;
        }
        return nil::actor::stop_iteration::no;
    });
}

ACTOR_THREAD_TEST_CASE(test_broken_promises) {
    boost::optional<future<>> f;
    boost::optional<future<>> f2;
    {    // Broken after attaching a continuation
        auto p = promise<>();
        f = p.get_future();
        f2 = f->then_wrapped([&](future<> f3) {
            BOOST_CHECK(f3.failed());
            BOOST_CHECK_THROW(f3.get(), broken_promise);
            f = {};
        });
    }
    f2->get();
    BOOST_CHECK(!f);

    {    // Broken before attaching a continuation
        auto p = promise<>();
        f = p.get_future();
    }
    f->then_wrapped([&](future<> f3) {
         BOOST_CHECK(f3.failed());
         BOOST_CHECK_THROW(f3.get(), broken_promise);
         f = {};
     }).get();
    BOOST_CHECK(!f);

    {    // Broken before suspending a thread
        auto p = promise<>();
        f = p.get_future();
    }
    BOOST_CHECK_THROW(f->get(), broken_promise);
}

ACTOR_TEST_CASE(test_warn_on_broken_promise_with_no_future) {
    // Example code where we expect a "Exceptional future ignored"
    // warning.
    promise<> p;
    // Intentionally destroy the future
    (void)p.get_future();

    with_allow_abandoned_failed_futures(1, [&] { p.set_exception(std::runtime_error("foo")); });

    return make_ready_future<>();
}

ACTOR_THREAD_TEST_CASE(test_exception_future_with_backtrace) {
    int counter = 0;
    auto inner = [&](bool return_exception) mutable {
        if (!return_exception) {
            return make_ready_future<int>(++counter);
        } else {
            return make_exception_future_with_backtrace<int>(expected_exception());
        }
    };
    auto outer = [&](bool return_exception) {
        return inner(return_exception).then([](int i) { return make_ready_future<int>(-i); });
    };

    BOOST_REQUIRE_EQUAL(outer(false).get0(), -1);
    BOOST_REQUIRE_EQUAL(counter, 1);

    BOOST_CHECK_THROW(outer(true).get0(), expected_exception);
    BOOST_REQUIRE_EQUAL(counter, 1);

    // Example code where we expect a "Exceptional future ignored"
    // warning.
    (void)outer(true).then_wrapped([](future<int> fut) {
        with_allow_abandoned_failed_futures(1, [fut = std::move(fut)]() mutable { auto foo = std::move(fut); });
    });
}

class throw_on_move {
    int _i;

public:
    throw_on_move(int i = 0) noexcept {
        _i = i;
    }
    throw_on_move(const throw_on_move &) = delete;
    throw_on_move(throw_on_move &&) {
        _i = -1;
        throw expected_exception();
    }

    int value() const {
        return _i;
    }
};

ACTOR_TEST_CASE(test_async_throw_on_move) {
    return async([](throw_on_move t) { BOOST_CHECK(false); }, throw_on_move())
        .handle_exception_type([](const expected_exception &) { return make_ready_future<>(); });
}

future<> func4() {
    return later().then([] { actor_logger.info("backtrace: {}", current_backtrace()); });
}

void func3() {
    nil::actor::async([] { func4().get(); }).get();
}

future<> func2() {
    return nil::actor::async([] { func3(); });
}

future<> func1() {
    return later().then([] { return func2(); });
}

ACTOR_THREAD_TEST_CASE(test_backtracing) {
    func1().get();
}

ACTOR_THREAD_TEST_CASE(test_then_unpack) {
    make_ready_future<std::tuple<>>().then_unpack([]() { BOOST_REQUIRE(true); }).get();
    make_ready_future<std::tuple<int>>(std::tuple<int>(1)).then_unpack([](int x) { BOOST_REQUIRE(x == 1); }).get();
    make_ready_future<std::tuple<int, long>>(std::tuple<int, long>(1, 2))
        .then_unpack([](int x, long y) { BOOST_REQUIRE(x == 1 && y == 2); })
        .get();
    make_ready_future<std::tuple<std::unique_ptr<int>>>(std::make_tuple(std::make_unique<int>(42)))
        .then_unpack([](std::unique_ptr<int> p1) { BOOST_REQUIRE(*p1 == 42); })
        .get();
}

future<> test_then_function_f() {
    return make_ready_future<>();
}

ACTOR_TEST_CASE(test_then_function) {
    return make_ready_future<>().then(test_then_function_f);
}

ACTOR_THREAD_TEST_CASE(test_with_gate) {
    gate g;
    int counter = 0;
    int gate_closed_errors = 0;
    int other_errors = 0;

    // test normal operation when gate is opened
    BOOST_CHECK_NO_THROW(with_gate(g, [&] { counter++; }).get());
    BOOST_REQUIRE_EQUAL(counter, 1);

    // test that an exception returned by the calling func
    // is propagated to with_gate future
    counter = gate_closed_errors = other_errors = 0;
    BOOST_CHECK_NO_THROW(with_gate(g,
                                   [&] {
                                       counter++;
                                       return make_exception_future<>(expected_exception());
                                   })
                             .handle_exception_type([&](gate_closed_exception &e) { gate_closed_errors++; })
                             .handle_exception([&](std::exception_ptr) { other_errors++; })
                             .get());
    BOOST_REQUIRE(counter);
    BOOST_REQUIRE(!gate_closed_errors);
    BOOST_REQUIRE(other_errors);

    g.close().get();

    // test that with_gate.get() throws when the gate is closed
    counter = gate_closed_errors = other_errors = 0;
    BOOST_CHECK_THROW(with_gate(g, [&] { counter++; }).get(), gate_closed_exception);
    BOOST_REQUIRE(!counter);

    // test that with_gate throws when the gate is closed
    counter = gate_closed_errors = other_errors = 0;
    BOOST_CHECK_THROW(with_gate(g, [&] { counter++; })
                          .then_wrapped([&](future<> f) {
                              auto eptr = f.get_exception();
                              try {
                                  std::rethrow_exception(eptr);
                              } catch (gate_closed_exception &e) {
                                  gate_closed_errors++;
                              } catch (...) {
                                  other_errors++;
                              }
                          })
                          .get(),
                      gate_closed_exception);
    BOOST_REQUIRE(!counter);
    BOOST_REQUIRE(!gate_closed_errors);
    BOOST_REQUIRE(!other_errors);

    // test that try_with_gate returns gate_closed_exception when the gate is closed
    counter = gate_closed_errors = other_errors = 0;
    try_with_gate(g, [&] { counter++; })
        .handle_exception_type([&](gate_closed_exception &e) { gate_closed_errors++; })
        .handle_exception([&](std::exception_ptr) { other_errors++; })
        .get();
    BOOST_REQUIRE(!counter);
    BOOST_REQUIRE(gate_closed_errors);
    BOOST_REQUIRE(!other_errors);
}

ACTOR_THREAD_TEST_CASE(test_max_concurrent_for_each) {
    BOOST_TEST_MESSAGE("empty range");
    max_concurrent_for_each(std::vector<int>(), 3, [](int) {
        BOOST_FAIL("should not reach");
        return make_exception_future<>(std::bad_function_call());
    }).get();

    auto range = boost::copy_range<std::vector<int>>(boost::irange(1, 8));

    BOOST_TEST_MESSAGE("iterator");
    auto sum = 0;
    max_concurrent_for_each(range.begin(), range.end(), 3, [&sum](int v) {
        sum += v;
        return make_ready_future<>();
    }).get();
    BOOST_REQUIRE_EQUAL(sum, 28);

    BOOST_TEST_MESSAGE("const iterator");
    sum = 0;
    max_concurrent_for_each(range.cbegin(), range.cend(), 3, [&sum](int v) {
        sum += v;
        return make_ready_future<>();
    }).get();
    BOOST_REQUIRE_EQUAL(sum, 28);

    BOOST_TEST_MESSAGE("reverse iterator");
    sum = 0;
    max_concurrent_for_each(range.rbegin(), range.rend(), 3, [&sum](int v) {
        sum += v;
        return make_ready_future<>();
    }).get();
    BOOST_REQUIRE_EQUAL(sum, 28);

    BOOST_TEST_MESSAGE("immediate result");
    sum = 0;
    max_concurrent_for_each(range, 3, [&sum](int v) {
        sum += v;
        return make_ready_future<>();
    }).get();
    BOOST_REQUIRE_EQUAL(sum, 28);

    BOOST_TEST_MESSAGE("suspend");
    sum = 0;
    max_concurrent_for_each(range, 3, [&sum](int v) { return later().then([&sum, v] { sum += v; }); }).get();
    BOOST_REQUIRE_EQUAL(sum, 28);

    BOOST_TEST_MESSAGE("throw immediately");
    sum = 0;
    BOOST_CHECK_EXCEPTION(max_concurrent_for_each(range, 3,
                                                  [&sum](int v) {
                                                      sum += v;
                                                      if (v == 1) {
                                                          throw 5;
                                                      }
                                                      return make_ready_future<>();
                                                  })
                              .get(),
                          int, [](int v) { return v == 5; });
    BOOST_REQUIRE_EQUAL(sum, 28);

    BOOST_TEST_MESSAGE("throw after suspension");
    sum = 0;
    BOOST_CHECK_EXCEPTION(max_concurrent_for_each(range, 3,
                                                  [&sum](int v) {
                                                      return later().then([&sum, v] {
                                                          sum += v;
                                                          if (v == 2) {
                                                              throw 5;
                                                          }
                                                      });
                                                  })
                              .get(),
                          int, [](int v) { return v == 5; });

    BOOST_TEST_MESSAGE("concurrency higher than vector length");
    sum = 0;
    max_concurrent_for_each(range, range.size() + 3, [&sum](int v) {
        sum += v;
        return make_ready_future<>();
    }).get();
    BOOST_REQUIRE_EQUAL(sum, 28);
}

ext: cc

max_stars_repo_name: NilFoundation/actor-core

max_issues_repo_name: NilFoundation/actor-core

max_forks_repo_name: NilFoundation/actor-core


text:
#include "pch.h"

#include <boost/test/unit_test.hpp>

#include "InputEvent/InputEvent.h"
#include "InputEvent/DefaultInputEvent.h"
#include "InputEvent/InputEventCollection.h"
#include "InputEvent/InputEventInverter.h"
#include "InputEvent/InputEventToggle.h"
#include "InputEvent/InputEventSwitch.h"
#include "InputEvent/SfJoystickAnalogInputEvent.h"
#include "InputEvent/SfJoystickDigitalInputEvent.h"
#include "InputEvent/SfKeyboardInputEvent.h"

typedef std::tuple<
DefaultInputEvent,
ActiveInputEvent,
InputEventCollection,
InputEventInverter,
InputEventToggle,
InputEventSwitch,
SfJoystickAnalogInputEvent,
SfJoystickDigitalInputEvent,
SfKeyboardInputEvent> testTypes;

BOOST_AUTO_TEST_SUITE(InputEventTest);
    BOOST_AUTO_TEST_CASE_TEMPLATE(contructorTest, T, testTypes)
    {
        T i, j;
        BOOST_TEST((i == j));
    }
    BOOST_AUTO_TEST_SUITE(InputEventEqualityTest);
        BOOST_AUTO_TEST_CASE_TEMPLATE(DefaultInputEventTestEquals, T, testTypes)
        {
            T i;
            DefaultInputEvent j;
            if (typeid(T) == typeid(DefaultInputEvent))
                BOOST_TEST((i == j));
            else
                BOOST_TEST(!(i == j));
        }

        BOOST_AUTO_TEST_CASE_TEMPLATE(DefaultInputEventTestNotEquals, T, testTypes)
        {
            T i;
            DefaultInputEvent j;
            if (typeid(T) == typeid(DefaultInputEvent))
                BOOST_TEST(!(i != j));
            else
                BOOST_TEST((i != j));
        }

        BOOST_AUTO_TEST_CASE_TEMPLATE(ActiveInputEventTestEquals, T, testTypes)
        {
            T i;
            ActiveInputEvent j;
            if (typeid(T) == typeid(ActiveInputEvent))
                BOOST_TEST((i == j));
            else
                BOOST_TEST(!(i == j));
        }

        BOOST_AUTO_TEST_CASE_TEMPLATE(ActiveInputEventTestNotEquals, T, testTypes)
        {
            T i;
            ActiveInputEvent j;
            if (typeid(T) == typeid(ActiveInputEvent))
                BOOST_TEST(!(i != j));
            else
                BOOST_TEST((i != j));
        }

        BOOST_AUTO_TEST_CASE_TEMPLATE(InputEventCollectionTestEquals, T, testTypes)
        {
            T i;
            InputEventCollection j;
            if (typeid(T) == typeid(InputEventCollection))
                BOOST_TEST((i == j));
            else
                BOOST_TEST(!(i == j));
        }

        BOOST_AUTO_TEST_CASE_TEMPLATE(InputEventCollectionTestNotEquals, T, testTypes)
        {
            T i;
            InputEventCollection j;
            if (typeid(T) == typeid(InputEventCollection))
                BOOST_TEST(!(i != j));
            else
                BOOST_TEST((i != j));
        }

        BOOST_AUTO_TEST_CASE_TEMPLATE(InputEventInverterTestEquals, T, testTypes)
        {
            T i;
            InputEventInverter j;
            if (typeid(T) == typeid(InputEventInverter))
                BOOST_TEST((i == j));
            else
                BOOST_TEST(!(i == j));
        }

        BOOST_AUTO_TEST_CASE_TEMPLATE(InputEventInverterTestNotEquals, T, testTypes)
        {
            T i;
            InputEventInverter j;
            if (typeid(T) == typeid(InputEventInverter))
                BOOST_TEST(!(i != j));
            else
                BOOST_TEST((i != j));
        }

        BOOST_AUTO_TEST_CASE_TEMPLATE(InputEventToggleTestEquals, T, testTypes)
        {
            T i;
            InputEventToggle j;
            if (typeid(T) == typeid(InputEventToggle))
                BOOST_TEST((i == j));
            else
                BOOST_TEST(!(i == j));
        }

        BOOST_AUTO_TEST_CASE_TEMPLATE(InputEventToggleTestNotEquals, T, testTypes)
        {
            T i;
            InputEventToggle j;
            if (typeid(T) == typeid(InputEventToggle))
                BOOST_TEST(!(i != j));
            else
                BOOST_TEST((i != j));
        }

        BOOST_AUTO_TEST_CASE_TEMPLATE(InputEventSwitchTestEquals, T, testTypes)
        {
            T i;
            InputEventSwitch j;
            if (typeid(T) == typeid(InputEventSwitch))
                BOOST_TEST((i == j));
            else
                BOOST_TEST(!(i == j));
        }

        BOOST_AUTO_TEST_CASE_TEMPLATE(InputEventSwitchTestNotEquals, T, testTypes)
        {
            T i;
            InputEventSwitch j;
            if (typeid(T) == typeid(InputEventSwitch))
                BOOST_TEST(!(i != j));
            else
                BOOST_TEST((i != j));
        }

        BOOST_AUTO_TEST_CASE_TEMPLATE(SfJoystickAnalogInputEventTestEquals, T, testTypes)
        {
            T i;
            SfJoystickAnalogInputEvent j;
            if (typeid(T) == typeid(SfJoystickAnalogInputEvent))
                BOOST_TEST((i == j));
            else
                BOOST_TEST(!(i == j));
        }

        BOOST_AUTO_TEST_CASE_TEMPLATE(SfJoystickAnalogInputEventTestNotEquals, T, testTypes)
        {
            T i;
            SfJoystickAnalogInputEvent j;
            if (typeid(T) == typeid(SfJoystickAnalogInputEvent))
                BOOST_TEST(!(i != j));
            else
                BOOST_TEST((i != j));
        }

        BOOST_AUTO_TEST_CASE_TEMPLATE(SfJoystickDigitalInputEventTestEquals, T, testTypes)
        {
            T i;
            SfJoystickDigitalInputEvent j;
            if (typeid(T) == typeid(SfJoystickDigitalInputEvent))
                BOOST_TEST((i == j));
            else
                BOOST_TEST(!(i == j));
        }

        BOOST_AUTO_TEST_CASE_TEMPLATE(SfJoystickDigitalInputEventTestNotEquals, T, testTypes)
        {
            T i;
            SfJoystickDigitalInputEvent j;
            if (typeid(T) == typeid(SfJoystickDigitalInputEvent))
                BOOST_TEST(!(i != j));
            else
                BOOST_TEST((i != j));
        }

        BOOST_AUTO_TEST_CASE_TEMPLATE(SfKeyboardInputEventTestEquals, T, testTypes)
        {
            T i;
            SfKeyboardInputEvent j;
            if (typeid(T) == typeid(SfKeyboardInputEvent))
                BOOST_TEST((i == j));
            else
                BOOST_TEST(!(i == j));
        }

        BOOST_AUTO_TEST_CASE_TEMPLATE(SfKeyboardInputEventTestNotEquals, T, testTypes)
        {
            T i;
            SfKeyboardInputEvent j;
            if (typeid(T) == typeid(SfKeyboardInputEvent))
                BOOST_TEST(!(i != j));
            else
                BOOST_TEST((i != j));
        }
    BOOST_AUTO_TEST_SUITE_END();
BOOST_AUTO_TEST_SUITE_END();

ext: cpp

max_stars_repo_name: Drew-j-Smith/switch-controller

max_issues_repo_name: Drew-j-Smith/switch-controller

max_forks_repo_name: Drew-j-Smith/switch-controller


text: #include <boost/wave/util/interpret_pragma.hpp>

ext: hpp

max_stars_repo_name: miathedev/BoostForArduino

max_issues_repo_name: miathedev/BoostForArduino

max_forks_repo_name: miathedev/BoostForArduino


text:
#include <boost/ut.hpp>
#include <gsl/gsl-lite.hpp>

#include <alc/linter/address_expr_collector.hpp>
#include <alc/ast/manager.hpp>
#include <alc/ast/statements.hpp>
#include <alc/ast/expressions.hpp>

#include <iostream>
#include <type_traits>
#include <unordered_map>

using namespace boost::ut;

template <class Container>
std::string to_string(const Container& exprs)
{
    if (exprs.empty())
        return "[]";

    std::stringstream ss;
    ss << "[";

    for (size_t i = 0; i < exprs.size() - 1; i++)
    {
        ss << exprs[i]->to_string() << ", ";
    }

    ss << exprs.back()->to_string() << "]";

    return ss.str();
}

template <class Container>
bool assert_no_duplicates(ast::manager& store, const Container& exprs)
{
    std::vector<int> eqClasses(exprs.size(), -1);

    bool noDuplicates = true;
    for (size_t i = 0; i < exprs.size(); i++)
    {
        if (eqClasses[i] != -1)
            continue;

        eqClasses[i] = i;

        size_t dupCount = 0;
        for (size_t j = i + 1; j < exprs.size(); j++)
        {
            if (store.same(exprs[i], exprs[j]))
            {
                eqClasses[j] = i;
                dupCount++;
                noDuplicates = false;
            }
        }

        expect(dupCount == 0_i) << "duplicates for expression" << exprs[i]->to_string();
    }

    if (!noDuplicates)
    {
        expect(false) << "got expression array containing duplicates:" << to_string(exprs);
    }

    return noDuplicates;
}

template <
    class ContainerA,
    class ContainerB,
    bool = std::is_base_of<ast::expression, typename ContainerA::value_type>::value,
    bool = std::is_base_of<ast::expression, typename ContainerB::value_type>::value>
bool assert_expression_sets(ast::manager& store, const ContainerA& got, const ContainerB& want)
{
    bool gotIsSet = true, wantIsSet = true;
    expect(gotIsSet = assert_no_duplicates(store, got)) << "but it should contain only unique expressions";
    expect(wantIsSet = assert_no_duplicates(store, want)) << "the test case is flawed (!!!)";

    if (!wantIsSet)
        return false;

    std::unordered_map<std::string, std::pair<size_t, size_t>> count;
    for (auto& expr : got)
    {
        count[expr->to_string()].first++;
    }

    for (auto& expr : want)
    {
        count[expr->to_string()].second++;
    }

    bool same = true;
    for (auto& kv : count)
    {
        if (kv.second.first != 0 && kv.second.second != 0)
            continue;

        same = false;
        if (kv.second.first == 0)
            expect(false) << "missing" << kv.first;
        else if (kv.second.second == 0)
            expect(false) << "got superfluous" << kv.first;
    }

    if (!same)
    {
        expect(false) << "got expressions" << to_string(got) << "but want" << to_string(want);
    }

    return gotIsSet && same;
}

struct test_case
{
    std::string name;
    ast::statement* input;
    std::vector<ast::var*> wantVars;
    std::vector<ast::expression*> wantExprs;
};

int main()
{
    ast::manager store;

    std::vector<test_case> perDefinitionCases{
        {
            "variable_assigned_allocated_memory_is_address",
            store.make_statement<ast::alloc>(
                "foo", 3
            ),
            {
                store.make_expression<ast::var>("foo"),
            },
            {
                store.make_expression<ast::var>("foo"),
                store.make_expression<ast::add>(ast::var(store, "foo"), ast::integer(store, 1)),
                store.make_expression<ast::add>(ast::var(store, "foo"), ast::integer(store, 2)),
            },
        },
        {
            // Make test for expr lhs
            "store_to_a_variable_makes_it_address",
            store.make_statement<ast::store>(
                ast::var(store, "foo"), ast::integer(store, 3)
            ),
            {
                store.make_expression<ast::var>("foo"),
            },
            {
                store.make_expression<ast::var>("foo"),
            },
        },
        {
            "load_from_a_variable_makes_it_address",
            store.make_statement<ast::load>(
                "foo", ast::var(store, "bar")
            ),
            {
                store.make_expression<ast::var>("bar"),
            },
            {
                store.make_expression<ast::var>("bar"),
            },
        },
        {
            "variable_in_dispose_is_address",
            store.make_statement<ast::dispose>(
                "zap"
            ),
            {
                store.make_expression<ast::var>("zap"),
            },
            {
                store.make_expression<ast::var>("zap"),
            },
        },
        {
            "expression_assigned_to_address_var_is_address",
            store.make_statement<ast::block>(
                ast::decl(store, "foo", ast::integer(store, 0)),
                ast::alloc(store, "x", 2),
                ast::assign(store, "x",
                    ast::add(store,
                        ast::multiply(store,
                            ast::integer(store, 2), ast::var(store, "x")
                        ),
                        ast::integer(store, 5)
                    )
                )
            ),
            {
                store.make_expression<ast::var>("x"),
            },
            {
                store.make_expression<ast::var>("x"),
                store.make_expression<ast::add>(
                    ast::var(store, "x"),
                    ast::integer(store, 1)
                ),
                store.make_expression<ast::add>(
                    ast::multiply(store,
                        ast::integer(store, 2), ast::var(store, "x")
                    ),
                    ast::integer(store, 5)
                ),
            },
        },
        {
            "store_to_an_expression_with_a_variable_makes_them_address",
            store.make_statement<ast::store>(
                ast::add(store,
                    ast::var(store, "foo"), ast::integer(store, 1)
                ),
                ast::integer(store, 3)
            ),
            {
                store.make_expression<ast::var>("foo"),
            },
            {
                store.make_expression<ast::var>("foo"),
                store.make_expression<ast::add>(
                    ast::var(store, "foo"), ast::integer(store, 1)
                ),
            },
        },
        {
            "load_from_an_expression_with_a_variable_makes_them_address",
            store.make_statement<ast::load>(
                "foo",
                ast::add(store,
                    ast::var(store, "bar"), ast::integer(store, 1)
                )
            ),
            {
                store.make_expression<ast::var>("bar"),
            },
            {
                store.make_expression<ast::var>("bar"),
                store.make_expression<ast::add>(
                    ast::var(store, "bar"), ast::integer(store, 1)
                ),
            },
        },
        {
            "declared_variable_is_address_if_initialized_with_address_expression",
            store.make_statement<ast::block>(
                ast::alloc(store, "x", 1),
                ast::decl(store, "y", ast::var(store, "x"))
            ),
            {
                store.make_expression<ast::var>("x"),
                store.make_expression<ast::var>("y"),
            },
            {
                store.make_expression<ast::var>("x"),
                store.make_expression<ast::var>("y"),
            },
        },
        {
            "variable_is_address_if_assigned_address_expr",
            store.make_statement<ast::block>(
                ast::decl(store, "x", ast::integer(store, 0)),
                ast::alloc(store, "y", 1),
                ast::assign(store, "x", ast::var(store, "y"))
            ),
            {
                store.make_expression<ast::var>("x"),
                store.make_expression<ast::var>("y"),
            },
            {
                store.make_expression<ast::var>("x"),
                store.make_expression<ast::var>("y"),
            },
        },
        {
            "assigning_an_address_variable_transfers_its_implicit_address_expressions",
            store.make_statement<ast::block>(
                ast::alloc(store, "x", 3),
                ast::assign(store, "y", ast::var(store, "x")),
                ast::assign(store, "z", ast::var(store, "y")),
                ast::assign(store, "n", ast::var(store, "y"))
            ),
            {
                store.make_expression<ast::var>("x"),
                store.make_expression<ast::var>("y"),
                store.make_expression<ast::var>("z"),
                store.make_expression<ast::var>("n"),
            },
            {
                store.make_expression<ast::var>("x"),
                store.make_expression<ast::add>(ast::var(store, "x"), ast::integer(store, 1)),
                store.make_expression<ast::add>(ast::var(store, "x"), ast::integer(store, 2)),
                store.make_expression<ast::var>("y"),
                store.make_expression<ast::add>(ast::var(store, "y"), ast::integer(store, 1)),
                store.make_expression<ast::add>(ast::var(store, "y"), ast::integer(store, 2)),
                store.make_expression<ast::var>("z"),
                store.make_expression<ast::add>(ast::var(store, "z"), ast::integer(store, 1)),
                store.make_expression<ast::add>(ast::var(store, "z"), ast::integer(store, 2)),
                store.make_expression<ast::var>("n"),
                store.make_expression<ast::add>(ast::var(store, "n"), ast::integer(store, 1)),
                store.make_expression<ast::add>(ast::var(store, "n"), ast::integer(store, 2)),
            },
        },
        {
            "assigning_an_address_variable_to_declaration_transfers_its_implicit_address_expressions",
            store.make_statement<ast::block>(
                ast::alloc(store, "x", 3),
                ast::decl(store, "y", ast::var(store, "x")),
                ast::decl(store, "z", ast::var(store, "y")),
                ast::decl(store, "n", ast::var(store, "y"))
            ),
            {
                store.make_expression<ast::var>("x"),
                store.make_expression<ast::var>("y"),
                store.make_expression<ast::var>("z"),
                store.make_expression<ast::var>("n"),
            },
            {
                store.make_expression<ast::var>("x"),
                store.make_expression<ast::add>(ast::var(store, "x"), ast::integer(store, 1)),
                store.make_expression<ast::add>(ast::var(store, "x"), ast::integer(store, 2)),
                store.make_expression<ast::var>("y"),
                store.make_expression<ast::add>(ast::var(store, "y"), ast::integer(store, 1)),
                store.make_expression<ast::add>(ast::var(store, "y"), ast::integer(store, 2)),
                store.make_expression<ast::var>("z"),
                store.make_expression<ast::add>(ast::var(store, "z"), ast::integer(store, 1)),
                store.make_expression<ast::add>(ast::var(store, "z"), ast::integer(store, 2)),
                store.make_expression<ast::var>("n"),
                store.make_expression<ast::add>(ast::var(store, "n"), ast::integer(store, 1)),
                store.make_expression<ast::add>(ast::var(store, "n"), ast::integer(store, 2)),
            },
        }
    };

    std::vector<test_case> noDuplicatesCases{
        {
            "two_stores_to_an_expression_with_a_variable_does_not_produce_duplicates",
            store.make_statement<ast::block>(
                ast::store(store,
                    ast::add(store,
                        ast::var(store, "foo"), ast::integer(store, 1)
                    ),
                    ast::integer(store, 3)
                ),
                ast::store(store,
                    ast::add(store,
                        ast::var(store, "foo"), ast::integer(store, 1)
                    ),
                    ast::integer(store, 3)
                )
            ),
            {
                store.make_expression<ast::var>("foo"),
            },
            {
                store.make_expression<ast::var>("foo"),
                store.make_expression<ast::add>(
                    ast::var(store, "foo"), ast::integer(store, 1)
                ),
            },
        },
        {
            "two_loads_from_an_expression_with_a_variable_does_not_produce_duplicates",
            store.make_statement<ast::block>(
                ast::load(store,
                    "foo",
                    ast::add(store,
                        ast::var(store, "bar"), ast::integer(store, 1)
                    )
                ),
                ast::load(store,
                    "foo",
                    ast::add(store,
                        ast::var(store, "bar"), ast::integer(store, 1)
                    )
                )
            ),
            {
                store.make_expression<ast::var>("bar"),
            },
            {
                store.make_expression<ast::var>("bar"),
                store.make_expression<ast::add>(
                    ast::var(store, "bar"), ast::integer(store, 1)
                ),
            },
        },
        {
            "double_disposing_a_variable_does_not_produce_duplicates",
            store.make_statement<ast::block>(
                ast::dispose(store, "x"),
                ast::dispose(store, "x")
            ),
            {
                store.make_expression<ast::var>("x"),
            },
            {
                store.make_expression<ast::var>("x"),
            },
        },
        {
            "double_allocation_does_not_produce_duplicates",
            store.make_statement<ast::block>(
                ast::alloc(store, "x", 2),
                ast::alloc(store, "x", 2)
            ),
            {
                store.make_expression<ast::var>("x"),
            },
            {
                store.make_expression<ast::var>("x"),
                store.make_expression<ast::add>(ast::var(store, "x"), ast::integer(store, 1)),
            },
        },
        {
            "double_assignment_does_not_produce_duplicates",
            store.make_statement<ast::block>(
                ast::alloc(store, "x", 2),
                ast::assign(store, "x", ast::integer(store, 2)),
                ast::assign(store, "x", ast::integer(store, 2)),
                ast::decl(store, "y", ast::var(store, "x")),
                ast::assign(store, "y", ast::var(store, "x"))
            ),
            {
                store.make_expression<ast::var>("x"),
                store.make_expression<ast::var>("y"),
            },
            {
                store.make_expression<ast::var>("x"),
                store.make_expression<ast::add>(ast::var(store, "x"), ast::integer(store, 1)),
                store.make_expression<ast::var>("y"),
                store.make_expression<ast::add>(ast::var(store, "y"), ast::integer(store, 1)),
                store.make_expression<ast::integer>(2),
            },
        },
    };

    std::vector<test_case> smallPrograms{
        {
            "control_statements",
            store.make_statement<ast::block>(
                ast::decl(store, "x", ast::integer(store, 0)),
                ast::if_else(store,
                    ast::block(store,
                        ast::while_loop(store,
                            ast::block(store,
                                ast::assign(store,
                                    "x",
                                    ast::add(store,
                                        ast::var(store, "x"), ast::integer(store, 1)
                                    )
                                )
                            )
                        )
                    ),
                    ast::block(store)
                ),
                ast::while_loop(store,
                    ast::if_else(store,
                        ast::alloc(store,
                            "x",
                            3
                        )
                    )
                )
            ),
            {
                store.make_expression<ast::var>("x"),
            },
            {
                store.make_expression<ast::var>("x"),
                store.make_expression<ast::add>(ast::var(store, "x"), ast::integer(store, 1)),
                store.make_expression<ast::add>(ast::var(store, "x"), ast::integer(store, 2)),
            },
        },
        {
            "empty_example",
            store.make_statement<ast::block>(
                ast::decl(store, "x", ast::integer(store, 0)),
                ast::decl(store, "y", ast::integer(store, 0)),

                ast::alloc(store, "x", 2),
                ast::store(store, ast::var(store, "x"), ast::integer(store, 1)),
                ast::store(store, ast::add(store, ast::var(store, "x"), ast::integer(store, 1)), ast::integer(store, 3)),

                ast::alloc(store, "y", 1),
                ast::store(store, ast::var(store, "y"), ast::integer(store, 2)),

                ast::dispose(store, "x"),
                ast::dispose(store, "y")
            ),
            {
                store.make_expression<ast::var>("x"),
                store.make_expression<ast::var>("y"),
            },
            {
                store.make_expression<ast::var>("x"),
                store.make_expression<ast::add>(ast::var(store, "x"), ast::integer(store, 1)),
                store.make_expression<ast::var>("y"),
            }
        }
    };

    auto runCases = [&store] (const std::vector<test_case>& cases) {
        return [&store, &cases] () {
            for (auto& testCase : cases)
            {
                should(testCase.name) = [&] {
                    linter::address_expr_collector collector(store);

                    testCase.input->accept(collector);

                    auto gotVars = collector.address_variables();
                    auto gotExprs = collector.address_expressions();

                    expect(assert_expression_sets(store, gotVars, testCase.wantVars)) << "while asserting found address variables";
                    expect(assert_expression_sets(store, gotExprs, testCase.wantExprs)) << "while asserting found address expressions";
                };
            }
        };
    };

    "per_definition"_test = runCases(perDefinitionCases);

    "no_duplicates"_test = runCases(noDuplicatesCases);

    "small_programs"_test = runCases(smallPrograms);

    return 0;
}

max_stars_repo_name: kuredoro/alcohol

max_issues_repo_name: kuredoro/alcohol

max_forks_repo_name: kuredoro/alcohol