harden(controller): 析构 abort 在飞句柄(退出契约 spec §7) + Grid 路径回灌/失败用例对称覆盖(评审 I-1/M-3/M-4)

This commit is contained in:
gaozheng 2026-06-11 20:51:45 +08:00
parent e57985c057
commit 350f46060d
3 changed files with 40 additions and 0 deletions

View File

@ -6,6 +6,11 @@ namespace geopro::controller {
DatasetDetailController::DatasetDetailController(data::IAsyncDatasetRepository& repo, QObject* parent)
: QObject(parent), repo_(repo) {}
DatasetDetailController::~DatasetDetailController() {
if (chartLoad_) chartLoad_->abort(); // 退出契约abort 在飞句柄,不依赖外部析构顺序兜底
if (gridLoad_) gridLoad_->abort();
}
void DatasetDetailController::openDataset(const QString& dsId, const QString& ddCode) {
if (ddCode != QLatin1String("dd_inversion_data")) { // 首版仅支持 ERT 反演
emit loadFailed(dsId, QStringLiteral("暂不支持该数据类型的预览"));

View File

@ -33,6 +33,7 @@ public:
};
explicit DatasetDetailController(data::IAsyncDatasetRepository& repo, QObject* parent = nullptr);
~DatasetDetailController() override; // 退出契约(spec §7)abort 在飞句柄,避免迟到信号打到已析构 this
public slots:
void openDataset(const QString& dsId, const QString& ddCode);
void focusDataset(const QString& dsId);

View File

@ -17,6 +17,7 @@ struct StubGridLoad : data::GridLoad {
bool aborted = false;
void abort() override { aborted = true; }
void fireDone() { emit done(data::GridParts{}); }
void fireFailed() { emit failed(QStringLiteral("x")); }
};
struct StubAsyncRepo : data::IAsyncDatasetRepository {
StubChartLoad* lastChart = nullptr;
@ -79,3 +80,36 @@ TEST(DatasetDetailController, DropsLateSignalFromAbortedLoad) {
b->fireDone(); // 当前句柄 → 正常
EXPECT_EQ(spy.count(), 1);
}
TEST(DatasetDetailController, EmitsGridReadyOnDone) {
StubAsyncRepo repo;
controller::DatasetDetailController c(repo);
QSignalSpy spy(&c, &controller::DatasetDetailController::gridReady);
c.loadGridData("ds1", "dd_inversion_data");
repo.lastGrid->fireDone();
EXPECT_EQ(spy.count(), 1);
}
TEST(DatasetDetailController, EmitsLoadFailedOnGridFailed) {
StubAsyncRepo repo;
controller::DatasetDetailController c(repo);
QSignalSpy spy(&c, &controller::DatasetDetailController::loadFailed);
c.loadGridData("ds1", "dd_inversion_data");
repo.lastGrid->fireFailed();
EXPECT_EQ(spy.count(), 1);
}
TEST(DatasetDetailController, DropsLateGridSignalFromAbortedLoad) {
StubAsyncRepo repo;
controller::DatasetDetailController c(repo);
QSignalSpy spy(&c, &controller::DatasetDetailController::gridReady);
c.loadGridData("dsA", "dd_inversion_data");
StubGridLoad* a = repo.lastGrid;
c.loadGridData("dsB", "dd_inversion_data"); // 替换 → 旧句柄被 abort
StubGridLoad* b = repo.lastGrid;
EXPECT_TRUE(a->aborted);
a->fireDone(); // 旧句柄迟到 → 身份比对丢弃
EXPECT_EQ(spy.count(), 0);
b->fireDone(); // 当前句柄 → 正常
EXPECT_EQ(spy.count(), 1);
}