From e980ddd34641731a6f242914610af4352a58a2d5 Mon Sep 17 00:00:00 2001 From: gaozheng Date: Thu, 11 Jun 2026 20:13:48 +0800 Subject: [PATCH] =?UTF-8?q?harden(net):=20ApiBatch=20=E5=A5=91=E7=BA=A6?= =?UTF-8?q?=E6=96=AD=E8=A8=80(=E9=9D=9E=E7=A9=BAcalls/=E9=9D=9E=E7=A9=BA?= =?UTF-8?q?=E8=B0=93=E8=AF=8D)+fail-fast=E6=B3=A8=E9=87=8A+=E5=8D=95?= =?UTF-8?q?=E5=85=83=E7=B4=A0=E6=B5=8B=E8=AF=95=EF=BC=88=E8=AF=84=E5=AE=A1?= =?UTF-8?q?=20I-1/I-2/M-1/M-2=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/net/ApiBatch.cpp | 4 +++- tests/net/test_api_batch.cpp | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/net/ApiBatch.cpp b/src/net/ApiBatch.cpp index 65c3b18..f9d8c7f 100644 --- a/src/net/ApiBatch.cpp +++ b/src/net/ApiBatch.cpp @@ -4,6 +4,8 @@ namespace geopro::net { ApiBatch::ApiBatch(QList calls, Predicate isFailure, QObject* parent) : QObject(parent), isFailure_(std::move(isFailure)) { + Q_ASSERT(!calls.isEmpty()); // 契约:至少一个 call(空 batch 永不发 succeeded,调用方会挂起) + Q_ASSERT(isFailure_); // 契约:必须提供失败谓词(否则首个 finished 即 bad_function_call) responses_.resize(calls.size()); remaining_ = static_cast(calls.size()); for (int i = 0; i < calls.size(); ++i) { @@ -12,7 +14,7 @@ ApiBatch::ApiBatch(QList calls, Predicate isFailure, QObject* parent) QObject::connect(c, &IApiCall::finished, this, [this, i](const ApiResponse& resp) { if (aborted_) return; // §5.0 入口守卫 if (isFailure_(resp)) { - aborted_ = true; + aborted_ = true; // 此后 remaining_ 不再维护:迟到 finished 全被入口守卫挡掉 for (const auto& other : calls_) { // fail-fast:abort 其余在飞 if (other) other->abort(); } diff --git a/tests/net/test_api_batch.cpp b/tests/net/test_api_batch.cpp index 8f3e9ab..566517c 100644 --- a/tests/net/test_api_batch.cpp +++ b/tests/net/test_api_batch.cpp @@ -26,6 +26,14 @@ TEST(ApiBatch, SucceedsWhenAllOk) { EXPECT_EQ(failSpy.count(), 0); } +TEST(ApiBatch, SucceedsWithSingleCall) { + auto* a = new FakeApiCall; + auto* batch = new ApiBatch({a}, isFailure); + QSignalSpy okSpy(batch, &ApiBatch::succeeded); + a->fire(ok()); // N=1:一次 fire 即触发 --remaining_==0 + EXPECT_EQ(okSpy.count(), 1); +} + TEST(ApiBatch, FailFastAbortsOthers) { auto* a = new FakeApiCall; auto* b = new FakeApiCall; // 慢的(永不 fire)