From 0725e1eff3e00e2984ce9e7f3a098bdcd754b474 Mon Sep 17 00:00:00 2001 From: sun-tribunal <2073909810@qq.com> Date: Wed, 2 Sep 2026 22:36:09 +0800 Subject: [PATCH 1/5] Add atomic Floor batch enqueue support Adds EnqueueBatch and Contains to Floor for atomic FIFO batch waiting-room inserts and duplicate checks. This keeps Floor queue semantics consistent across direction-specific queues, prevents partial batch inserts, and supports grouped passenger arrivals without changing existing APIs. Includes a focused Floor regression suite, project registration updates, and a test report documenting the A-part compliance and validation results. --- ElevatorSimulation/Core/Floor.cpp | 24 +++ ElevatorSimulation/Core/Floor.h | 7 + ElevatorSimulation/ElevatorSimulation.vcxproj | 1 + .../ElevatorSimulation.vcxproj.filters | 5 +- Tests/FloorTests.cpp | 81 ++++++++++ Tests/RunCoreTests.cmd | 4 +- docs/PartA_TestReport.md | 153 ++++++++++++++++++ 7 files changed, 272 insertions(+), 3 deletions(-) create mode 100644 Tests/FloorTests.cpp create mode 100644 docs/PartA_TestReport.md diff --git a/ElevatorSimulation/Core/Floor.cpp b/ElevatorSimulation/Core/Floor.cpp index 35d5acc..3f820f5 100644 --- a/ElevatorSimulation/Core/Floor.cpp +++ b/ElevatorSimulation/Core/Floor.cpp @@ -2,6 +2,7 @@ #include #include +#include Floor::Floor(int floorNumber) : m_floorNumber(floorNumber) { @@ -45,3 +46,26 @@ const std::deque& Floor::GetWaitingIds(Direction direction) const if (direction == Direction::Down) return m_downWaitingPassengers; throw std::invalid_argument("Waiting queue requires Up or Down"); } + +bool Floor::EnqueueBatch(const std::vector& ids, Direction direction) +{ + if (direction != Direction::Up && direction != Direction::Down) return false; + // 先校验全部 id,通过后再统一入队,保证整批成功或整批失败,不留下部分状态。 + std::unordered_set seen; + seen.reserve(ids.size()); + for (PassengerId id : ids) + { + if (id < 0 || Contains(id) || !seen.insert(id).second) return false; + } + auto& queue = direction == Direction::Up ? m_upWaitingPassengers : m_downWaitingPassengers; + queue.insert(queue.end(), ids.begin(), ids.end()); + return true; +} + +bool Floor::Contains(PassengerId id) const noexcept +{ + if (id < 0) return false; + for (const auto* queue : { &m_upWaitingPassengers, &m_downWaitingPassengers }) + if (std::find(queue->begin(), queue->end(), id) != queue->end()) return true; + return false; +} diff --git a/ElevatorSimulation/Core/Floor.h b/ElevatorSimulation/Core/Floor.h index 8e7b071..bbdb494 100644 --- a/ElevatorSimulation/Core/Floor.h +++ b/ElevatorSimulation/Core/Floor.h @@ -3,6 +3,7 @@ #include "CommonTypes.h" #include +#include class Floor { @@ -19,6 +20,12 @@ class Floor PassengerId Peek(Direction direction) const; const std::deque& GetWaitingIds(Direction direction) const; + // 批量交通输入:一次性把同一方向的整组乘客按 FIFO 顺序入队,供批量/分组到达场景使用。 + // 任一项 id 非法、批内重复或已在本层等待时整批失败,不做部分入队。 + bool EnqueueBatch(const std::vector& ids, Direction direction); + // 查询指定乘客是否正等待在本层任一方向队列中,用于去重与一致性校验。 + bool Contains(PassengerId id) const noexcept; + private: int m_floorNumber; std::deque m_upWaitingPassengers; diff --git a/ElevatorSimulation/ElevatorSimulation.vcxproj b/ElevatorSimulation/ElevatorSimulation.vcxproj index 636a860..08f143f 100644 --- a/ElevatorSimulation/ElevatorSimulation.vcxproj +++ b/ElevatorSimulation/ElevatorSimulation.vcxproj @@ -258,6 +258,7 @@ + diff --git a/ElevatorSimulation/ElevatorSimulation.vcxproj.filters b/ElevatorSimulation/ElevatorSimulation.vcxproj.filters index 7bb51b5..d91c7a2 100644 --- a/ElevatorSimulation/ElevatorSimulation.vcxproj.filters +++ b/ElevatorSimulation/ElevatorSimulation.vcxproj.filters @@ -1,4 +1,4 @@ - + @@ -140,6 +140,9 @@ Development + + Development + Development diff --git a/Tests/FloorTests.cpp b/Tests/FloorTests.cpp new file mode 100644 index 0000000..2f196c6 --- /dev/null +++ b/Tests/FloorTests.cpp @@ -0,0 +1,81 @@ +#include "Core/Floor.h" +#include "TestSupport.h" + +#include + +// A 部分(Floor)批量交通输入接口回归:EnqueueBatch 原子整组入队与 Contains 去重/一致性校验。 +int main() +{ + TestSuite tests("Floor"); + tests.Run("initial state empty", [&] { + Floor floor(1); + tests.Check(floor.GetUpWaitingCount() == 0 && floor.GetDownWaitingCount() == 0, "empty queues"); + tests.Check(!floor.Contains(0) && !floor.Contains(-1), "contains empty"); + }); + tests.Run("batch enqueue FIFO order", [&] { + Floor floor(3); + tests.Check(floor.EnqueueBatch({ 0, 1, 2 }, Direction::Up), "batch accepted"); + tests.Check(floor.GetUpWaitingCount() == 3 && floor.GetDownWaitingCount() == 0, "up count"); + const auto& up = floor.GetWaitingIds(Direction::Up); + tests.Check(up.size() == 3 && up[0] == 0 && up[1] == 1 && up[2] == 2, "order preserved"); + tests.Check(floor.Peek(Direction::Up) == 0, "peek head"); + tests.Check(floor.Peek(Direction::Down) == InvalidPassengerId, "empty down peek"); + }); + tests.Run("directions are independent queues", [&] { + Floor floor(3); + tests.Check(floor.EnqueueBatch({ 0, 1 }, Direction::Up), "up batch"); + tests.Check(floor.EnqueueBatch({ 2, 3 }, Direction::Down), "down batch"); + tests.Check(floor.GetUpWaitingCount() == 2 && floor.GetDownWaitingCount() == 2, "split counts"); + tests.Check(floor.GetWaitingIds(Direction::Up)[0] == 0, "up queue head"); + tests.Check(floor.GetWaitingIds(Direction::Down)[0] == 2, "down queue head"); + }); + tests.Run("batch duplicate rejected atomically", [&] { + Floor floor(1); + tests.Check(!floor.EnqueueBatch({ 4, 4 }, Direction::Up), "in-batch duplicate"); + tests.Check(floor.GetUpWaitingCount() == 0, "no partial enqueue"); + }); + tests.Run("already waiting id rejects batch", [&] { + Floor floor(1); + tests.Check(floor.Enqueue(5, Direction::Up), "seed single"); + tests.Check(!floor.EnqueueBatch({ 6, 5 }, Direction::Up), "waiting id rejects"); + tests.Check(floor.GetUpWaitingCount() == 1, "no partial enqueue"); + // 跨方向去重口径与单条 Enqueue 一致:同一 id 不能同时出现在上下行。 + tests.Check(!floor.EnqueueBatch({ 5 }, Direction::Down), "cross-direction duplicate"); + tests.Check(floor.GetDownWaitingCount() == 0, "down still empty"); + }); + tests.Run("invalid parameters rejected", [&] { + Floor floor(1); + tests.Check(!floor.EnqueueBatch({ 7, -1 }, Direction::Up), "negative id"); + tests.Check(!floor.EnqueueBatch({ 7 }, Direction::Idle), "idle direction"); + tests.Check(floor.GetUpWaitingCount() == 0, "no partial enqueue"); + }); + tests.Run("empty batch is harmless no-op", [&] { + Floor floor(1); + tests.Check(floor.EnqueueBatch({}, Direction::Up), "empty accepted"); + tests.Check(floor.GetUpWaitingCount() == 0, "unchanged"); + }); + tests.Run("append preserves existing FIFO", [&] { + Floor floor(1); + tests.Check(floor.EnqueueBatch({ 0, 1 }, Direction::Up), "first batch"); + tests.Check(floor.EnqueueBatch({ 2, 3 }, Direction::Up), "second batch"); + const auto& up = floor.GetWaitingIds(Direction::Up); + tests.Check(up.size() == 4 && up[0] == 0 && up[1] == 1 && up[2] == 2 && up[3] == 3, "global order"); + }); + tests.Run("contains tracks removal", [&] { + Floor floor(1); + tests.Check(floor.EnqueueBatch({ 0, 1, 2 }, Direction::Up), "seed batch"); + tests.Check(floor.Contains(0) && floor.Contains(1) && floor.Contains(2), "all contained"); + tests.Check(!floor.Contains(3) && !floor.Contains(-1), "absent ids"); + tests.Check(floor.RemoveFront(0, Direction::Up), "remove head"); + tests.Check(!floor.Contains(0), "removed id gone"); + tests.Check(floor.Contains(1), "remaining kept"); + }); + tests.Run("snapshot reflects batch counts", [&] { + Floor floor(3); + tests.Check(floor.EnqueueBatch({ 0, 1 }, Direction::Up) && floor.EnqueueBatch({ 2 }, Direction::Down), "seed batches"); + const auto snapshot = floor.GetSnapshot(); + tests.Check(snapshot.floorNumber == 3 && snapshot.upWaitingCount == 2 && snapshot.downWaitingCount == 1, + "snapshot counts"); + }); + return tests.Finish(); +} diff --git a/Tests/RunCoreTests.cmd b/Tests/RunCoreTests.cmd index 9a679a6..82f50ba 100644 --- a/Tests/RunCoreTests.cmd +++ b/Tests/RunCoreTests.cmd @@ -13,10 +13,10 @@ if errorlevel 1 exit /b 2 if not exist "%~dp0..\build\core-tests\%TEST_ARCH%" mkdir "%~dp0..\build\core-tests\%TEST_ARCH%" pushd "%~dp0..\build\core-tests\%TEST_ARCH%" if errorlevel 1 exit /b 2 -set "TEST_SUITES=Dispatcher Elevator Simulation" +set "TEST_SUITES=Dispatcher Elevator Simulation Floor" if not "%~1"=="" if /i not "%~1"=="All" ( set "TEST_SUITES=" - for %%s in (Dispatcher Elevator Simulation) do if /i "%~1"=="%%s" set "TEST_SUITES=%%s" + for %%s in (Dispatcher Elevator Simulation Floor) do if /i "%~1"=="%%s" set "TEST_SUITES=%%s" ) if not defined TEST_SUITES (popd & exit /b 2) for %%s in (%TEST_SUITES%) do ( diff --git a/docs/PartA_TestReport.md b/docs/PartA_TestReport.md new file mode 100644 index 0000000..90abd23 --- /dev/null +++ b/docs/PartA_TestReport.md @@ -0,0 +1,153 @@ +# A 部分(Passenger/Floor)合规与测试报告 + +- 生成日期:2026-09-02 +- 复验日期:2026-09-02(重跑 Floor 套件与冒烟测试,双架构) +- 被测版本:工作树 `A` 分支(`git diff` 仅含本轮 A 部分改动) +- 报告性质:交付物合规审计(命名规则 / 接口 / 公约)+ 测试执行结果 + README 模块状态核验 + +--- + +## 1. 报告概述 + +| 项 | 说明 | +| --- | --- | +| 被测模块 | `Core/Floor`(新增 `EnqueueBatch`、`Contains`)、`Core/Passenger`(未改动,随套件回归) | +| 交付物 | `Core/Floor.h/.cpp` 增量修改;`Tests/FloorTests.cpp` 新增;`Tests/RunCoreTests.cmd`、`ElevatorSimulation.vcxproj/.filters` 登记 | +| 依据规范 | `AGENTS.md` 全部规则、README.md 公共契约与编码规范 | +| 检验方法 | 源码逐项核对(git diff 全量审查)+ 双架构编译与测试实测 | + +--- + +## 2. 命名规则严格检验 + +依据 AGENTS:**PascalCase 类名/函数、camelCase 局部变量、`m_` 成员、中文注释、`enum class`、`nullptr`、RAII、STL、避免裸 new/delete**。 + +### 2.1 新增标识符逐项核对(Core/Floor) + +| 标识符 | 类型 | 规范要求 | 实际 | 判定 | +| --- | --- | --- | --- | --- | +| `EnqueueBatch` | 公有方法 | PascalCase | 匹配 | ✅ | +| `Contains` | 公有方法 | PascalCase | 匹配 | ✅ | +| `ids` | 参数 | camelCase | 匹配 | ✅ | +| `direction` | 参数 | camelCase | 匹配 | ✅ | +| `id` | 循环变量 | camelCase | 匹配 | ✅ | +| `seen` | 局部变量 | camelCase | 匹配 | ✅ | +| `queue` | 局部变量 | camelCase | 匹配 | ✅ | +| `m_upWaitingPassengers` / `m_downWaitingPassengers` | 既有成员 | `m_` 前缀 | 新增代码仅复用,未新增成员 | ✅ | +| 新增成员变量 | — | `m_` 前缀 | 无新增(0 个) | ✅ N/A | + +### 2.2 新增标识符逐项核对(Tests/FloorTests.cpp) + +| 标识符 | 类型 | 判定 | +| --- | --- | --- | +| `tests`、`floor`、`up`、`snapshot` | 局部变量 camelCase | ✅ | +| `TestSuite`、`Run`、`Check`、`Finish` | 既有框架 API | ✅ | + +### 2.3 代码风格核对 + +| 检查项 | 判定 | +| --- | --- | +| 中文注释(新增 3 处注释均为中文) | ✅ | +| `enum class`(Direction::Up/Down 用法) | ✅ | +| STL 容器(`vector`、`deque`、`unordered_set`、`find`) | ✅ | +| 无裸 `new`/`delete`、无 C 数组动态数据 | ✅ | +| `nullptr` 使用 | ✅(`GetWaitingIds` 抛出 `invalid_argument` 而非返回裸指针,沿用既有契约) | +| `noexcept` 标注(`Contains` 为只读且不抛) | ✅ 与既有 getter 风格一致 | + +**命名规则结论:全部通过,无违规。** + +--- + +## 3. 接口情况严格检验 + +### 3.1 既有接口完整性(git diff 实证) + +- `Core/Floor.h/.cpp` diff 为 **31 行纯新增、0 行删除**,以下既有公共接口逐字未改: + `GetFloorNumber`、`GetUpWaitingCount`、`GetDownWaitingCount`、`GetSnapshot`、`Enqueue`、`RemoveFront`、`Peek`、`GetWaitingIds`。 +- `Core/Passenger.*` **零改动**(9 个 getter、`MarkBoarded`/`MarkArrived`、`GetSnapshot`、构造函数全部保持原样)。 +- 唯一公共契约 `Core/CommonTypes.h` **零改动**,未重复定义任何枚举/结构/配置类型。 + +### 3.2 新增接口 + +| 接口 | 签名 | 语义 | 无副作用 | 快照/副本 | +| --- | --- | --- | --- | --- | +| `EnqueueBatch` | `bool(ids, direction)` | 原子整组 FIFO 入队 | ✅ 仅修改本层队列 | ✅ | +| `Contains` | `bool(id) noexcept` | 去重/一致性校验 | ✅ 只读 | ✅ | + +### 3.3 调用点核查 + +- 新增接口当前无外部调用(`rg` 全库确认),`Contains` 仅被 `EnqueueBatch` 内部使用——属**纯增量能力**,不影响任何既有调用路径。 +- 未新增快照类型、未暴露可写容器出口、未通过 `friend`/可写指针绕过模块接口。 + +**接口情况结论:既有接口零破坏,新增接口为纯增量且语义明确。** + +--- + +## 4. 公约(AGENTS.md)合规检验 + +| 编号 | 公约条款 | 判定 | 说明 | +| --- | --- | --- | --- | +| 1 | 优先保证 `ElevatorSimulation.sln` 可编译 | ✅ | 四配置 + 变更后重建均 0 错误 | +| 2 | Core 不包含 `pch.h`/`framework.h`/`afx*.h`/窗口头 | ✅ | 仅含 `CommonTypes.h` 与标准库 | +| 3 | `CommonTypes.h` 唯一公共定义 | ✅ | 未重复定义类型 | +| 4 | 楼层编号 1~L | ✅ | `Floor` 构造仍校验 `floorNumber>=1`;新增方法不引入楼层语义 | +| 5 | 初始化 N 分组、所有权规则 | ✅ | 未触碰初始化/所有权逻辑 | +| 6 | UI 只调公开接口、读快照副本 | ✅ | 未触碰 UI 与快照契约 | +| 7 | 修改尽量限于负责模块 | ✅ | 仅 Floor + 用户明确授权的 Tests/登记 | +| 8 | 不重构 MFC 自动生成代码 | ✅ | 未触碰 App/资源/PCH/framework | +| 9 | 新增核心 .cpp 需登记并禁用 PCH | ✅ N/A | 未新增核心 .cpp;新增测试 .cpp 按既有模式登记为 `None`+Development,不编入 MFC exe | +| 10 | 新文件 UTF-8、`/utf-8` | ✅ | 测试脚本已带 `/utf-8`;未动 `.rc` | +| 11 | PascalCase/camelCase/`m_`/中文注释/RAII/STL/`enum class`/`nullptr`/无裸 new/delete | ✅ | 见第 2 节逐项核对 | +| 12 | 公共接口修改前检查全部调用点、更新 README | ⚠️ **偏差** | 已核查调用点(无既有调用),但 README 接口表未同步更新(此前受"仅改 Floor/Passenger"约束,需授权补) | +| 13 | 未实现部分明确 TODO,不虚报占位/零统计 | ✅ | 功能真实实现并经测试;未宣称批量输入已接入 Simulation | +| 14 | 原 406 项冒烟检查不得删除 | ✅ | 冒烟仍为 406 项 × 双架构通过 | +| 15 | 模块职责边界(Tests 属 F) | ⚠️ **说明** | 本次新增测试属 F 范围,但由用户明确授权执行,不构成越权 | + +**公约合规结论:14/15 条通过;2 项需注明——README 未更新(待授权补),Tests 修改(已获授权)。无硬性违规。** + +--- + +## 5. 测试执行结果(功能证据) + +环境:MSVC v143、`/std:c++17 /W4 /WX /utf-8 /MDd`,x64/x86 双架构,独立编译 Core/Statistics、无 MFC。 + +| 套件 | x64 | x86 | +| --- | --- | --- | +| **Floor(新增)** | 10 场景 / 35 断言 / 0 失败 | 10 场景 / 35 断言 / 0 失败 | +| Dispatcher | 83 / 444 / 0 失败 | 83 / 444 / 0 失败 | +| Elevator | 26 / 87 / 0 失败 | 26 / 87 / 0 失败 | +| Simulation | 41 / 2149 / 0 失败 | 41 / 2149 / 0 失败 | +| 冒烟基线 | 406 项通过 | 406 项通过 | + +**本次复验(2026-09-02)**:`RunCoreTests.cmd Floor x64/x86` → 均 10 场景 / 35 断言 / 0 失败;`RunCoreSmokeTests.cmd [x86]` → 均 406 项通过。结果与上次全量一致。 + +Floor 套件覆盖:FIFO 顺序、方向独立、批内/跨方向去重原子性、非法参数、空批、追加顺序、`Contains` 生命周期、快照一致性。 + +--- + +## 6. README A 模块状态声明核验 + +README 状态行:`| A | Core/Passenger.*、Core/Floor.* | 已补齐状态转换、FIFO 与 ID 校验;后续可扩展交通输入方式 |` + +| 声明项 | 代码依据 | 测试证据 | 判定 | +| --- | --- | --- | --- | +| 状态转换 | [Passenger.cpp](file:///e:/360MoveData/Users/sun/Documents/GitHub/ElevatorSimulation/ElevatorSimulation/Core/Passenger.cpp):`MarkBoarded`(Waiting→Riding)、`MarkArrived`(Riding→Arrived),非法转换返回 false | Smoke:初始状态 Waiting、时间戳 UnsetTime、同层乘客抛 `invalid_argument`;SimulationTests:上梯 T 完成变 Riding、下梯完成 Arrived 并删除活动对象 | ✅ 已补齐 | +| FIFO | [Floor.cpp](file:///e:/360MoveData/Users/sun/Documents/GitHub/ElevatorSimulation/ElevatorSimulation/Core/Floor.cpp):上下行 `deque`,`Enqueue` 追加队尾、`RemoveFront` 弹出队头、`Peek` 返回队头 | FloorTests F2/F8:FIFO 顺序、多批追加保持全局顺序;SimulationTests:最老优先上梯 | ✅ 已补齐 | +| ID 校验 | Passenger 构造校验 `id>=0`、楼层合法、起终点不同、`requestTime` 有限;`Floor::Enqueue` 拒绝 `id<0` 与重复 ID | Smoke:同层/非法输入拒绝;FloorTests F4/F5/F6:批内重复、已在等待、负数均整批拒绝 | ✅ 已补齐 | +| 后续可扩展交通输入方式 | 已交付 `EnqueueBatch`(原子整组 FIFO 入队)+ `Contains`(去重/一致性校验) | FloorTests 10 场景 / 35 断言,双架构 0 失败 | ✅ **已扩展完成**(原"后续可扩展"现已实现) | + +**核验结论:README 前四项声明(状态转换、FIFO、ID 校验)全部属实;"后续可扩展交通输入方式"已由本轮交付完成。README 状态行建议更新为「已补齐状态转换、FIFO 与 ID 校验;已扩展批量交通输入」。** + +--- + +## 7. 结论 + +| 检查维度 | 结论 | +| --- | --- | +| 命名规则 | ✅ 全部通过,无违规 | +| 接口情况 | ✅ 既有接口零破坏;新增接口纯增量、语义明确 | +| 公约合规 | ✅ 14/15 通过;2 项需注明(README 未更新——待授权;Tests 修改——已授权) | +| 功能验证 | ✅ 双架构全量通过(160 场景/2715 断言 + 406 冒烟项);本次复验一致 | +| README 状态核验 | ✅ 状态转换、FIFO、ID 校验声明属实;"后续可扩展交通输入方式"已实现完成 | + +**需改进项**:README 接口行为表尚未补充 `EnqueueBatch`/`Contains`,且 A 模块状态行仍标注"后续可扩展交通输入方式"(建议改为「已补齐状态转换、FIFO 与 ID 校验;已扩展批量交通输入」)。该项为文档同步,建议授权后更新 README。 From 8e8a819f818d134b8b211f090c93d6c013cb60bc Mon Sep 17 00:00:00 2001 From: sun-tribunal <2073909810@qq.com> Date: Sun, 6 Sep 2026 17:09:03 +0800 Subject: [PATCH 2/5] feat: complete F statistics, system tests and reliability verification Statistics: add FormatSummary, per-elevator full-load count and working time as pure increments (existing methods and snapshot unchanged) Tests: add Statistics, System and Reliability suites; extend dispatch comparison harness to 8 scenarios (evening_peak/interfloor/saturation) Register new suites in RunCoreTests.cmd and project filters; add PartF and reliability verification reports with measured results --- ElevatorSimulation/ElevatorSimulation.vcxproj | 3 + .../ElevatorSimulation.vcxproj.filters | 9 + ElevatorSimulation/Statistics/Statistics.cpp | 60 +++- ElevatorSimulation/Statistics/Statistics.h | 12 + Tests/DispatchComparison.cpp | 45 ++- Tests/ReliabilityTests.cpp | 292 ++++++++++++++++++ Tests/RunCoreTests.cmd | 4 +- Tests/StatisticsTests.cpp | 178 +++++++++++ Tests/SystemTests.cpp | 223 +++++++++++++ docs/PartF_TestReport.md | 134 ++++++++ docs/Reliability_TestReport.md | 120 +++++++ 11 files changed, 1074 insertions(+), 6 deletions(-) create mode 100644 Tests/ReliabilityTests.cpp create mode 100644 Tests/StatisticsTests.cpp create mode 100644 Tests/SystemTests.cpp create mode 100644 docs/PartF_TestReport.md create mode 100644 docs/Reliability_TestReport.md diff --git a/ElevatorSimulation/ElevatorSimulation.vcxproj b/ElevatorSimulation/ElevatorSimulation.vcxproj index 08f143f..b6c5c00 100644 --- a/ElevatorSimulation/ElevatorSimulation.vcxproj +++ b/ElevatorSimulation/ElevatorSimulation.vcxproj @@ -259,6 +259,9 @@ + + + diff --git a/ElevatorSimulation/ElevatorSimulation.vcxproj.filters b/ElevatorSimulation/ElevatorSimulation.vcxproj.filters index d91c7a2..70eb651 100644 --- a/ElevatorSimulation/ElevatorSimulation.vcxproj.filters +++ b/ElevatorSimulation/ElevatorSimulation.vcxproj.filters @@ -143,6 +143,15 @@ Development + + Development + + + Development + + + Development + Development diff --git a/ElevatorSimulation/Statistics/Statistics.cpp b/ElevatorSimulation/Statistics/Statistics.cpp index b1c85eb..fce70c7 100644 --- a/ElevatorSimulation/Statistics/Statistics.cpp +++ b/ElevatorSimulation/Statistics/Statistics.cpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include void Statistics::Reset(int elevatorCount) { @@ -17,6 +19,10 @@ void Statistics::Reset(int elevatorCount) m_snapshot = std::move(snapshot); m_waitingTimeSum = 0.0; m_rideTimeSum = 0.0; + const std::size_t count = static_cast(elevatorCount); + m_fullLoadCounts.assign(count, std::size_t{ 0 }); + m_wasFull.assign(count, false); + m_workingTimes.assign(count, 0.0); } StatisticsSnapshot Statistics::GetSnapshot() const @@ -64,7 +70,57 @@ void Statistics::ElevatorMoved(int elevatorId, bool empty) void Statistics::ElevatorTimeElapsed(int elevatorId, double seconds, ElevatorState state, bool full) { if (!std::isfinite(seconds) || seconds < 0.0) throw std::invalid_argument("Invalid elapsed time"); - auto& elevator = m_snapshot.elevators.at(static_cast(elevatorId)); + const std::size_t index = static_cast(elevatorId); + auto& elevator = m_snapshot.elevators.at(index); if (state == ElevatorState::Idle) elevator.idleTime += seconds; - if (full) elevator.fullTime += seconds; + else m_workingTimes.at(index) += seconds; // 工作时间:非空闲状态累计。 + if (full) + { + elevator.fullTime += seconds; + // 满载次数按连续满载时段计数,避免同一时段逐帧重复累计。 + if (!m_wasFull.at(index)) + { + ++m_fullLoadCounts.at(index); + m_wasFull.at(index) = true; + } + } + else + m_wasFull.at(index) = false; +} + +std::size_t Statistics::GetFullLoadCount(int elevatorId) const +{ + return m_fullLoadCounts.at(static_cast(elevatorId)); +} + +double Statistics::GetWorkingTime(int elevatorId) const +{ + return m_workingTimes.at(static_cast(elevatorId)); +} + +std::string Statistics::FormatSummary() const +{ + std::ostringstream output; + output << std::fixed << std::setprecision(2); + output << "总乘客=" << m_snapshot.totalPassengerCount + << " 等待中=" << m_snapshot.waitingCount + << " 乘梯中=" << m_snapshot.ridingCount + << " 已到达=" << m_snapshot.arrivedCount + << " 平均等待=" << m_snapshot.averageWaitingTime << "s" + << " 最大等待=" << m_snapshot.maxWaitingTime << "s" + << " 平均乘梯=" << m_snapshot.averageRideTime << "s\n"; + for (const auto& elevator : m_snapshot.elevators) + { + // UI 显示 E1~EN,即 id + 1。 + const std::size_t index = static_cast(elevator.id); + output << "E" << elevator.id + 1 + << ": 送达=" << elevator.transportedCount + << " 移动=" << elevator.traveledFloors + << " 空驶=" << elevator.emptyTravelFloors + << " 满载=" << m_fullLoadCounts.at(index) << "次" + << " 满载时长=" << elevator.fullTime << "s" + << " 空闲=" << elevator.idleTime << "s" + << " 工作=" << m_workingTimes.at(index) << "s\n"; + } + return output.str(); } diff --git a/ElevatorSimulation/Statistics/Statistics.h b/ElevatorSimulation/Statistics/Statistics.h index 559d739..ce03294 100644 --- a/ElevatorSimulation/Statistics/Statistics.h +++ b/ElevatorSimulation/Statistics/Statistics.h @@ -2,6 +2,9 @@ #include "../Core/CommonTypes.h" +#include +#include + // 被 Simulation 组合,仅依赖 Common,不反向依赖 Simulation/UI。 class Statistics { @@ -15,8 +18,17 @@ class Statistics void ElevatorMoved(int elevatorId, bool empty); void ElevatorTimeElapsed(int elevatorId, double seconds, ElevatorState state, bool full); + // 只读文本汇总,供测试与后续 UI 展示;不改变统计状态。 + std::string FormatSummary() const; + // 单梯满载次数(连续满载时段计数)与工作时间(非空闲累计秒);越界抛 out_of_range。 + std::size_t GetFullLoadCount(int elevatorId) const; + double GetWorkingTime(int elevatorId) const; + private: StatisticsSnapshot m_snapshot; double m_waitingTimeSum = 0.0; double m_rideTimeSum = 0.0; + std::vector m_fullLoadCounts; + std::vector m_wasFull; + std::vector m_workingTimes; }; diff --git a/Tests/DispatchComparison.cpp b/Tests/DispatchComparison.cpp index ad4ecc9..ba16666 100644 --- a/Tests/DispatchComparison.cpp +++ b/Tests/DispatchComparison.cpp @@ -24,6 +24,16 @@ namespace config.moveTimePerFloor=0.3; config.personTime=0.2; config.passengerRate=8; config.simulationDuration=600; } + if(name=="evening_peak") + { + config.elevatorCount=6; config.capacity=4; + config.moveTimePerFloor=0.3; config.personTime=0.2; config.simulationDuration=600; + } + if(name=="interfloor" || name=="saturation") + { + config.elevatorCount=6; config.capacity=name=="saturation" ? 2 : 4; + config.moveTimePerFloor=0.3; config.personTime=0.2; config.simulationDuration=20000; + } Simulation simulation; if(!simulation.Initialize(config,321)) throw std::runtime_error("initialize failed"); if(name=="two_calls") @@ -40,6 +50,35 @@ namespace simulation.AddPassenger(start,(start-1+1+i%19)%20+1); } } + else if(name=="evening_peak") + { + // 晚高峰下行:偶数乘客从上层→1,奇数乘客从 1→上层。 + for(int i=0;i<300;++i) + { + const int floor=2+i%19; + if(i%2==0) simulation.AddPassenger(floor,1); + else simulation.AddPassenger(1,floor); + } + } + else if(name=="interfloor") + { + // 区间双向:在 5~16 层内错位起终点,均衡上行/下行。 + for(int i=0;i<300;++i) + { + const int start=5+i%12; + const int target=(start-4+i%11)%12+5; + simulation.AddPassenger(start,target); + } + } + else if(name=="saturation") + { + // 容量饱和:capacity=2,600 人有限批次,考查高积压下的送达与等待。 + for(int i=0;i<600;++i) + { + const int start=i%20+1; + simulation.AddPassenger(start,(start-1+1+i%19)%20+1); + } + } simulation.Start(); if(name=="new_detour") { @@ -56,14 +95,16 @@ int main() try { std::cout << "scenario,total,arrived,waiting,riding,mean_wait_s,completed_pickup_delay_s,elapsed_ms\n"; - for(const std::string name:{"two_calls","new_detour","finite90","batch2000","poisson321"}) + for(const std::string name:{"two_calls","new_detour","finite90","batch2000","poisson321", + "evening_peak","interfloor","saturation"}) { StatisticsSnapshot stats; const auto begin=std::chrono::steady_clock::now(); constexpr int repeats=3; for(int run=0;run(std::chrono::steady_clock::now()-begin).count()/repeats; - const double personTime=name=="finite90" || name=="batch2000" ? 0.25 : (name=="poisson321" ? 0.2 : 3.0); + const double personTime=name=="finite90" || name=="batch2000" ? 0.25 : + (name=="poisson321" || name=="evening_peak" || name=="interfloor" || name=="saturation" ? 0.2 : 3.0); // 已完成上梯者的响应延迟总和,扣掉其自身上梯 T;积压另列,不能当作全体均值。 const double response=(stats.averageWaitingTime-personTime)*stats.boardedCount; std::cout << std::fixed << std::setprecision(4) << name << ',' << stats.totalPassengerCount << ',' diff --git a/Tests/ReliabilityTests.cpp b/Tests/ReliabilityTests.cpp new file mode 100644 index 0000000..1a31c95 --- /dev/null +++ b/Tests/ReliabilityTests.cpp @@ -0,0 +1,292 @@ +#include "Core/Simulation.h" +#include "TestSupport.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +// 可靠性多维检验:功能 / 性能 / 边界 / 一致性 / 稳定性 / 可扩展性。 +namespace +{ + struct PerfRow + { + std::string dimension; + std::string label; + double wallMs = 0.0; + std::size_t delivered = 0; + }; + std::vector g_perf; + + SimulationConfig Config(int floorCount = 20, int elevatorCount = 6, int capacity = 4, + double rate = 0.0, double duration = 600.0) + { + SimulationConfig config; + config.floorCount = floorCount; + config.elevatorCount = elevatorCount; + config.capacity = capacity; + config.moveTimePerFloor = 1.0; + config.personTime = 1.0; + config.simulationDuration = duration; + config.passengerRate = rate; + return config; + } + + void InjectBatch(Simulation& simulation, std::size_t count) + { + const int floors = simulation.GetConfig().floorCount; + for (std::size_t i = 0; i < count; ++i) + { + const int start = static_cast(i % static_cast(floors)) + 1; + const int target = (start - 1 + 1 + + static_cast(i % static_cast(floors - 1))) % floors + 1; + if (simulation.AddPassenger(start, target) == InvalidPassengerId) + throw std::runtime_error("injection failed"); + } + } + + double RunWallMs(Simulation& simulation, double seconds) + { + const auto begin = std::chrono::steady_clock::now(); + simulation.Update(seconds); + return std::chrono::duration(std::chrono::steady_clock::now() - begin).count(); + } + + void PrintPerfTable() + { + std::cout << "[reliability perf] dimension,label,wall_ms,delivered\n"; + for (const auto& row : g_perf) + std::cout << std::fixed << std::setprecision(2) << row.dimension << ',' << row.label + << ',' << row.wallMs << ',' << row.delivered << '\n'; + } +} + +int main() +{ + TestSuite tests("Reliability"); + + // ---------- 1. 功能验证 ---------- + tests.Run("functional end-to-end matrix", [&] { + Simulation simulation; simulation.Initialize(Config(), 1); + const int trips[][2] = { { 1, 20 }, { 20, 1 }, { 5, 15 }, { 15, 5 }, { 3, 10 }, + { 10, 3 }, { 8, 12 }, { 12, 8 }, { 2, 19 }, { 19, 2 }, { 7, 14 }, { 14, 7 } }; + for (const auto& trip : trips) simulation.AddPassenger(trip[0], trip[1]); + simulation.Start(); simulation.Update(600); + const auto stats = simulation.GetStatisticsSnapshot(); + tests.Check(stats.totalPassengerCount == 12 && stats.arrivedCount == 12, "all twelve delivered"); + std::size_t transported = 0; + for (const auto& e : stats.elevators) transported += e.transportedCount; + tests.Check(transported == 12, "transport sums to total"); + tests.Check(simulation.GetHallCallSnapshots().empty() && simulation.GetPassengerSnapshots().empty(), + "no residual calls or passengers"); + tests.Check(simulation.ValidateState(), "consistent state"); + }); + + // ---------- 2. 性能评估 ---------- + tests.Run("performance light load", [&] { + Simulation simulation; simulation.Initialize(Config(20, 6, 4, 1.0, 300.0), 1); + simulation.Start(); + const double ms = RunWallMs(simulation, 300.0); + const auto stats = simulation.GetStatisticsSnapshot(); + g_perf.push_back({ "perf", "light_lambda1_300s", ms, stats.arrivedCount }); + tests.Check(stats.arrivedCount > 0, "light load delivered"); + }); + tests.Run("performance medium load", [&] { + Simulation simulation; simulation.Initialize(Config(20, 6, 4, 4.0, 300.0), 2); + simulation.Start(); + const double ms = RunWallMs(simulation, 300.0); + const auto stats = simulation.GetStatisticsSnapshot(); + g_perf.push_back({ "perf", "medium_lambda4_300s", ms, stats.arrivedCount }); + tests.Check(stats.arrivedCount > 0, "medium load delivered"); + }); + tests.Run("performance high load", [&] { + Simulation simulation; simulation.Initialize(Config(20, 6, 4, 8.0, 300.0), 3); + simulation.Start(); + const double ms = RunWallMs(simulation, 300.0); + const auto stats = simulation.GetStatisticsSnapshot(); + g_perf.push_back({ "perf", "high_lambda8_300s", ms, stats.arrivedCount }); + tests.Check(stats.arrivedCount > 0, "high load delivered"); + }); + tests.Run("performance bounded batch", [&] { + Simulation simulation; simulation.Initialize(Config(20, 6, 4, 0.0, 2000.0), 4); + InjectBatch(simulation, 600); + simulation.Start(); + const double ms = RunWallMs(simulation, 2000.0); + const auto stats = simulation.GetStatisticsSnapshot(); + g_perf.push_back({ "perf", "batch600", ms, stats.arrivedCount }); + tests.Check(stats.arrivedCount == 600, "batch fully delivered"); + }); + + // ---------- 3. 边界测试 ---------- + tests.Run("boundary minimum building", [&] { + Simulation simulation; simulation.Initialize(Config(2, 3, 2, 0.0, 60.0), 5); + simulation.AddPassenger(1, 2); simulation.AddPassenger(2, 1); + simulation.Start(); simulation.Update(120); + tests.Check(simulation.GetStatisticsSnapshot().arrivedCount == 2, "two-floor delivered"); + tests.Check(simulation.ValidateState(), "consistent state"); + }); + tests.Run("boundary large building", [&] { + Simulation simulation; simulation.Initialize(Config(200, 12, 4, 0.0, 300.0), 6); + simulation.AddPassenger(1, 200); simulation.AddPassenger(200, 1); simulation.AddPassenger(100, 50); + simulation.Start(); simulation.Update(600); + tests.Check(simulation.GetStatisticsSnapshot().arrivedCount == 3, "200-floor delivered"); + tests.Check(simulation.ValidateState(), "consistent state"); + }); + tests.Run("boundary extreme inputs rejected", [&] { + Simulation simulation; simulation.Initialize(Config(), 7); + tests.Check(simulation.AddPassenger(0, 5) == InvalidPassengerId, "floor zero rejected"); + tests.Check(simulation.AddPassenger(21, 5) == InvalidPassengerId, "above top rejected"); + tests.Check(simulation.AddPassenger(5, 5) == InvalidPassengerId, "same floor rejected"); + for (double value : { 0.0, -1.0, std::numeric_limits::quiet_NaN(), + std::numeric_limits::infinity() }) + simulation.Update(value); + tests.Check(simulation.GetCurrentTime() == 0.0, "invalid deltas ignored"); + bool rejected = false; + auto bad = Config(); bad.floorCount = 1; + if (!simulation.Initialize(bad)) rejected = true; + tests.Check(rejected, "invalid config rejected"); + tests.Check(simulation.GetState() == SimulationState::Ready && simulation.GetCurrentTime() == 0.0, + "failed init preserves state"); + }); + tests.Run("boundary capacity one", [&] { + Simulation simulation; simulation.Initialize(Config(20, 6, 1, 0.0, 300.0), 8); + simulation.AddPassenger(1, 3); simulation.AddPassenger(1, 4); simulation.AddPassenger(1, 5); + simulation.Start(); simulation.Update(300); + const auto stats = simulation.GetStatisticsSnapshot(); + tests.Check(stats.arrivedCount == 3, "capacity one delivered"); + double fullTime = 0.0; + for (const auto& e : stats.elevators) fullTime += e.fullTime; + tests.Check(fullTime > 0.0, "full load recorded at capacity one"); + }); + tests.Run("boundary zero rate", [&] { + Simulation simulation; simulation.Initialize(Config(20, 6, 4, 0.0, 600.0), 9); + simulation.Start(); simulation.Update(700); + tests.Check(simulation.GetStatisticsSnapshot().totalPassengerCount == 0, "no random passengers"); + }); + + // ---------- 4. 一致性检查 ---------- + tests.Run("consistency seed determinism", [&] { + Simulation a, b; + a.Initialize(Config(20, 6, 4, 2.0, 300.0), 12345); + b.Initialize(Config(20, 6, 4, 2.0, 300.0), 12345); + a.Start(); b.Start(); + a.Update(300.0); b.Update(300.0); + const auto sa = a.GetStatisticsSnapshot(), sb = b.GetStatisticsSnapshot(); + tests.Check(sa.totalPassengerCount == sb.totalPassengerCount && + sa.arrivedCount == sb.arrivedCount && sa.waitingCount == sb.waitingCount && + sa.ridingCount == sb.ridingCount, "deterministic counts"); + tests.Check(sa.averageWaitingTime == sb.averageWaitingTime && + sa.maxWaitingTime == sb.maxWaitingTime && sa.averageRideTime == sb.averageRideTime, + "deterministic means"); + tests.Check(a.ValidateState() && b.ValidateState(), "both consistent"); + }); + tests.Run("consistency frame partition invariance", [&] { + Simulation a, b; + a.Initialize(Config(20, 6, 4, 2.0, 60.0), 42); + b.Initialize(Config(20, 6, 4, 2.0, 60.0), 42); + a.Start(); b.Start(); + for (int i = 0; i < 60; ++i) a.Update(1.0); + b.Update(60.0); + const auto sa = a.GetStatisticsSnapshot(), sb = b.GetStatisticsSnapshot(); + tests.Check(sa.totalPassengerCount == sb.totalPassengerCount && sa.arrivedCount == sb.arrivedCount && + sa.averageWaitingTime == sb.averageWaitingTime && sa.averageRideTime == sb.averageRideTime, + "frame partition identical"); + }); + tests.Run("consistency conservation identities", [&] { + Simulation simulation; simulation.Initialize(Config(20, 6, 4, 1.5, 120.0), 7); + simulation.Start(); simulation.Update(120.0); + const auto stats = simulation.GetStatisticsSnapshot(); + tests.Check(stats.totalPassengerCount == stats.waitingCount + stats.ridingCount + stats.arrivedCount, + "population conservation"); + tests.Check(stats.boardedCount == stats.ridingCount + stats.arrivedCount, "boarded identity"); + tests.Check(simulation.ValidateState(), "consistent state"); + }); + tests.Run("consistency theoretical waiting equals T", [&] { + auto config = Config(20, 3, 4, 0.0, 60.0); + config.moveTimePerFloor = 1.0; config.personTime = 1.0; + Simulation simulation; simulation.Initialize(config, 3); + simulation.AddPassenger(1, 2); + simulation.Start(); simulation.Update(60.0); + tests.Near(simulation.GetStatisticsSnapshot().averageWaitingTime, 1.0, "wait equals boarding T"); + }); + tests.Run("consistency theoretical ride equals travel plus alight T", [&] { + auto config = Config(20, 3, 4, 0.0, 60.0); + config.moveTimePerFloor = 1.0; config.personTime = 1.0; + Simulation simulation; simulation.Initialize(config, 4); + simulation.AddPassenger(1, 3); + simulation.Start(); simulation.Update(60.0); + tests.Near(simulation.GetStatisticsSnapshot().averageRideTime, 3.0, "ride equals two floors plus alight T"); + }); + + // ---------- 5. 稳定性测试 ---------- + tests.Run("stability one hour run", [&] { + Simulation simulation; simulation.Initialize(Config(20, 6, 4, 0.5, 3600.0), 987); + simulation.Start(); simulation.Update(3600.0); + const auto stats = simulation.GetStatisticsSnapshot(); + tests.Check(simulation.IsFinished() && stats.totalPassengerCount > 0 && stats.arrivedCount > 0, + "hour completed with delivery"); + tests.Check(stats.totalPassengerCount == stats.waitingCount + stats.ridingCount + stats.arrivedCount, + "conservation after hour"); + tests.Check(simulation.ValidateState(), "consistent state"); + }); + tests.Run("stability reset repeat", [&] { + Simulation simulation; simulation.Initialize(Config(20, 6, 4, 2.0, 120.0), 55); + simulation.Start(); simulation.Update(120.0); + const auto first = simulation.GetStatisticsSnapshot(); + simulation.Reset(); + tests.Check(simulation.GetRandomSeed() == 55, "seed preserved across reset"); + simulation.Start(); simulation.Update(120.0); + const auto second = simulation.GetStatisticsSnapshot(); + tests.Check(first.totalPassengerCount == second.totalPassengerCount && + first.arrivedCount == second.arrivedCount && + first.averageWaitingTime == second.averageWaitingTime, "repeat identical after reset"); + }); + + // ---------- 6. 可扩展性评估 ---------- + tests.Run("scalability elevator count", [&] { + for (int n : { 3, 6, 12, 24 }) + { + Simulation simulation; simulation.Initialize(Config(20, n, 4, 0.0, 600.0), 11); + InjectBatch(simulation, 80); + simulation.Start(); + const double ms = RunWallMs(simulation, 600.0); + const auto stats = simulation.GetStatisticsSnapshot(); + g_perf.push_back({ "scale", "elevators=" + std::to_string(n), ms, stats.arrivedCount }); + tests.Check(stats.arrivedCount == 80, "all delivered"); + tests.Check(simulation.GetElevatorSnapshots().size() == static_cast(n), + "elevator count matches"); + tests.Check(simulation.ValidateState(), "consistent state"); + } + }); + tests.Run("scalability floor count", [&] { + for (int floors : { 10, 20, 40, 80 }) + { + Simulation simulation; simulation.Initialize(Config(floors, 6, 4, 0.0, 600.0), 12); + InjectBatch(simulation, 80); + simulation.Start(); + const double ms = RunWallMs(simulation, 600.0); + const auto stats = simulation.GetStatisticsSnapshot(); + g_perf.push_back({ "scale", "floors=" + std::to_string(floors), ms, stats.arrivedCount }); + tests.Check(stats.arrivedCount == 80, "all delivered"); + tests.Check(simulation.ValidateState(), "consistent state"); + } + }); + tests.Run("scalability initial layout at scale", [&] { + Simulation simulation; simulation.Initialize(Config(40, 24, 4, 0.0, 60.0), 13); + const auto elevators = simulation.GetElevatorSnapshots(); + tests.Check(elevators.size() == 24, "twenty four elevators"); + for (int id = 0; id < 24; ++id) + { + const int expected = id < 8 ? 1 : (id < 16 ? 40 : 20); + tests.Check(elevators[static_cast(id)].currentFloor == expected, "group placement"); + } + }); + + const int result = tests.Finish(); + PrintPerfTable(); + return result; +} diff --git a/Tests/RunCoreTests.cmd b/Tests/RunCoreTests.cmd index 82f50ba..af055eb 100644 --- a/Tests/RunCoreTests.cmd +++ b/Tests/RunCoreTests.cmd @@ -13,10 +13,10 @@ if errorlevel 1 exit /b 2 if not exist "%~dp0..\build\core-tests\%TEST_ARCH%" mkdir "%~dp0..\build\core-tests\%TEST_ARCH%" pushd "%~dp0..\build\core-tests\%TEST_ARCH%" if errorlevel 1 exit /b 2 -set "TEST_SUITES=Dispatcher Elevator Simulation Floor" +set "TEST_SUITES=Dispatcher Elevator Simulation Floor Statistics System Reliability" if not "%~1"=="" if /i not "%~1"=="All" ( set "TEST_SUITES=" - for %%s in (Dispatcher Elevator Simulation Floor) do if /i "%~1"=="%%s" set "TEST_SUITES=%%s" + for %%s in (Dispatcher Elevator Simulation Floor Statistics System Reliability) do if /i "%~1"=="%%s" set "TEST_SUITES=%%s" ) if not defined TEST_SUITES (popd & exit /b 2) for %%s in (%TEST_SUITES%) do ( diff --git a/Tests/StatisticsTests.cpp b/Tests/StatisticsTests.cpp new file mode 100644 index 0000000..76bc590 --- /dev/null +++ b/Tests/StatisticsTests.cpp @@ -0,0 +1,178 @@ +#include "Statistics/Statistics.h" +#include "TestSupport.h" + +#include +#include +#include + +// F 部分(Statistics)单元回归:事件计数、统计口径、非法事件与零样本边界。 +int main() +{ + TestSuite tests("Statistics"); + tests.Run("reset creates per-elevator slots", [&] { + Statistics statistics; statistics.Reset(3); + const auto snapshot = statistics.GetSnapshot(); + tests.Check(snapshot.elevators.size() == 3, "three slots"); + tests.Check(snapshot.elevators[0].id == 0 && snapshot.elevators[2].id == 2, "ids 0..N-1"); + tests.Check(snapshot.totalPassengerCount == 0 && snapshot.arrivedCount == 0 && + snapshot.boardedCount == 0, "empty counts"); + }); + tests.Run("reset zero elevators allowed", [&] { + Statistics statistics; statistics.Reset(0); + tests.Check(statistics.GetSnapshot().elevators.empty(), "no slots"); + }); + tests.Run("negative elevator count rejected", [&] { + Statistics statistics; bool rejected = false; + try { statistics.Reset(-1); } + catch (const std::invalid_argument&) { rejected = true; } + tests.Check(rejected, "invalid argument"); + }); + tests.Run("created increments total and waiting", [&] { + Statistics statistics; statistics.Reset(1); + statistics.PassengerCreated(); + const auto snapshot = statistics.GetSnapshot(); + tests.Check(snapshot.totalPassengerCount == 1 && snapshot.waitingCount == 1, "created counts"); + }); + tests.Run("boarded moves waiting to riding with T-inclusive mean", [&] { + Statistics statistics; statistics.Reset(1); + statistics.PassengerCreated(); + statistics.PassengerBoarded(5.0); + const auto snapshot = statistics.GetSnapshot(); + tests.Check(snapshot.waitingCount == 0 && snapshot.ridingCount == 1 && snapshot.boardedCount == 1, + "transferred"); + tests.Near(snapshot.averageWaitingTime, 5.0, "mean includes T"); + tests.Near(snapshot.maxWaitingTime, 5.0, "max updated"); + }); + tests.Run("boarded updates running mean and max", [&] { + Statistics statistics; statistics.Reset(1); + statistics.PassengerCreated(); statistics.PassengerBoarded(2.0); + statistics.PassengerCreated(); statistics.PassengerBoarded(8.0); + const auto snapshot = statistics.GetSnapshot(); + tests.Near(snapshot.averageWaitingTime, 5.0, "running mean"); + tests.Near(snapshot.maxWaitingTime, 8.0, "running max"); + }); + tests.Run("invalid boarded events rejected", [&] { + Statistics statistics; statistics.Reset(1); + bool rejected = false; + try { statistics.PassengerBoarded(1.0); } + catch (const std::logic_error&) { rejected = true; } + tests.Check(rejected, "no waiting passenger"); + statistics.PassengerCreated(); + for (double value : { -1.0, std::numeric_limits::quiet_NaN(), + std::numeric_limits::infinity() }) + { + bool invalid = false; + try { statistics.PassengerBoarded(value); } + catch (const std::logic_error&) { invalid = true; } + tests.Check(invalid, "invalid time rejected"); + } + }); + tests.Run("arrived moves riding to arrived with per-elevator count", [&] { + Statistics statistics; statistics.Reset(2); + statistics.PassengerCreated(); statistics.PassengerBoarded(3.0); + statistics.PassengerArrived(1, 7.0); + const auto snapshot = statistics.GetSnapshot(); + tests.Check(snapshot.ridingCount == 0 && snapshot.arrivedCount == 1, "arrived"); + tests.Check(snapshot.elevators[1].transportedCount == 1 && snapshot.elevators[0].transportedCount == 0, + "per elevator"); + tests.Near(snapshot.averageRideTime, 7.0, "mean ride includes T"); + }); + tests.Run("invalid arrived events rejected", [&] { + Statistics statistics; statistics.Reset(1); + bool rejected = false; + try { statistics.PassengerArrived(0, 1.0); } + catch (const std::logic_error&) { rejected = true; } + tests.Check(rejected, "no riding passenger"); + statistics.PassengerCreated(); statistics.PassengerBoarded(1.0); + for (double value : { -1.0, std::numeric_limits::quiet_NaN(), + std::numeric_limits::infinity() }) + { + bool invalid = false; + try { statistics.PassengerArrived(0, value); } + catch (const std::logic_error&) { invalid = true; } + tests.Check(invalid, "invalid ride time rejected"); + } + bool missing = false; + try { statistics.PassengerArrived(5, 1.0); } + catch (const std::out_of_range&) { missing = true; } + tests.Check(missing, "invalid elevator id rejected"); + }); + tests.Run("moved accumulates traveled and empty floors", [&] { + Statistics statistics; statistics.Reset(1); + statistics.ElevatorMoved(0, false); + statistics.ElevatorMoved(0, true); + const auto snapshot = statistics.GetSnapshot(); + tests.Check(snapshot.elevators[0].traveledFloors == 2, "traveled"); + tests.Check(snapshot.elevators[0].emptyTravelFloors == 1, "empty"); + }); + tests.Run("elapsed accumulates idle and full time", [&] { + Statistics statistics; statistics.Reset(1); + statistics.ElevatorTimeElapsed(0, 3.0, ElevatorState::Idle, false); + statistics.ElevatorTimeElapsed(0, 2.0, ElevatorState::MovingUp, true); + const auto snapshot = statistics.GetSnapshot(); + tests.Near(snapshot.elevators[0].idleTime, 3.0, "idle"); + tests.Near(snapshot.elevators[0].fullTime, 2.0, "full"); + bool invalid = false; + try { statistics.ElevatorTimeElapsed(0, -1.0, ElevatorState::Idle, false); } + catch (const std::invalid_argument&) { invalid = true; } + tests.Check(invalid, "negative seconds rejected"); + }); + tests.Run("invariants total equals waiting plus riding plus arrived", [&] { + Statistics statistics; statistics.Reset(2); + for (int i = 0; i < 3; ++i) statistics.PassengerCreated(); + statistics.PassengerBoarded(1.0); + statistics.PassengerBoarded(2.0); + statistics.PassengerArrived(0, 3.0); + const auto snapshot = statistics.GetSnapshot(); + tests.Check(snapshot.totalPassengerCount == 3 && snapshot.waitingCount == 1 && + snapshot.ridingCount == 1 && snapshot.arrivedCount == 1, "split counts"); + tests.Check(snapshot.totalPassengerCount == snapshot.waitingCount + snapshot.ridingCount + snapshot.arrivedCount, + "conservation"); + tests.Check(snapshot.boardedCount == snapshot.ridingCount + snapshot.arrivedCount, "boarded identity"); + }); + tests.Run("zero-sample averages stay zero", [&] { + Statistics statistics; statistics.Reset(1); + const auto snapshot = statistics.GetSnapshot(); + tests.Check(snapshot.averageWaitingTime == 0.0 && snapshot.averageRideTime == 0.0 && + snapshot.maxWaitingTime == 0.0, "no NaN before samples"); + }); + tests.Run("snapshot copy isolation", [&] { + Statistics statistics; statistics.Reset(1); + auto snapshot = statistics.GetSnapshot(); + snapshot.totalPassengerCount = 999; snapshot.elevators[0].transportedCount = 999; + const auto after = statistics.GetSnapshot(); + tests.Check(after.totalPassengerCount == 0 && after.elevators[0].transportedCount == 0, "copy isolated"); + }); + tests.Run("elapsed tracks working time and full episodes", [&] { + Statistics statistics; statistics.Reset(1); + statistics.ElevatorTimeElapsed(0, 2.0, ElevatorState::MovingUp, false); + statistics.ElevatorTimeElapsed(0, 3.0, ElevatorState::MovingUp, true); + statistics.ElevatorTimeElapsed(0, 1.0, ElevatorState::MovingUp, true); + statistics.ElevatorTimeElapsed(0, 4.0, ElevatorState::Idle, false); + tests.Near(statistics.GetWorkingTime(0), 6.0, "working time excludes idle"); + tests.Check(statistics.GetFullLoadCount(0) == 1, "one contiguous full episode"); + tests.Near(statistics.GetSnapshot().elevators[0].fullTime, 4.0, "full seconds"); + statistics.ElevatorTimeElapsed(0, 1.0, ElevatorState::MovingUp, false); + statistics.ElevatorTimeElapsed(0, 1.0, ElevatorState::MovingUp, true); + tests.Check(statistics.GetFullLoadCount(0) == 2, "second full episode counted"); + tests.Near(statistics.GetWorkingTime(0), 8.0, "working time keeps accumulating"); + }); + tests.Run("full load getters bounds check", [&] { + Statistics statistics; statistics.Reset(1); + bool missing = false; + try { statistics.GetFullLoadCount(5); } + catch (const std::out_of_range&) { missing = true; } + tests.Check(missing, "invalid elevator id rejected"); + }); + tests.Run("format summary contains key fields", [&] { + Statistics statistics; statistics.Reset(2); + statistics.PassengerCreated(); statistics.PassengerBoarded(4.0); + const std::string summary = statistics.FormatSummary(); + tests.Check(summary.find("总乘客=1") != std::string::npos, "total"); + tests.Check(summary.find("平均等待=4.00s") != std::string::npos, "mean wait"); + tests.Check(summary.find("E1:") != std::string::npos && summary.find("E2:") != std::string::npos, + "per elevator lines"); + tests.Check(summary.find("送达=") != std::string::npos, "transported field"); + }); + return tests.Finish(); +} diff --git a/Tests/SystemTests.cpp b/Tests/SystemTests.cpp new file mode 100644 index 0000000..3265138 --- /dev/null +++ b/Tests/SystemTests.cpp @@ -0,0 +1,223 @@ +#include "Core/Simulation.h" +#include "Core/Elevator.h" +#include "Core/Dispatcher.h" +#include "TestSupport.h" + +#include +#include + +// F 部分系统测试:典型情况(无乘客~长时间运行)、客流(低/正常/高)、 +// 异常检查(超载、错误掉头、重复分配、目标层错误、等待乘客丢失)与统计字段。 +namespace +{ + SimulationConfig Config(int capacity = 4) + { + SimulationConfig config; + config.floorCount = 20; + config.elevatorCount = 6; + config.capacity = capacity; + config.moveTimePerFloor = 1.0; + config.personTime = 1.0; + config.simulationDuration = 600.0; + config.passengerRate = 0.0; + return config; + } + + bool IsFinite(double value) + { + return std::isfinite(value); + } +} + +int main() +{ + TestSuite tests("System"); + + // ---------- 典型情况 ---------- + tests.Run("no passengers", [&] { + Simulation simulation; simulation.Initialize(Config(), 1); + simulation.Start(); simulation.Update(700); // 截断到 600 秒总时长。 + const auto stats = simulation.GetStatisticsSnapshot(); + tests.Check(simulation.GetCurrentTime() == 600.0 && simulation.IsFinished(), "finished at duration"); + tests.Check(stats.totalPassengerCount == 0 && stats.arrivedCount == 0 && + stats.waitingCount == 0 && stats.ridingCount == 0, "empty statistics"); + tests.Check(stats.averageWaitingTime == 0.0 && stats.averageRideTime == 0.0 && + stats.maxWaitingTime == 0.0, "zero means"); + tests.Check(simulation.ValidateState(), "consistent state"); + }); + tests.Run("single passenger", [&] { + Simulation simulation; simulation.Initialize(Config(), 1); + simulation.AddPassenger(1, 3); simulation.Start(); simulation.Update(300); + const auto stats = simulation.GetStatisticsSnapshot(); + tests.Check(stats.totalPassengerCount == 1 && stats.arrivedCount == 1 && + stats.waitingCount == 0 && stats.ridingCount == 0, "single delivered"); + tests.Check(IsFinite(stats.averageWaitingTime) && stats.averageWaitingTime >= 1.0, "wait includes T"); + tests.Check(simulation.ValidateState(), "consistent state"); + }); + tests.Run("multiple passengers", [&] { + Simulation simulation; simulation.Initialize(Config(), 1); + const int trips[][2] = { { 1, 20 }, { 20, 1 }, { 5, 15 }, { 15, 5 }, { 3, 10 }, + { 10, 3 }, { 8, 12 }, { 12, 8 }, { 2, 19 }, { 19, 2 } }; + for (const auto& trip : trips) simulation.AddPassenger(trip[0], trip[1]); + simulation.Start(); simulation.Update(400); + const auto stats = simulation.GetStatisticsSnapshot(); + tests.Check(stats.totalPassengerCount == 10 && stats.arrivedCount == 10, "all ten delivered"); + std::size_t transported = 0; + for (const auto& elevator : stats.elevators) transported += elevator.transportedCount; + tests.Check(transported == 10, "per-elevator transport sums to total"); + tests.Check(simulation.ValidateState(), "consistent state"); + }); + tests.Run("full elevator transports", [&] { + auto config = Config(1); // 满载:容量 1。 + config.moveTimePerFloor = 0.5; config.personTime = 0.5; + Simulation simulation; simulation.Initialize(config, 1); + simulation.AddPassenger(1, 3); simulation.AddPassenger(1, 4); simulation.AddPassenger(1, 5); + simulation.Start(); simulation.Update(300); + const auto stats = simulation.GetStatisticsSnapshot(); + tests.Check(stats.totalPassengerCount == 3 && stats.arrivedCount == 3, "all delivered at capacity one"); + double fullTime = 0.0; + for (const auto& elevator : stats.elevators) fullTime += elevator.fullTime; + tests.Check(fullTime > 0.0, "full-load seconds recorded"); + tests.Check(simulation.ValidateState(), "consistent state"); + }); + tests.Run("multiple idle elevators", [&] { + Simulation simulation; simulation.Initialize(Config(), 1); + const auto elevators = simulation.GetElevatorSnapshots(); + tests.Check(elevators.size() == 6, "six elevators"); + bool allIdle = true; + for (const auto& elevator : elevators) + allIdle = allIdle && elevator.state == ElevatorState::Idle && elevator.direction == Direction::Idle; + tests.Check(allIdle, "all idle with no traffic"); + }); + tests.Run("opposite direction requests", [&] { + Simulation simulation; simulation.Initialize(Config(), 1); + simulation.AddPassenger(1, 20); simulation.AddPassenger(20, 1); + simulation.Start(); simulation.Update(400); + const auto stats = simulation.GetStatisticsSnapshot(); + tests.Check(stats.totalPassengerCount == 2 && stats.arrivedCount == 2, "both delivered"); + tests.Check(simulation.ValidateState(), "consistent state"); + }); + tests.Run("top and bottom floor requests", [&] { + Simulation simulation; simulation.Initialize(Config(), 1); + simulation.AddPassenger(20, 19); // 顶层下行 + simulation.AddPassenger(1, 2); // 底层上行 + simulation.Start(); simulation.Update(400); + const auto stats = simulation.GetStatisticsSnapshot(); + tests.Check(stats.totalPassengerCount == 2 && stats.arrivedCount == 2, "both delivered"); + // 非法方向请求:顶层向上、底层向下不可服务,调度器返回未分配。 + const ElevatorDispatcher dispatcher; + const std::vector oneCar{ Elevator(0, 1, Config()) }; + tests.Check(dispatcher.SelectElevator(20, Direction::Up, oneCar) == InvalidElevatorId, "top up invalid"); + tests.Check(dispatcher.SelectElevator(1, Direction::Down, oneCar) == InvalidElevatorId, "bottom down invalid"); + tests.Check(simulation.ValidateState(), "consistent state"); + }); + tests.Run("long duration run", [&] { + auto config = Config(); config.passengerRate = 0.3; config.simulationDuration = 3600.0; + Simulation simulation; simulation.Initialize(config, 987); + simulation.Start(); simulation.Update(4000); + const auto stats = simulation.GetStatisticsSnapshot(); + tests.Check(simulation.GetCurrentTime() == 3600.0 && simulation.IsFinished(), "finished at 3600s"); + tests.Check(stats.totalPassengerCount > 0 && stats.arrivedCount > 0, "long run generated and delivered"); + tests.Check(IsFinite(stats.averageWaitingTime) && IsFinite(stats.averageRideTime), "means finite"); + tests.Check(simulation.ValidateState(), "consistent state"); + }); + + // ---------- 客流:低 / 正常 / 高 ---------- + tests.Run("low flow traffic", [&] { + auto config = Config(); config.passengerRate = 0.05; config.simulationDuration = 3600.0; + Simulation simulation; simulation.Initialize(config, 321); + simulation.Start(); simulation.Update(4000); + const auto stats = simulation.GetStatisticsSnapshot(); + tests.Check(stats.totalPassengerCount > 0 && stats.arrivedCount > 0, "low flow runs"); + tests.Check(stats.totalPassengerCount < 500, "low flow bounded"); + tests.Check(simulation.ValidateState(), "consistent state"); + }); + tests.Run("normal flow traffic", [&] { + auto config = Config(); config.passengerRate = 0.6; + Simulation simulation; simulation.Initialize(config, 987); + simulation.Start(); simulation.Update(700); + const auto stats = simulation.GetStatisticsSnapshot(); + tests.Check(stats.totalPassengerCount > 0 && stats.arrivedCount > 0, "normal flow runs"); + tests.Check(IsFinite(stats.averageWaitingTime), "finite mean wait"); + tests.Check(simulation.ValidateState(), "consistent state"); + }); + tests.Run("high flow traffic", [&] { + auto config = Config(); config.passengerRate = 8.0; + Simulation simulation; simulation.Initialize(config, 321); + simulation.Start(); simulation.Update(700); + const auto stats = simulation.GetStatisticsSnapshot(); + tests.Check(stats.totalPassengerCount > 1000, "high flow volume"); + tests.Check(stats.arrivedCount > 0 && IsFinite(stats.averageWaitingTime), "delivered with finite mean"); + tests.Check(simulation.ValidateState(), "consistent state"); + }); + + // ---------- 异常情况检查 ---------- + tests.Run("overload prevented", [&] { + Elevator car(0, 1, Config(1)); // 满载:容量 1。 + car.AddHallCall(1, Direction::Up); + tests.Check(car.BeginBoarding(1, 3), "first seat"); + car.Advance(1.0); // 完成上梯传送后轿厢满载。 + tests.Check(!car.CanBoard() && !car.BeginBoarding(2, 4), "overload prevented at capacity"); + }); + tests.Run("wrong turn prevented", [&] { + Elevator car(0, 5, Config()); + car.AddInternalTarget(8); car.AddInternalTarget(12); + car.Advance(0.5); car.AddHallCall(3, Direction::Up); + tests.Check(car.GetSnapshot().direction == Direction::Up, "direction locked while tasks ahead"); + }); + tests.Run("duplicate assignment deduplicated", [&] { + Simulation simulation; simulation.Initialize(Config(), 1); + simulation.AddPassenger(1, 5); simulation.AddPassenger(1, 6); simulation.AddPassenger(1, 7); + simulation.Start(); simulation.Update(1.0); + const auto calls = simulation.GetHallCallSnapshots(); + // 1 秒后首名乘客可能已完成上梯,队列人数为 2~3;外呼仍按 (楼层,方向) 唯一。 + tests.Check(calls.size() == 1 && calls[0].floorNumber == 1 && + calls[0].direction == Direction::Up && calls[0].waitingCount >= 2, "one hall call per key"); + tests.Check(calls[0].assignedElevatorId != InvalidElevatorId, "uniquely assigned"); + Elevator car(0, 1, Config()); + car.AddHallCall(3, Direction::Up); car.AddHallCall(3, Direction::Up); + tests.Check(car.GetUpTasks().count(3) == 1, "single elevator task dedup"); + }); + tests.Run("wrong target floor rejected", [&] { + Elevator car(0, 5, Config()); + car.AddHallCall(5, Direction::Up); + tests.Check(!car.BeginBoarding(7, 3), "opposite direction destination rejected"); + tests.Check(!car.AddInternalTarget(21) && !car.AddInternalTarget(0), "target out of bounds rejected"); + }); + tests.Run("no waiting passenger lost", [&] { + auto config = Config(); config.moveTimePerFloor = 1.0; config.personTime = 1.0; + Simulation simulation; simulation.Initialize(config, 1); + simulation.AddPassenger(1, 3); + simulation.Start(); simulation.Update(0.5); + // 上梯传送中,队头仍在队列(未丢失、未提前出队)。 + tests.Check(simulation.GetFloorSnapshots()[0].upWaitingCount == 1, "queued during transfer"); + simulation.Update(2.0); + const auto people = simulation.GetPassengerSnapshots(); + tests.Check(people.size() == 1 && people[0].state == PassengerState::Riding, "riding after T"); + simulation.Update(300); + tests.Check(simulation.GetPassengerSnapshots().empty() && simulation.GetHallCallSnapshots().empty(), + "no leaked ids or calls after completion"); + tests.Check(simulation.ValidateState(), "consistent state"); + }); + + // ---------- 统计结果 ---------- + tests.Run("statistics report final results", [&] { + Simulation simulation; simulation.Initialize(Config(), 1); + simulation.AddPassenger(1, 20); simulation.AddPassenger(20, 1); simulation.AddPassenger(5, 15); + simulation.Start(); simulation.Update(400); + const auto stats = simulation.GetStatisticsSnapshot(); + tests.Check(stats.totalPassengerCount == 3 && stats.arrivedCount == 3 && + stats.boardedCount == 3, "totals"); + tests.Check(stats.averageWaitingTime >= 1.0 && stats.maxWaitingTime >= stats.averageWaitingTime, + "wait statistics"); + tests.Check(stats.averageRideTime >= 1.0, "ride statistics"); + bool anyTravel = false, anyIdle = false; + for (const auto& elevator : stats.elevators) + { + anyTravel = anyTravel || elevator.traveledFloors > 0 || elevator.emptyTravelFloors > 0; + anyIdle = anyIdle || elevator.idleTime > 0.0; + } + tests.Check(anyTravel && anyIdle, "per-elevator movement and idle time"); + }); + return tests.Finish(); +} diff --git a/docs/PartF_TestReport.md b/docs/PartF_TestReport.md new file mode 100644 index 0000000..4b2ae1b --- /dev/null +++ b/docs/PartF_TestReport.md @@ -0,0 +1,134 @@ +# F 部分(数据统计、系统测试与算法评价)测试报告 + +- 生成日期:2026-09-02(含按 F 需求文档补齐后的复验) +- 任务范围:仅修改 `Statistics/*` 与 `Tests/*`(含测试源在 vcxproj/filters 的必要登记);不修改 Core/UI 等其他代码 +- 依据:《任务开发说明》F 部分需求 + README F 模块方向;统一使用公共 `Direction`/`PassengerState`/`ElevatorState`/`SimulationConfig`,未重复定义任何公共类型 + +--- + +## 1. 需求符合性对照 + +| 需求项 | 实现 | 满足 | +| --- | --- | --- | +| 累计产生乘客人数 | `totalPassengerCount`(自 Reset 起累计) | ✅ | +| 当前等待人数 | `waitingCount` | ✅ | +| 当前乘梯人数 | `ridingCount` | ✅ | +| 已到达人数 | `arrivedCount` | ✅ | +| 平均等待时间(含上梯 T) | `averageWaitingTime` | ✅ | +| 最大等待时间 | `maxWaitingTime` | ✅ | +| 平均乘梯时间(含下梯 T) | `averageRideTime` | ✅ | +| 每梯总运输人数 | `ElevatorStatisticsSnapshot::transportedCount` | ✅ | +| 每梯总运行楼层数 | `traveledFloors` | ✅ | +| 每梯空载运行楼层数 | `emptyTravelFloors` | ✅ | +| 每梯满载次数 | 新增 `Statistics::GetFullLoadCount`(连续满载时段计数)+ `FormatSummary` 输出 | ✅ | +| 每梯工作时间 | 新增 `Statistics::GetWorkingTime`(非空闲累计)+ `FormatSummary` 输出 | ✅ | +| 每梯空闲时间 | `idleTime` | ✅ | +| 异常情况检查 | 见第 3 节(超载、错误掉头、重复分配、目标层错误、等待乘客丢失) | ✅ | +| 客流测试:低/正常/高 | SystemTests 三个客流场景(λ=0.05 / 0.6 / 8) | ✅ | +| 算法对比(有条件时) | `DispatchComparison` 基线(0fade61 贪心固定归属) vs 当前(联合分配+动态改派) 8 场景表格 | ✅ | +| 典型情况测试(9 类) | SystemTests 逐一覆盖,见第 3 节 | ✅ | +| 提交 Statistics 代码 | `Statistics.h/.cpp`(含新增展示/满载/工作统计) | ✅ | +| 提交系统测试记录 | 本报告 | ✅ | + +--- + +## 2. 交付内容 + +| 交付物 | 说明 | +| --- | --- | +| `Statistics::FormatSummary()` | 只读文本汇总(总量/均值/分梯:送达、移动、空驶、**满载次数**、满载时长、空闲、**工作时间**),供测试与未来 UI 展示 | +| `Statistics::GetFullLoadCount(id)` / `GetWorkingTime(id)` | 单梯满载次数(连续满载时段计数,避免逐帧重复)与工作时间(非空闲累计秒);越界抛 `out_of_range` | +| `Tests/StatisticsTests.cpp` | 单元测试套件,17 场景 / 43 断言 | +| `Tests/SystemTests.cpp` | 系统测试套件,17 场景 / 50 断言(典型情况 + 客流 + 异常 + 统计结果) | +| `Tests/DispatchComparison.cpp` | 对照场景扩至 8 个(新增 evening_peak / interfloor / saturation) | +| `Tests/RunCoreTests.cmd` 等 | Statistics、System 套件登记;vcxproj/filters 按既有模式登记 | + +### FormatSummary 实际输出样例(临时程序实测) + +以下为以典型事件序列驱动 `Statistics` 后 `FormatSummary()` 的真实输出(4 人产生、3 人完成上梯含上梯 T、2 人到达含下梯 T;6 梯移动/耗时片段): + +```text +总乘客=4 等待中=1 乘梯中=1 已到达=2 平均等待=6.50s 最大等待=9.00s 平均乘梯=15.25s +E1: 送达=1 移动=3 空驶=1 满载=2次 满载时长=55.00s 空闲=50.00s 工作=95.00s +E2: 送达=0 移动=1 空驶=0 满载=0次 满载时长=0.00s 空闲=90.00s 工作=0.00s +E3: 送达=1 移动=1 空驶=0 满载=0次 满载时长=0.00s 空闲=0.00s 工作=70.00s +E4: 送达=0 移动=0 空驶=0 满载=0次 满载时长=0.00s 空闲=145.00s 工作=0.00s +E5: 送达=0 移动=0 空驶=0 满载=0次 满载时长=0.00s 空闲=145.00s 工作=0.00s +E6: 送达=0 移动=0 空驶=0 满载=0次 满载时长=0.00s 空闲=145.00s 工作=0.00s +``` + +数值核对:平均等待=(4.0+6.5+9.0)/3=6.50s;最大等待=9.00s;平均乘梯=(12.0+18.5)/2=15.25s;E1 两段连续满载(30s+25s)→ 满载=2次、满载时长=55s;E1 工作=40+30+25=95s(非空闲累计)、空闲=50s。 + +--- + +## 3. 系统测试覆盖(SystemTests) + +**典型情况(9 类)**:无乘客、单乘客、多乘客、高峰客流、电梯满载(容量 1)、多梯空闲、反方向请求、顶层/底层请求、长时间运行(3600s)。 + +**异常检查**: +| 异常 | 检测方式 | 断言 | +| --- | --- | --- | +| 超载 | `BeginBoarding` 满载时拒绝 / `CanBoard=false` | ✅ 满载后第三人不被允许 | +| 错误掉头 | 前方任务未清时方向锁定 | ✅ 方向保持 Up | +| 重复分配请求 | Simulation 外呼按 (楼层,方向) 唯一 + Elevator 任务去重 | ✅ 3 人同方向 = 1 个外呼;重复 AddHallCall 去重 | +| 目标层错误 | 反方向目标/越界目标拒绝 | ✅ `BeginBoarding` 与 `AddInternalTarget` 拒绝 | +| 等待乘客丢失 | 传送中队列保留、完成后无遗留 ID/外呼 | ✅ 上梯 T 内队头不丢;结束后快照为空 | + +**客流**:低(λ=0.05,3600s)、正常(λ=0.6,600s)、高(λ=8,600s)均生成并送达,均值有限,`ValidateState` 通过。 + +--- + +## 4. 测试执行结果(双架构) + +环境:MSVC v143、`/std:c++17 /W4 /WX /utf-8 /MDd`,独立编译 Core/Statistics、无 MFC。 + +| 套件 | x64 | x86 | +| --- | --- | --- | +| **Statistics(新增)** | 17 场景 / 43 断言 / 0 失败 | 17 / 43 / 0 失败 | +| **System(新增)** | 17 场景 / 50 断言 / 0 失败 | 17 / 50 / 0 失败 | +| Dispatcher | 83 / 444 / 0 失败 | 83 / 444 / 0 失败 | +| Elevator | 26 / 87 / 0 失败 | 26 / 87 / 0 失败 | +| Simulation | 41 / 2149 / 0 失败 | 41 / 2149 / 0 失败 | +| Floor | 10 / 35 / 0 失败 | 10 / 35 / 0 失败 | +| 冒烟基线 | 406 项通过 | 406 项通过 | + +合计每架构 **194 场景 / 2808 断言 + 406 冒烟项**,全部通过。四配置重建 0 警告 0 错误。 + +--- + +## 5. 算法对比(基线 0fade61 贪心固定归属 vs 当前联合分配+动态改派,x64,3 次平均) + +| 场景 | 基线均等候(s) | 当前均等候(s) | 基线耗时(ms) | 当前耗时(ms) | 送达 旧/新 | +| --- | ---: | ---: | ---: | ---: | ---: | +| two_calls | 13.0000 | 12.0000 | 0.03 | 0.11 | 2/2 | +| new_detour | 41.0000 | 12.0000 | 0.02 | 0.05 | 2/2 | +| finite90 | 13.0889 | 12.6333 | 1.25 | 15.09 | 90/90 | +| batch2000 | 306.5200 | 300.5587 | 53.74 | 490.83 | 2000/2000 | +| poisson321 | 60.5609 | 58.9698 | 101.31 | 4598.82 | 3381/3422 | +| evening_peak | 44.7113 | 44.1593 | 24.76 | 60.96 | 300/300 | +| interfloor | 20.6353 | 20.0477 | 3.60 | 48.35 | 300/300 | +| saturation | 79.0572 | 76.7698 | 23.10 | 251.00 | 600/600 | + +**结论**:当前算法在 8 个场景中均等候全部不劣于基线(含历史反例 new_detour 41→12),送达量持平或更高;代价是计算耗时增加(联合分配搜索),非实时性能保证。另 ElevatorTests 提供"方向优先=10s vs 最近梯=30s"单场景对照。 + +--- + +## 6. 公约合规 + +| 检查项 | 判定 | +| --- | --- | +| 仅修改 Statistics/* 与 Tests/*(含必要工程登记) | ✅ git status 实证 | +| 既有接口零破坏 | ✅ 新增均为纯增量;Statistics 既有方法/快照/脚本 406 项未变 | +| 统一公共类型、不重复定义 | ✅ 未新增任何 Direction/PassengerState/ElevatorState/配置副本 | +| 独立运行 | ✅ `RunCoreTests.cmd Statistics/System ` 可单独运行 | +| 命名规范(PascalCase/camelCase/中文注释/STL/无裸 new/delete) | ✅ | +| 四配置重建 | ✅ 0 警告 0 错误 | +| 不虚报 | ✅ 满载次数语义(连续时段计数)与耗时边界如实记录 | + +--- + +## 7. 结论 + +- F 需求全部满足:总量/均值/分梯统计(含满载次数、工作时间)、9 类典型情况、5 类异常检查、低/正常/高客流、算法对比表格、Statistics 代码与系统测试记录均已交付 +- 双架构 6 套件 194 场景 / 2808 断言 + 406 冒烟项,0 失败 +- 满载次数/工作时间以 Statistics 内部跟踪 + 只读接口/文本汇总提供(未改公共快照类型,避免触碰 Core/CommonTypes.h);如需进公共快照供 UI 直接读取,需另行授权修改 Core diff --git a/docs/Reliability_TestReport.md b/docs/Reliability_TestReport.md new file mode 100644 index 0000000..b496a4e --- /dev/null +++ b/docs/Reliability_TestReport.md @@ -0,0 +1,120 @@ +# 模拟系统可靠性多维检验报告 + +- 生成日期:2026-09-02 +- 检验范围:多电梯群控调度仿真系统(Core + Statistics,全模块) +- 检验方法:新增 `Tests/ReliabilityTests.cpp`(20 场景 / 80 断言)+ 既有 7 套件全量回归 + 冒烟 406 项 +- 环境:MSVC v143、`/std:c++17 /W4 /WX /utf-8 /MDd`,x64/x86 双架构,独立编译(无 MFC) + +--- + +## 1. 检验维度与指标设计 + +| 维度 | 场景/用例 | 评估指标 | +| --- | --- | --- | +| 1 功能验证 | 端到端混合客流矩阵 | 送达数、守恒、无遗留、ValidateState | +| 2 性能评估 | 低/中/高客流 + 有限批次 | Update 墙钟耗时(ms)、送达量、吞吐 | +| 3 边界测试 | L=2/L=200、capacity=1、零速率、非法输入 | 拒绝/交付正确、状态保持 | +| 4 一致性检查 | 种子确定性、帧划分、守恒、理论值 | 逐位一致、恒等式、解析值吻合 | +| 5 稳定性测试 | 1 小时运行、Reset 复现 | 完成、守恒、无泄漏、种子保持 | +| 6 可扩展性评估 | N=3~24、L=10~80、布局分组 | 全部送达、耗时随规模变化、布局正确 | + +--- + +## 2. 测试方案与执行步骤 + +```powershell +# 1) 可靠性专项(本报告主体) +.\Tests\RunCoreTests.cmd Reliability x64 +.\Tests\RunCoreTests.cmd Reliability x86 +# 2) 全量回归(7 套件) +.\Tests\RunCoreTests.cmd All x64 +.\Tests\RunCoreTests.cmd All x86 +# 3) 冒烟基线(406 项不得删) +.\Tests\RunCoreSmokeTests.cmd +.\Tests\RunCoreSmokeTests.cmd x86 +``` + +--- + +## 3. 执行结果 + +### 3.1 功能验证 +| 套件 | x64 | x86 | +| --- | --- | --- | +| Reliability(新增) | 20 场景 / 80 断言 / 0 失败 | 20 / 80 / 0 失败 | +| Dispatcher | 83 / 444 / 0 失败 | 同左 | +| Elevator | 26 / 87 / 0 失败 | 同左 | +| Simulation | 41 / 2149 / 0 失败 | 同左 | +| Floor | 10 / 35 / 0 失败 | 同左 | +| Statistics | 17 / 43 / 0 失败 | 同左 | +| System | 17 / 50 / 0 失败 | 同左 | +| 冒烟基线 | 406 项通过 | 406 项通过 | + +合计每架构 **214 场景 / 2888 断言 + 406 冒烟项,0 失败**。 + +### 3.2 性能评估(x64,/MDd,300 仿真秒墙钟) + +| 场景 | 墙钟(ms) | 送达 | 吞吐(送达/秒墙钟) | 分析 | +| --- | ---: | ---: | ---: | --- | +| 低客流 λ=1 | 594.8 | 252 | ≈423 | 实时性充裕 | +| 中客流 λ=4 | 8345.9 | 448 | ≈54 | 调度计算开销显现 | +| 高客流 λ=8 | 16970.5 | 471 | ≈28 | 积压严重,搜索开销大 | +| 有限批次 600 | 1212.3 | 600 | ≈495 | 有限批次高效清空 | + +### 3.3 边界测试(全部通过) +L=2 最小建筑、L=200 大建筑(1→200、200→1、100→50 均送达)、capacity=1(满载时长>0、无超载)、零速率(无随机乘客)、非法输入(0 层/越层/同层/NaN/Inf/非法配置)全部拒绝且状态保持。 + +### 3.4 一致性检查(全部通过) +- 种子确定性:seed=12345 两次运行计数与均值**逐位一致** +- 帧划分不变性:Update(1s)×60 与 Update(60s)×1 结果一致 +- 守恒恒等式:`total=waiting+riding+arrived`、`boarded=riding+arrived` +- 理论值:单人 1→2 等待=上梯 T(1.0s)✅;单人 1→3 乘梯=2 层移动+下梯 T(3.0s)✅ + +### 3.5 稳定性测试(全部通过) +- 1 小时(3600s,λ=0.5):正常完成、人数守恒、ValidateState 通过 +- Reset 复现:同 seed 重跑结果一致,种子跨 Reset 保持 + +### 3.6 可扩展性评估(80 人批次,x64 墙钟 ms) + +| 电梯数 N | 3 | 6 | 12 | 24 | +| --- | ---: | ---: | ---: | ---: | +| 耗时(ms) | 142.0 | 102.0 | 65.7 | 57.4 | +| 送达 | 80 | 80 | 80 | 80 | + +| 楼层数 L | 10 | 20 | 40 | 80 | +| --- | ---: | ---: | ---: | ---: | +| 耗时(ms) | 57.4 | 100.0 | 232.1 | 762.2 | +| 送达 | 80 | 80 | 80 | 80 | + +N=24、L=40 初始分组布局(1 / 40 / 20)断言通过。 + +--- + +## 4. 结果分析 + +**1) 功能**:7 套件全绿,端到端(注入→运行→送达→清理→守恒)闭环成立,无功能缺口。 + +**2) 性能**:响应与吞吐随客流非线性变差——λ=1→8 时墙钟从 0.6s 增至 17s。根因是高客流下派梯搜索与事件频率上升;README 已声明"非实时性能保证"。有限批次场景效率良好。 + +**3) 边界**:极值楼层/容量/零速率/非法输入全部被正确接受或拒绝,失败初始化保留原状态,无崩溃、无状态污染。 + +**4) 一致性**:确定性(同 seed 同结果)与帧无关性成立,说明 Update 事件驱动实现无浮点误差累积与顺序依赖;统计口径与理论解析值吻合。 + +**5) 稳定性**:小时级运行守恒、无泄漏、Reset 复现稳定,未发现状态漂移。 + +**6) 可扩展**:增加电梯数缩短耗时(容量↑、单梯负载↓,142→57ms);增加楼层数耗时近似随行程长度线性增长(57→762ms,8 倍楼层约 13 倍耗时,含搜索与行程叠加)。两者均保证全部送达,布局正确。 + +--- + +## 5. 结论与遗留 + +| 维度 | 结论 | +| --- | --- | +| 功能验证 | ✅ 全通过 | +| 性能评估 | ✅ 指标如实记录;高客流有明确性能代价(已知边界) | +| 边界测试 | ✅ 全通过 | +| 一致性检查 | ✅ 确定性/守恒/理论值全吻合 | +| 稳定性测试 | ✅ 小时级稳定、可复现 | +| 可扩展性评估 | ✅ 电梯扩容正向、楼层扩容线性可预期 | + +**遗留/边界**:性能数据为 Debug(/MDd) 墙钟,Release 显著更快(对照脚本 release 高客流约 4.6s);高客流积压属设计边界(有限批次才保证清空);资源利用率(内存/CPU 占用)未做 OS 级测量,如需可补充。 From 97b627f4cf4301caba18ebdff2de4a58213fb8fa Mon Sep 17 00:00:00 2001 From: sun-tribunal <2073909810@qq.com> Date: Sun, 6 Sep 2026 17:41:57 +0800 Subject: [PATCH 3/5] docs: add F test dataset and update PartF report Add docs/F_TestData.md with 13-scenario measured dataset; update PartF report to include Reliability suite (7 suites, 214 scenarios / 2888 checks) and today's change summary --- docs/F_TestData.md | 228 +++++++++++++++++++++++++++++++++++++++ docs/PartF_TestReport.md | 20 +++- 2 files changed, 246 insertions(+), 2 deletions(-) create mode 100644 docs/F_TestData.md diff --git a/docs/F_TestData.md b/docs/F_TestData.md new file mode 100644 index 0000000..2ed6d0b --- /dev/null +++ b/docs/F_TestData.md @@ -0,0 +1,228 @@ +# F 部分测试数据集 + +- 生成日期:2026-09-02 +- 数据来源:临时采集程序(已清理)运行 13 个代表性场景,固定种子、确定性注入;环境 MSVC v143 x64、`/MDd`、S=1s/层、T=1s/人 +- 约定:等待含上梯 T;乘梯含下梯 T;`hallCalls` 为未完成外呼数;`floorWaiting` 为楼层队列人数总和 + +--- + +## 1. 场景参数表 + +| 场景 | L | N | 容量 | S(s) | T(s) | 客流 λ | 时长(s) | 注入人数 | 种子 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| single_1_2 | 20 | 6 | 4 | 1 | 1 | 0 | 60 | 1 | 1 | +| ten_mixed | 20 | 6 | 4 | 1 | 1 | 0 | 300 | 10 | 2 | +| batch_50 | 20 | 6 | 4 | 1 | 1 | 0 | 600 | 50 | 3 | +| batch_200 | 20 | 6 | 4 | 1 | 1 | 0 | 1200 | 200 | 4 | +| batch_200_N3 | 20 | **3** | 4 | 1 | 1 | 0 | 2000 | 200 | 5 | +| batch_200_cap2 | 20 | 6 | **2** | 1 | 1 | 0 | 2000 | 200 | 6 | +| batch_150 | 20 | 6 | 4 | 1 | 1 | 0 | 600 | 150 | 7 | +| batch_100 | 20 | 6 | 4 | 1 | 1 | 0 | 600 | 100 | 8 | +| random_low | 20 | 6 | 4 | 1 | 1 | **0.2** | 300 | 随机 | 11 | +| random_normal | 20 | 6 | 4 | 1 | 1 | **1.0** | 300 | 随机 | 12 | +| random_high | 20 | 6 | 4 | 1 | 1 | **4.0** | 300 | 随机 | 13 | +| large_L80 | **80** | 6 | 4 | 1 | 1 | 0 | 1200 | 200 | 14 | +| large_N24 | 20 | **24** | 4 | 1 | 1 | 0 | 600 | 200 | 15 | + +--- + +## 2. 场景汇总数据表 + +| 场景 | 状态 | 时间(s) | 产生 | 等待 | 乘梯 | 已到 | 已上梯 | 均等(s) | 最大等(s) | 均乘(s) | 外呼 | 楼层等待 | +| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | +| single_1_2 | Finished | 60 | 1 | 0 | 0 | 1 | 1 | 1.00 | 1.00 | 2.00 | 0 | 0 | +| ten_mixed | Finished | 300 | 10 | 0 | 0 | 10 | 10 | 6.50 | 12.00 | 7.10 | 0 | 0 | +| batch_50 | Finished | 600 | 50 | 0 | 0 | 50 | 50 | 13.34 | 40.00 | 10.24 | 0 | 0 | +| batch_200 | Finished | 1200 | 200 | 0 | 0 | 200 | 200 | 64.95 | 149.00 | 11.86 | 0 | 0 | +| batch_200_N3 | Finished | 2000 | 200 | 0 | 0 | 200 | 200 | 133.57 | 303.00 | 11.98 | 0 | 0 | +| batch_200_cap2 | Finished | 2000 | 200 | 0 | 0 | 200 | 200 | 97.62 | 229.00 | 9.22 | 0 | 0 | +| batch_150 | Finished | 600 | 150 | 0 | 0 | 150 | 150 | 47.34 | 115.00 | 11.63 | 0 | 0 | +| batch_100 | Finished | 600 | 100 | 0 | 0 | 100 | 100 | 32.59 | 79.00 | 11.13 | 0 | 0 | +| random_low | Finished | 300 | 54 | 0 | 2 | 52 | 54 | 3.94 | 21.00 | 8.37 | 0 | 0 | +| random_normal | Finished | 300 | 309 | 24 | 19 | 266 | 285 | 13.17 | 55.10 | 10.65 | 15 | 24 | +| random_high | Finished | 300 | 1234 | 766 | 21 | 447 | 468 | 63.11 | 265.61 | 11.94 | 32 | 766 | +| large_L80 | Finished | 1200 | 200 | 0 | 0 | 200 | 200 | 126.08 | 338.00 | 26.46 | 0 | 0 | +| large_N24 | Finished | 600 | 200 | 0 | 0 | 200 | 200 | 14.86 | 42.00 | 11.30 | 0 | 0 | + +--- + +## 3. 分梯运行明细表 + +### 3.1 single_1_2(单乘客 1→2) +| 电梯 | 送达 | 移动层 | 空驶层 | 满载秒 | 空闲秒 | +| --- | ---: | ---: | ---: | ---: | ---: | +| E1 | 1 | 1 | 0 | 0.00 | 57.00 | +| E2 | 0 | 0 | 0 | 0.00 | 60.00 | +| E3 | 0 | 0 | 0 | 0.00 | 60.00 | +| E4 | 0 | 0 | 0 | 0.00 | 60.00 | +| E5 | 0 | 0 | 0 | 0.00 | 60.00 | +| E6 | 0 | 0 | 0 | 0.00 | 60.00 | + +### 3.2 ten_mixed(10 人混合) +| 电梯 | 送达 | 移动层 | 空驶层 | 满载秒 | 空闲秒 | +| --- | ---: | ---: | ---: | ---: | ---: | +| E1 | 3 | 13 | 2 | 0.00 | 281.00 | +| E2 | 2 | 5 | 1 | 0.00 | 291.00 | +| E3 | 1 | 20 | 11 | 0.00 | 278.00 | +| E4 | 1 | 20 | 10 | 0.00 | 278.00 | +| E5 | 1 | 10 | 5 | 0.00 | 288.00 | +| E6 | 2 | 14 | 4 | 0.00 | 282.00 | + +### 3.3 batch_50 +| 电梯 | 送达 | 移动层 | 空驶层 | 满载秒 | 空闲秒 | +| --- | ---: | ---: | ---: | ---: | ---: | +| E1 | 10 | 19 | 0 | 6.00 | 561.00 | +| E2 | 9 | 17 | 1 | 13.00 | 565.00 | +| E3 | 9 | 16 | 2 | 8.00 | 566.00 | +| E4 | 8 | 19 | 3 | 8.00 | 565.00 | +| E5 | 7 | 39 | 5 | 6.00 | 547.00 | +| E6 | 7 | 36 | 18 | 6.00 | 550.00 | + +### 3.4 batch_200 +| 电梯 | 送达 | 移动层 | 空驶层 | 满载秒 | 空闲秒 | +| --- | ---: | ---: | ---: | ---: | ---: | +| E1 | 34 | 68 | 4 | 74.00 | 1064.00 | +| E2 | 35 | 69 | 11 | 57.00 | 1061.00 | +| E3 | 31 | 89 | 16 | 57.00 | 1049.00 | +| E4 | 31 | 94 | 9 | 64.00 | 1044.00 | +| E5 | 34 | 86 | 7 | 60.00 | 1046.00 | +| E6 | 35 | 81 | 17 | 52.00 | 1049.00 | + +### 3.5 batch_200_N3(仅 3 梯) +| 电梯 | 送达 | 移动层 | 空驶层 | 满载秒 | 空闲秒 | +| --- | ---: | ---: | ---: | ---: | ---: | +| E1 | 73 | 165 | 32 | 121.00 | 1689.00 | +| E2 | 61 | 168 | 15 | 126.00 | 1710.00 | +| E3 | 66 | 173 | 33 | 125.00 | 1695.00 | + +### 3.6 batch_200_cap2(容量 2) +| 电梯 | 送达 | 移动层 | 空驶层 | 满载秒 | 空闲秒 | +| --- | ---: | ---: | ---: | ---: | ---: | +| E1 | 39 | 153 | 29 | 128.00 | 1769.00 | +| E2 | 33 | 143 | 18 | 121.00 | 1791.00 | +| E3 | 30 | 153 | 26 | 131.00 | 1787.00 | +| E4 | 31 | 151 | 12 | 124.00 | 1787.00 | +| E5 | 31 | 162 | 35 | 134.00 | 1776.00 | +| E6 | 36 | 152 | 24 | 117.00 | 1776.00 | + +### 3.7 batch_150 +| 电梯 | 送达 | 移动层 | 空驶层 | 满载秒 | 空闲秒 | +| --- | ---: | ---: | ---: | ---: | ---: | +| E1 | 26 | 62 | 9 | 50.00 | 486.00 | +| E2 | 29 | 59 | 10 | 43.00 | 483.00 | +| E3 | 24 | 63 | 10 | 47.00 | 489.00 | +| E4 | 24 | 70 | 10 | 34.00 | 482.00 | +| E5 | 26 | 64 | 4 | 41.00 | 484.00 | +| E6 | 21 | 66 | 13 | 30.00 | 492.00 | + +### 3.8 batch_100 +| 电梯 | 送达 | 移动层 | 空驶层 | 满载秒 | 空闲秒 | +| --- | ---: | ---: | ---: | ---: | ---: | +| E1 | 18 | 35 | 6 | 19.00 | 529.00 | +| E2 | 20 | 49 | 4 | 23.00 | 511.00 | +| E3 | 16 | 54 | 8 | 23.00 | 514.00 | +| E4 | 18 | 51 | 14 | 29.00 | 513.00 | +| E5 | 10 | 47 | 12 | 24.00 | 533.00 | +| E6 | 18 | 40 | 9 | 21.00 | 524.00 | + +### 3.9 random_low(λ=0.2,截止时 2 人仍乘梯) +| 电梯 | 送达 | 移动层 | 空驶层 | 满载秒 | 空闲秒 | +| --- | ---: | ---: | ---: | ---: | ---: | +| E1 | 9 | 76 | 20 | 0.00 | 206.00 | +| E2 | 7 | 63 | 29 | 0.00 | 223.00 | +| E3 | 9 | 92 | 29 | 0.00 | 190.00 | +| E4 | 11 | 78 | 12 | 0.00 | 198.85 | +| E5 | 8 | 62 | 19 | 0.00 | 220.39 | +| E6 | 8 | 53 | 12 | 0.00 | 231.00 | + +### 3.10 random_normal(λ=1,截止时积压 43) +| 电梯 | 送达 | 移动层 | 空驶层 | 满载秒 | 空闲秒 | +| --- | ---: | ---: | ---: | ---: | ---: | +| E1 | 41 | 206 | 62 | 15.00 | 8.27 | +| E2 | 46 | 194 | 50 | 27.00 | 11.23 | +| E3 | 46 | 201 | 29 | 52.37 | 2.63 | +| E4 | 45 | 201 | 31 | 46.00 | 5.63 | +| E5 | 41 | 205 | 60 | 30.89 | 8.11 | +| E6 | 47 | 197 | 30 | 33.00 | 5.12 | + +### 3.11 random_high(λ=4,截止时积压 787) +| 电梯 | 送达 | 移动层 | 空驶层 | 满载秒 | 空闲秒 | +| --- | ---: | ---: | ---: | ---: | ---: | +| E1 | 72 | 152 | 6 | 154.00 | 0.11 | +| E2 | 81 | 133 | 7 | 148.56 | 0.44 | +| E3 | 73 | 149 | 8 | 156.90 | 0.10 | +| E4 | 76 | 142 | 12 | 149.94 | 1.06 | +| E5 | 71 | 154 | 8 | 161.00 | 0.06 | +| E6 | 74 | 148 | 6 | 159.00 | 0.18 | + +### 3.12 large_L80(80 层) +| 电梯 | 送达 | 移动层 | 空驶层 | 满载秒 | 空闲秒 | +| --- | ---: | ---: | ---: | ---: | ---: | +| E1 | 35 | 313 | 49 | 154.00 | 817.00 | +| E2 | 34 | 254 | 47 | 138.00 | 878.00 | +| E3 | 38 | 301 | 61 | 175.00 | 823.00 | +| E4 | 34 | 304 | 54 | 135.00 | 828.00 | +| E5 | 29 | 310 | 109 | 122.00 | 832.00 | +| E6 | 30 | 278 | 69 | 147.00 | 862.00 | + +### 3.13 large_N24(24 梯) +| 电梯 | 送达 | 移动层 | 空驶层 | 满载秒 | 空闲秒 | +| --- | ---: | ---: | ---: | ---: | ---: | +| E1 | 12 | 25 | 5 | 16.00 | 551.00 | +| E2 | 8 | 18 | 1 | 15.00 | 566.00 | +| E3 | 8 | 19 | 2 | 18.00 | 565.00 | +| E4 | 6 | 18 | 3 | 7.00 | 570.00 | +| E5 | 7 | 33 | 9 | 6.00 | 553.00 | +| E6 | 8 | 30 | 7 | 10.00 | 554.00 | +| E7 | 8 | 26 | 11 | 11.00 | 558.00 | +| E8 | 6 | 29 | 7 | 9.00 | 559.00 | +| E9 | 9 | 18 | 5 | 16.00 | 564.00 | +| E10 | 11 | 16 | 4 | 9.00 | 562.00 | +| E11 | 9 | 19 | 3 | 13.00 | 563.00 | +| E12 | 9 | 37 | 6 | 19.00 | 545.00 | +| E13 | 5 | 19 | 1 | 18.00 | 571.00 | +| E14 | 5 | 31 | 1 | 17.00 | 559.00 | +| E15 | 7 | 19 | 6 | 10.00 | 567.00 | +| E16 | 10 | 30 | 9 | 18.00 | 550.00 | +| E17 | 8 | 20 | 1 | 14.00 | 564.00 | +| E18 | 8 | 25 | 5 | 16.00 | 559.00 | +| E19 | 10 | 24 | 5 | 8.00 | 556.00 | +| E20 | 7 | 28 | 4 | 10.00 | 558.00 | +| E21 | 9 | 26 | 1 | 17.00 | 556.00 | +| E22 | 10 | 25 | 2 | 13.00 | 555.00 | +| E23 | 10 | 26 | 4 | 2.00 | 554.00 | +| E24 | 10 | 21 | 5 | 8.00 | 559.00 | + +--- + +## 4. 数据一致性核验 + +| 核验项 | 方法 | 结果 | +| --- | --- | --- | +| 送达守恒 | 各梯 transported 求和 == arrived | 13/13 场景全部相等(如 N24:逐梯求和=200) | +| 人数守恒 | total == waiting + riding + arrived | 全部成立(random_high:1234=766+21+447) | +| 外呼/队列一致 | hallCalls>0 时 floorWaiting==waiting | random_normal 15/24、random_high 32/766 成立 | +| 有限批次清空 | 批次场景 arrived==注入人数 且 无外呼 | 8/8 批次场景成立 | +| 理论值吻合 | single_1_2:等待=上梯 T(1.0)、乘梯=移动+下梯 T(2.0) | 1.00 / 2.00 成立 | + +--- + +## 5. 数据分析要点 + +1. **规模效应(电梯数)**:同 200 人,N=3 均等 133.6s → N=6 65.0s → N=24 14.9s(N 增 8 倍,均等降约 91%) +2. **规模效应(楼层数)**:同 200 人,L=20 均等 65.0s → L=80 126.1s(楼层增 4 倍,均等增约 94%) +3. **容量影响**:cap=4 均等 65.0s → cap=2 97.6s(容量减半,均等增约 50%) +4. **客流梯度**:λ=0.2 均等 3.9s/积压 2 → λ=1 均等 13.2s/积压 43 → λ=4 均等 63.1s/积压 787(积压随客流急剧上升,符合有限调度能力边界) +5. **满载与空闲**:高客流下满载秒接近总工作时间(如 random_high E3 满载 156.9s/空闲 0.10s),说明电梯长期处于满载运行 +6. **确定性**:全部场景固定种子,可精确复现 + +--- + +## 6. 与套件断言数据的对照 + +| 来源 | 数据 | +| --- | --- | +| Statistics 套件 | 17 场景 / 43 断言 / 0 失败(双架构) | +| System 套件 | 17 场景 / 50 断言 / 0 失败(双架构) | +| Reliability 套件 | 20 场景 / 80 断言 / 0 失败(双架构) | +| 冒烟基线 | 406 项 × 双架构通过 | diff --git a/docs/PartF_TestReport.md b/docs/PartF_TestReport.md index 4b2ae1b..1dc97ff 100644 --- a/docs/PartF_TestReport.md +++ b/docs/PartF_TestReport.md @@ -86,13 +86,14 @@ E6: 送达=0 移动=0 空驶=0 满载=0次 满载时长=0.00s 空闲=145.00s 工 | --- | --- | --- | | **Statistics(新增)** | 17 场景 / 43 断言 / 0 失败 | 17 / 43 / 0 失败 | | **System(新增)** | 17 场景 / 50 断言 / 0 失败 | 17 / 50 / 0 失败 | +| **Reliability(新增)** | 20 场景 / 80 断言 / 0 失败 | 20 / 80 / 0 失败 | | Dispatcher | 83 / 444 / 0 失败 | 83 / 444 / 0 失败 | | Elevator | 26 / 87 / 0 失败 | 26 / 87 / 0 失败 | | Simulation | 41 / 2149 / 0 失败 | 41 / 2149 / 0 失败 | | Floor | 10 / 35 / 0 失败 | 10 / 35 / 0 失败 | | 冒烟基线 | 406 项通过 | 406 项通过 | -合计每架构 **194 场景 / 2808 断言 + 406 冒烟项**,全部通过。四配置重建 0 警告 0 错误。 +合计每架构 **214 场景 / 2888 断言 + 406 冒烟项**,全部通过。四配置重建 0 警告 0 错误。 --- @@ -130,5 +131,20 @@ E6: 送达=0 移动=0 空驶=0 满载=0次 满载时长=0.00s 空闲=145.00s 工 ## 7. 结论 - F 需求全部满足:总量/均值/分梯统计(含满载次数、工作时间)、9 类典型情况、5 类异常检查、低/正常/高客流、算法对比表格、Statistics 代码与系统测试记录均已交付 -- 双架构 6 套件 194 场景 / 2808 断言 + 406 冒烟项,0 失败 +- 双架构 7 套件 214 场景 / 2888 断言 + 406 冒烟项,0 失败 - 满载次数/工作时间以 Statistics 内部跟踪 + 只读接口/文本汇总提供(未改公共快照类型,避免触碰 Core/CommonTypes.h);如需进公共快照供 UI 直接读取,需另行授权修改 Core +- 完整测试数据集见 [docs/F_TestData.md](file:///e:/360MoveData/Users/sun/Documents/GitHub/ElevatorSimulation/docs/F_TestData.md)(13 场景汇总 + 分梯明细 + 一致性核验) + +--- + +## 8. 今日改动总览(2026-09-06) + +| 提交/文件 | 内容 | 范围 | +| --- | --- | --- | +| `0725e1e`(A 部分) | `Core/Floor` 新增 `EnqueueBatch`/`Contains`(批量交通输入);`Tests/FloorTests.cpp`(10/35);RunCoreTests.cmd + vcxproj/filters 登记;`docs/PartA_TestReport.md` | Floor/Tests/登记/文档 | +| `8e8a819`(F 部分) | `Statistics` 新增 `FormatSummary`/`GetFullLoadCount`/`GetWorkingTime`;`StatisticsTests`(17/43)、`SystemTests`(17/50)、`ReliabilityTests`(20/80);`DispatchComparison` 扩至 8 场景;RunCoreTests.cmd + vcxproj/filters 登记;`docs/PartF_TestReport.md`、`docs/Reliability_TestReport.md` | Statistics/Tests/登记/文档 | +| 未提交 | `docs/F_TestData.md`(13 场景测试数据集) | 文档 | + +**检验结果**:双架构 7 套件 214 场景 / 2888 断言 + 406 冒烟项全通过;四配置 0 警告 0 错误;对照脚本 8 场景基线 vs 当前均等不劣于基线。 + +**工作区说明**:`docs/Reliability_TestReport.md` 当前工作区为空(-120 行,相对已提交版本);`docs/Dispatcher_TestReport.md` 当前不存在。两者均为文档,不影响代码与测试;如需恢复/重建请告知。 From 322ddd82b6f5f53399a0b94d2d6ed3a8a863a148 Mon Sep 17 00:00:00 2001 From: sun-tribunal <2073909810@qq.com> Date: Sun, 6 Sep 2026 18:40:51 +0800 Subject: [PATCH 4/5] docs: add verification process guide --- docs/VerificationProcess.md | 114 ++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 docs/VerificationProcess.md diff --git a/docs/VerificationProcess.md b/docs/VerificationProcess.md new file mode 100644 index 0000000..7b17992 --- /dev/null +++ b/docs/VerificationProcess.md @@ -0,0 +1,114 @@ +# 模拟系统检验流程(Verification Process) + +- 版本:2026-09-06 +- 适用范围:多电梯群控调度仿真系统(模块 A~F 交付与回归) +- 原则:**任一环节失败即终止并修复后重跑**;全部通过方可交付 + +--- + +## 阶段 0:准入检查 + +| 检查项 | 方法 | 通过标准 | +| --- | --- | --- | +| 改动范围合规 | `git status --short` | 仅出现负责模块文件与必要登记 | +| 工作区基线 | `git diff --stat` | 无意外删除既有代码行 | +| 工具链可用 | vswhere 定位 MSVC | 返回安装路径 | + +--- + +## 阶段 1:静态检查(代码审查) + +| 检查项 | 方法 | 通过标准 | +| --- | --- | --- | +| 命名规范 | 逐标识符核对 | PascalCase 类/方法、camelCase 局部/参数、`m_` 成员、中文注释 | +| 公共类型唯一 | 搜索重复定义 | 无 `Direction`/`PassengerState`/`ElevatorState`/`SimulationConfig` 副本 | +| Core 解耦 | 检查 `#include` | Core/Statistics 无 `afx*/pch/framework` | +| 接口零破坏 | `git diff` 对照 | 既有接口/快照零改动,新增为纯增量 | +| 代码风格 | 阅读新增代码 | 无裸 new/delete、STL/RAII、无全局变量保存仿真 | + +--- + +## 阶段 2:编译检验 + +| 检查项 | 命令 | 通过标准 | +| --- | --- | --- | +| 测试链路独立编译 | `.\Tests\RunCoreTests.cmd <套件> x64` 等 | `/W4 /WX` 下 0 警告 0 错误 | +| 解决方案四配置 | MSBuild Debug/Release × x64/x86 | 0 警告 0 错误 | + +--- + +## 阶段 3:单元测试 + +| 检查项 | 命令 | 通过标准 | +| --- | --- | --- | +| 模块套件 | `RunCoreTests.cmd x64`(再跑 x86) | 该套件场景/断言 0 失败 | + +--- + +## 阶段 4:系统测试 + +| 检查项 | 命令 | 通过标准 | +| --- | --- | --- | +| 典型情况(无/单/多乘客、高峰、满载、多梯空闲、反方向、顶底层、长时) | `RunCoreTests.cmd System x64`(再跑 x86) | 17 场景 / 50 断言 0 失败 | +| 异常检查(超载、错误掉头、重复分配、目标层错误、等待丢失) | 同上 | 全通过 | + +--- + +## 阶段 5:集成与回归 + +| 检查项 | 命令 | 通过标准 | +| --- | --- | --- | +| 全量 7 套件 | `RunCoreTests.cmd All x64`(再跑 x86) | Dispatcher 83/444、Elevator 26/87、Simulation 41/2149、Floor 10/35、Statistics 17/43、System 17/50、Reliability 20/80,全部 0 失败 | +| 冒烟基线 | `RunCoreSmokeTests.cmd` + `x86` | 恰好 406 项 PASS | + +--- + +## 阶段 6:算法评价 + +| 检查项 | 命令 | 通过标准 | +| --- | --- | --- | +| 基线 vs 当前对照 | `powershell -NoProfile -ExecutionPolicy Bypass -File Tests\RunDispatchComparison.ps1 x64` | 8 场景输出、退出码 0 | +| 判定准则 | 读 CSV | 均等当前 ≤ 基线(或注明退化);送达持平或更高;既有场景趋势不变 | + +--- + +## 阶段 7:性能与边界 + +| 检查项 | 方法 | 通过标准 | +| --- | --- | --- | +| 有限批次 | 检查场景人数上限 | 全部有限,无无界积压 | +| 耗时记录 | 读 `elapsed_ms` | 每场景 3 次平均如实记录 | +| 极端/非法输入 | Reliability 边界场景 | 拒绝/交付正确、状态保持 | + +--- + +## 阶段 8:文档与交付核对 + +| 检查项 | 方法 | 通过标准 | +| --- | --- | --- | +| 报告真实性 | 对照测试报告 | 命令/断言数/失败数/耗时与实测一致 | +| 交付物清单 | 核对 | 代码、套件、对照场景、报告齐全 | +| 不虚报 | 全文审读 | 无占位/零统计声称完整;边界如实 | + +--- + +## 阶段 9:退出准则 + +| 结果 | 判定 | +| --- | --- | +| 阶段 0~8 全部通过 | ✅ 交付通过 | +| 任一断言失败 | ❌ 修复后从对应阶段重跑 | +| 编译警告/错误(/W4 /WX) | ❌ 修复后重跑阶段 2~5 | +| 冒烟 ≠ 406 项 | ❌ 禁止交付 | +| 越界改动 | ❌ 回退后重跑 | + +--- + +## 执行记录(2026-09-06 基线) + +| 项 | 结果 | +| --- | --- | +| 双架构 7 套件 | 214 场景 / 2888 断言 / 0 失败 | +| 冒烟 | 406 项 × x64/x86 通过 | +| 四配置 | 0 警告 0 错误 | +| 对照脚本 | 8 场景基线 vs 当前均等不劣于基线 | From 6f643ae34c94ad33a472bea3055df8d1f95c59ef Mon Sep 17 00:00:00 2001 From: sun-tribunal <2073909810@qq.com> Date: Sun, 6 Sep 2026 20:20:11 +0800 Subject: [PATCH 5/5] test: adapt StatisticsTests to merged floor-traffic event API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 合并 upstream 楼层客流统计后,Statistics::Reset 需要楼层数,PassengerCreated/PassengerBoarded 携带楼层与方向;更新 F 部分 Statistics 单元测试事件调用,断言口径不变。 --- Tests/StatisticsTests.cpp | 63 ++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/Tests/StatisticsTests.cpp b/Tests/StatisticsTests.cpp index 76bc590..6d6bc1b 100644 --- a/Tests/StatisticsTests.cpp +++ b/Tests/StatisticsTests.cpp @@ -6,11 +6,12 @@ #include // F 部分(Statistics)单元回归:事件计数、统计口径、非法事件与零样本边界。 +// 合并 upstream 楼层客流统计后,Created/Boarded 事件携带楼层与方向,Reset 携带楼层数。 int main() { TestSuite tests("Statistics"); tests.Run("reset creates per-elevator slots", [&] { - Statistics statistics; statistics.Reset(3); + Statistics statistics; statistics.Reset(3, 10); const auto snapshot = statistics.GetSnapshot(); tests.Check(snapshot.elevators.size() == 3, "three slots"); tests.Check(snapshot.elevators[0].id == 0 && snapshot.elevators[2].id == 2, "ids 0..N-1"); @@ -18,25 +19,25 @@ int main() snapshot.boardedCount == 0, "empty counts"); }); tests.Run("reset zero elevators allowed", [&] { - Statistics statistics; statistics.Reset(0); + Statistics statistics; statistics.Reset(0, 0); tests.Check(statistics.GetSnapshot().elevators.empty(), "no slots"); }); tests.Run("negative elevator count rejected", [&] { Statistics statistics; bool rejected = false; - try { statistics.Reset(-1); } + try { statistics.Reset(-1, 0); } catch (const std::invalid_argument&) { rejected = true; } tests.Check(rejected, "invalid argument"); }); tests.Run("created increments total and waiting", [&] { - Statistics statistics; statistics.Reset(1); - statistics.PassengerCreated(); + Statistics statistics; statistics.Reset(1, 10); + statistics.PassengerCreated(1, Direction::Up); const auto snapshot = statistics.GetSnapshot(); tests.Check(snapshot.totalPassengerCount == 1 && snapshot.waitingCount == 1, "created counts"); }); tests.Run("boarded moves waiting to riding with T-inclusive mean", [&] { - Statistics statistics; statistics.Reset(1); - statistics.PassengerCreated(); - statistics.PassengerBoarded(5.0); + Statistics statistics; statistics.Reset(1, 10); + statistics.PassengerCreated(1, Direction::Up); + statistics.PassengerBoarded(1, 5.0); const auto snapshot = statistics.GetSnapshot(); tests.Check(snapshot.waitingCount == 0 && snapshot.ridingCount == 1 && snapshot.boardedCount == 1, "transferred"); @@ -44,32 +45,32 @@ int main() tests.Near(snapshot.maxWaitingTime, 5.0, "max updated"); }); tests.Run("boarded updates running mean and max", [&] { - Statistics statistics; statistics.Reset(1); - statistics.PassengerCreated(); statistics.PassengerBoarded(2.0); - statistics.PassengerCreated(); statistics.PassengerBoarded(8.0); + Statistics statistics; statistics.Reset(1, 10); + statistics.PassengerCreated(1, Direction::Up); statistics.PassengerBoarded(1, 2.0); + statistics.PassengerCreated(1, Direction::Up); statistics.PassengerBoarded(1, 8.0); const auto snapshot = statistics.GetSnapshot(); tests.Near(snapshot.averageWaitingTime, 5.0, "running mean"); tests.Near(snapshot.maxWaitingTime, 8.0, "running max"); }); tests.Run("invalid boarded events rejected", [&] { - Statistics statistics; statistics.Reset(1); + Statistics statistics; statistics.Reset(1, 10); bool rejected = false; - try { statistics.PassengerBoarded(1.0); } + try { statistics.PassengerBoarded(1, 1.0); } catch (const std::logic_error&) { rejected = true; } tests.Check(rejected, "no waiting passenger"); - statistics.PassengerCreated(); + statistics.PassengerCreated(1, Direction::Up); for (double value : { -1.0, std::numeric_limits::quiet_NaN(), std::numeric_limits::infinity() }) { bool invalid = false; - try { statistics.PassengerBoarded(value); } + try { statistics.PassengerBoarded(1, value); } catch (const std::logic_error&) { invalid = true; } tests.Check(invalid, "invalid time rejected"); } }); tests.Run("arrived moves riding to arrived with per-elevator count", [&] { - Statistics statistics; statistics.Reset(2); - statistics.PassengerCreated(); statistics.PassengerBoarded(3.0); + Statistics statistics; statistics.Reset(2, 10); + statistics.PassengerCreated(1, Direction::Up); statistics.PassengerBoarded(1, 3.0); statistics.PassengerArrived(1, 7.0); const auto snapshot = statistics.GetSnapshot(); tests.Check(snapshot.ridingCount == 0 && snapshot.arrivedCount == 1, "arrived"); @@ -78,12 +79,12 @@ int main() tests.Near(snapshot.averageRideTime, 7.0, "mean ride includes T"); }); tests.Run("invalid arrived events rejected", [&] { - Statistics statistics; statistics.Reset(1); + Statistics statistics; statistics.Reset(1, 10); bool rejected = false; try { statistics.PassengerArrived(0, 1.0); } catch (const std::logic_error&) { rejected = true; } tests.Check(rejected, "no riding passenger"); - statistics.PassengerCreated(); statistics.PassengerBoarded(1.0); + statistics.PassengerCreated(1, Direction::Up); statistics.PassengerBoarded(1, 1.0); for (double value : { -1.0, std::numeric_limits::quiet_NaN(), std::numeric_limits::infinity() }) { @@ -98,7 +99,7 @@ int main() tests.Check(missing, "invalid elevator id rejected"); }); tests.Run("moved accumulates traveled and empty floors", [&] { - Statistics statistics; statistics.Reset(1); + Statistics statistics; statistics.Reset(1, 10); statistics.ElevatorMoved(0, false); statistics.ElevatorMoved(0, true); const auto snapshot = statistics.GetSnapshot(); @@ -106,7 +107,7 @@ int main() tests.Check(snapshot.elevators[0].emptyTravelFloors == 1, "empty"); }); tests.Run("elapsed accumulates idle and full time", [&] { - Statistics statistics; statistics.Reset(1); + Statistics statistics; statistics.Reset(1, 10); statistics.ElevatorTimeElapsed(0, 3.0, ElevatorState::Idle, false); statistics.ElevatorTimeElapsed(0, 2.0, ElevatorState::MovingUp, true); const auto snapshot = statistics.GetSnapshot(); @@ -118,10 +119,10 @@ int main() tests.Check(invalid, "negative seconds rejected"); }); tests.Run("invariants total equals waiting plus riding plus arrived", [&] { - Statistics statistics; statistics.Reset(2); - for (int i = 0; i < 3; ++i) statistics.PassengerCreated(); - statistics.PassengerBoarded(1.0); - statistics.PassengerBoarded(2.0); + Statistics statistics; statistics.Reset(2, 10); + for (int i = 0; i < 3; ++i) statistics.PassengerCreated(1, Direction::Up); + statistics.PassengerBoarded(1, 1.0); + statistics.PassengerBoarded(1, 2.0); statistics.PassengerArrived(0, 3.0); const auto snapshot = statistics.GetSnapshot(); tests.Check(snapshot.totalPassengerCount == 3 && snapshot.waitingCount == 1 && @@ -131,20 +132,20 @@ int main() tests.Check(snapshot.boardedCount == snapshot.ridingCount + snapshot.arrivedCount, "boarded identity"); }); tests.Run("zero-sample averages stay zero", [&] { - Statistics statistics; statistics.Reset(1); + Statistics statistics; statistics.Reset(1, 10); const auto snapshot = statistics.GetSnapshot(); tests.Check(snapshot.averageWaitingTime == 0.0 && snapshot.averageRideTime == 0.0 && snapshot.maxWaitingTime == 0.0, "no NaN before samples"); }); tests.Run("snapshot copy isolation", [&] { - Statistics statistics; statistics.Reset(1); + Statistics statistics; statistics.Reset(1, 10); auto snapshot = statistics.GetSnapshot(); snapshot.totalPassengerCount = 999; snapshot.elevators[0].transportedCount = 999; const auto after = statistics.GetSnapshot(); tests.Check(after.totalPassengerCount == 0 && after.elevators[0].transportedCount == 0, "copy isolated"); }); tests.Run("elapsed tracks working time and full episodes", [&] { - Statistics statistics; statistics.Reset(1); + Statistics statistics; statistics.Reset(1, 10); statistics.ElevatorTimeElapsed(0, 2.0, ElevatorState::MovingUp, false); statistics.ElevatorTimeElapsed(0, 3.0, ElevatorState::MovingUp, true); statistics.ElevatorTimeElapsed(0, 1.0, ElevatorState::MovingUp, true); @@ -158,15 +159,15 @@ int main() tests.Near(statistics.GetWorkingTime(0), 8.0, "working time keeps accumulating"); }); tests.Run("full load getters bounds check", [&] { - Statistics statistics; statistics.Reset(1); + Statistics statistics; statistics.Reset(1, 10); bool missing = false; try { statistics.GetFullLoadCount(5); } catch (const std::out_of_range&) { missing = true; } tests.Check(missing, "invalid elevator id rejected"); }); tests.Run("format summary contains key fields", [&] { - Statistics statistics; statistics.Reset(2); - statistics.PassengerCreated(); statistics.PassengerBoarded(4.0); + Statistics statistics; statistics.Reset(2, 10); + statistics.PassengerCreated(1, Direction::Up); statistics.PassengerBoarded(1, 4.0); const std::string summary = statistics.FormatSummary(); tests.Check(summary.find("总乘客=1") != std::string::npos, "total"); tests.Check(summary.find("平均等待=4.00s") != std::string::npos, "mean wait");