/** * Copyright (C) 2018-present MongoDB, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the Server Side Public License, version 1, * as published by MongoDB, Inc. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * Server Side Public License for more details. * * You should have received a copy of the Server Side Public License * along with this program. If not, see * . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the Server Side Public License in all respects for * all of the code used other than as permitted herein. If you modify file(s) * with this exception, you may extend this exception to your version of the * file(s), but you are not obligated to do so. If you do not wish to do so, * delete this exception statement from your version. If you delete this * exception statement from all source files in the program, then also delete * it in the license file. */ #pragma once #include "mongo/util/future.h" #include "mongo/stdx/thread.h" #include "mongo/unittest/death_test.h" #include "mongo/unittest/unittest.h" #include "mongo/util/executor_test_util.h" #if !defined(__has_feature) #define __has_feature(x) 0 #endif namespace mongo { enum DoExecutorFuture : bool { // Force exemptions to say *why* they shouldn't test ExecutorFuture to ensure that if the // reason stops applying (eg, if we implement ExecutorFuture::tap()) we can delete the enum // value and recover the test coverage. kNoExecutorFuture_needsTap = false, kNoExecutorFuture_needsPromiseSetFrom = false, kDoExecutorFuture = true, }; class DummyInterruptable final : public Interruptible { StatusWith waitForConditionOrInterruptNoAssertUntil( stdx::condition_variable& cv, BasicLockableAdapter m, Date_t deadline) noexcept override { return Status(ErrorCodes::Interrupted, ""); } Date_t getDeadline() const override { MONGO_UNREACHABLE; } Status checkForInterruptNoAssert() noexcept override { MONGO_UNREACHABLE; } IgnoreInterruptsState pushIgnoreInterrupts() override { MONGO_UNREACHABLE; } void popIgnoreInterrupts(IgnoreInterruptsState iis) override { MONGO_UNREACHABLE; } DeadlineState pushArtificialDeadline(Date_t deadline, ErrorCodes::Error error) override { MONGO_UNREACHABLE; } void popArtificialDeadline(DeadlineState) override { MONGO_UNREACHABLE; } Date_t getExpirationDateForWaitForValue(Milliseconds waitFor) override { return Date_t::now() + waitFor; } }; template void completePromise(Promise* promise, Func&& func) { promise->emplaceValue(func()); } template void completePromise(Promise* promise, Func&& func) { func(); promise->emplaceValue(); } inline void sleepIfShould() { #if !__has_feature(thread_sanitizer) // TSAN and rr work better without this sleep, but it is useful for testing correctness. static const bool runningUnderRR = getenv("RUNNING_UNDER_RR") != nullptr; if (!runningUnderRR) sleepmillis(100); // Try to wait until after the Future has been handled. #endif } template > Future async(Func&& func) { auto pf = makePromiseFuture(); stdx::thread([promise = std::move(pf.promise), func = std::forward(func)]() mutable { sleepIfShould(); try { completePromise(&promise, func); } catch (const DBException& ex) { promise.setError(ex.toStatus()); } }) .detach(); return std::move(pf.future); } inline Status failStatus() { return Status(ErrorCodes::Error(50728), "expected failure"); } #define ASSERT_THROWS_failStatus(expr) \ [&] { \ ASSERT_THROWS_WITH_CHECK(expr, DBException, [](const DBException& ex) { \ ASSERT_EQ(ex.toStatus(), failStatus()); \ }); \ }() // Tests a Future completed by completionExpr using testFunc. The Future will be completed in // various ways to maximize test coverage. template >::value>> void FUTURE_SUCCESS_TEST(const CompletionFunc& completion, const TestFunc& test) { using CompletionType = decltype(completion()); { // immediate future test(Future::makeReady(completion())); } { // ready future from promise auto pf = makePromiseFuture(); pf.promise.emplaceValue(completion()); test(std::move(pf.future)); } { // async future test(async([&] { return completion(); })); } if constexpr (doExecutorFuture) { // immediate executor future auto exec = InlineCountingExecutor::make(); test(Future::makeReady(completion()).thenRunOn(exec)); } } template >::value>, typename = void> void FUTURE_SUCCESS_TEST(const CompletionFunc& completion, const TestFunc& test) { using CompletionType = decltype(completion()); { // immediate future completion(); test(Future::makeReady()); } { // ready future from promise auto pf = makePromiseFuture(); completion(); pf.promise.emplaceValue(); test(std::move(pf.future)); } { // async future test(async([&] { return completion(); })); } if constexpr (doExecutorFuture) { // immediate executor future completion(); auto exec = InlineCountingExecutor::make(); test(Future::makeReady().thenRunOn(exec)); } } template void FUTURE_FAIL_TEST(const TestFunc& test) { { // immediate future test(Future::makeReady(failStatus())); } { // ready future from promise auto pf = makePromiseFuture(); pf.promise.setError(failStatus()); test(std::move(pf.future)); } { // async future test(async([&]() -> CompletionType { uassertStatusOK(failStatus()); MONGO_UNREACHABLE; })); } if constexpr (doExecutorFuture) { // immediate executor future auto exec = InlineCountingExecutor::make(); test(Future::makeReady(failStatus()).thenRunOn(exec)); } } } // namespace mongo