From fccf1a2cc3fc4d579a6e13168c5c351875de5c8a Mon Sep 17 00:00:00 2001 From: Simon Giesecke Date: Fri, 28 Aug 2020 10:02:49 +0000 Subject: [PATCH] Bug 1661084 - Add CollectWhile/CollectEach algorithms for Result. r=janv Differential Revision: https://phabricator.services.mozilla.com/D88169 --- dom/quota/QuotaCommon.h | 48 +++++++++ dom/quota/test/gtest/TestQuotaCommon.cpp | 118 +++++++++++++++++++++++ 2 files changed, 166 insertions(+) diff --git a/dom/quota/QuotaCommon.h b/dom/quota/QuotaCommon.h index 7efc9931733a..59f90b2f13bf 100644 --- a/dom/quota/QuotaCommon.h +++ b/dom/quota/QuotaCommon.h @@ -611,6 +611,54 @@ Result ToResultGet(const Func& aFunc, Args&&... aArgs) { return res; } +// Like Rust's collect with a step function, not a generic iterator/range. +// +// Cond must be a function type with a return type to Result, where +// V is convertible to bool +// - success converts to true indicates that collection shall continue +// - success converts to false indicates that collection is completed +// - error indicates that collection shall stop, propagating the error +// +// Body must a function type accepting a V xvalue with a return type convertible +// to Result. +template +auto CollectEach(const Step& aStep, const Body& aBody) + -> Result::err_type> { + using StepResultType = typename std::result_of_t::ok_type; + + static_assert(std::is_empty_v< + typename std::result_of_t::ok_type>); + + while (true) { + StepResultType element; + MOZ_TRY_VAR(element, aStep()); + + if (!static_cast(element)) { + break; + } + + MOZ_TRY(aBody(std::move(element))); + } + + return mozilla::Ok{}; +} + +// Like Rust's collect with a while loop, not a generic iterator/range. +// +// Cond must be a function type accepting no parameters with a return type +// convertible to Result, where +// - success true indicates that collection shall continue +// - success false indicates that collection is completed +// - error indicates that collection shall stop, propagating the error +// +// Body must a function type accepting no parameters with a return type +// convertible to Result. +template +auto CollectWhile(const Cond& aCond, const Body& aBody) + -> Result::err_type> { + return CollectEach(aCond, [&aBody](bool) { return aBody(); }); +} + namespace dom { namespace quota { diff --git a/dom/quota/test/gtest/TestQuotaCommon.cpp b/dom/quota/test/gtest/TestQuotaCommon.cpp index fb4ee0894f66..f567a25c7b81 100644 --- a/dom/quota/test/gtest/TestQuotaCommon.cpp +++ b/dom/quota/test/gtest/TestQuotaCommon.cpp @@ -491,3 +491,121 @@ TEST(QuotaCommon_ToResultGet, Lambda_WithInput_Err) EXPECT_TRUE(res.isErr()); EXPECT_EQ(res.unwrapErr(), NS_ERROR_FAILURE); } + +// BEGIN COPY FROM mfbt/tests/TestResult.cpp +struct Failed { + int x; +}; + +static GenericErrorResult Fail() { + static Failed failed; + return Err(failed); +} + +static Result Task1(bool pass) { + if (!pass) { + return Fail(); // implicit conversion from GenericErrorResult to Result + } + return Ok(); +} +// END COPY FROM mfbt/tests/TestResult.cpp + +static Result Condition(bool aNoError, bool aResult) { + return Task1(aNoError).map([aResult](auto) { return aResult; }); +} + +TEST(QuotaCommon_CollectWhileTest, NoFailures) +{ + const size_t loopCount = 5; + size_t conditionExecutions = 0; + size_t bodyExecutions = 0; + auto result = CollectWhile( + [&conditionExecutions] { + ++conditionExecutions; + return Condition(true, conditionExecutions <= loopCount); + }, + [&bodyExecutions] { + ++bodyExecutions; + return Task1(true); + }); + static_assert(std::is_same_v>); + MOZ_RELEASE_ASSERT(result.isOk()); + MOZ_RELEASE_ASSERT(loopCount == bodyExecutions); + MOZ_RELEASE_ASSERT(1 + loopCount == conditionExecutions); +} + +TEST(QuotaCommon_CollectWhileTest, BodyFailsImmediately) +{ + size_t conditionExecutions = 0; + size_t bodyExecutions = 0; + auto result = CollectWhile( + [&conditionExecutions] { + ++conditionExecutions; + return Condition(true, true); + }, + [&bodyExecutions] { + ++bodyExecutions; + return Task1(false); + }); + static_assert(std::is_same_v>); + MOZ_RELEASE_ASSERT(result.isErr()); + MOZ_RELEASE_ASSERT(1 == bodyExecutions); + MOZ_RELEASE_ASSERT(1 == conditionExecutions); +} + +TEST(QuotaCommon_CollectWhileTest, BodyFailsOnSecondExecution) +{ + size_t conditionExecutions = 0; + size_t bodyExecutions = 0; + auto result = CollectWhile( + [&conditionExecutions] { + ++conditionExecutions; + return Condition(true, true); + }, + [&bodyExecutions] { + ++bodyExecutions; + return Task1(bodyExecutions < 2); + }); + static_assert(std::is_same_v>); + MOZ_RELEASE_ASSERT(result.isErr()); + MOZ_RELEASE_ASSERT(2 == bodyExecutions); + MOZ_RELEASE_ASSERT(2 == conditionExecutions); +} + +TEST(QuotaCommon_CollectWhileTest, ConditionFailsImmediately) +{ + size_t conditionExecutions = 0; + size_t bodyExecutions = 0; + auto result = CollectWhile( + [&conditionExecutions] { + ++conditionExecutions; + return Condition(false, true); + }, + [&bodyExecutions] { + ++bodyExecutions; + return Task1(true); + }); + static_assert(std::is_same_v>); + MOZ_RELEASE_ASSERT(result.isErr()); + MOZ_RELEASE_ASSERT(0 == bodyExecutions); + MOZ_RELEASE_ASSERT(1 == conditionExecutions); +} + +TEST(QuotaCommon_CollectWhileTest, ConditionFailsOnSecondExecution) +{ + size_t conditionExecutions = 0; + size_t bodyExecutions = 0; + auto result = CollectWhile( + [&conditionExecutions] { + ++conditionExecutions; + return Condition(conditionExecutions < 2, true); + }, + [&bodyExecutions] { + ++bodyExecutions; + return Task1(true); + }); + static_assert(std::is_same_v>); + MOZ_RELEASE_ASSERT(result.isErr()); + MOZ_RELEASE_ASSERT(1 == bodyExecutions); + MOZ_RELEASE_ASSERT(2 == conditionExecutions); +}