fix(product-suite): finalize and cancel generation
Tests / Python 3.11 / Windows (push) Has been cancelled
Tests / Python 3.11 / Windows (push) Has been cancelled
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
@@ -823,6 +824,251 @@ class ProductSuiteGuiTests(TempDirMixin, unittest.TestCase):
|
||||
self.app.processEvents()
|
||||
self.assertGreater(edit.height(), wide_height)
|
||||
|
||||
def test_generation_terminal_watchdog_finalizes_once_and_restores_button(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
config = self._config(temp_dir)
|
||||
project, assets = self._create_project_with_assets(temp_dir, config, 1)
|
||||
jobs = [
|
||||
image_studio.create_job(
|
||||
project.id,
|
||||
source_asset_id=assets[0].id,
|
||||
job_type="白底图",
|
||||
prompt="终态看门狗测试",
|
||||
path=config["db_path"],
|
||||
)
|
||||
for _ in range(2)
|
||||
]
|
||||
for job in jobs:
|
||||
image_studio.update_job_status(
|
||||
job.id,
|
||||
"succeeded",
|
||||
path=config["db_path"],
|
||||
)
|
||||
|
||||
tab = ProductSuiteTab(config=config, db_path=config["db_path"])
|
||||
self.addCleanup(tab.close)
|
||||
state = tab._displayed_state
|
||||
state.account_alias = "alias-a"
|
||||
state.item_id = project.item_id
|
||||
state.project_id = project.id
|
||||
state.project_binding_state = project.binding_state
|
||||
state.worker = mock.Mock()
|
||||
state.thread = mock.Mock()
|
||||
state.generation_run_token = "watchdog-run"
|
||||
state.current_job_ids = [job.id for job in jobs]
|
||||
state.total = len(jobs)
|
||||
state.started_at = time.monotonic()
|
||||
tab._generation_run_states["watchdog-run"] = state.key
|
||||
tab._load_state(state)
|
||||
messages = []
|
||||
tab._message = lambda title, message, **kwargs: messages.append(
|
||||
(title, message)
|
||||
)
|
||||
|
||||
tab._check_generation_watchdogs()
|
||||
self.assertIsNotNone(state.worker)
|
||||
tab._check_generation_watchdogs()
|
||||
|
||||
self.assertIsNone(state.worker)
|
||||
self.assertIsNone(state.thread)
|
||||
self.assertEqual("", state.generation_run_token)
|
||||
self.assertTrue(tab.generate_button.text().startswith("生成套图"))
|
||||
self.assertEqual(1, len(messages))
|
||||
self.assertEqual("商品套图生成完成", messages[0][0])
|
||||
self.assertFalse(
|
||||
tab._finalize_generation(
|
||||
state,
|
||||
"watchdog-run",
|
||||
{"total": 2, "success": 2},
|
||||
source="worker",
|
||||
)
|
||||
)
|
||||
self.assertEqual(1, len(messages))
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_generation_real_qthread_completion_restores_gui_state(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
config = self._config(temp_dir)
|
||||
project, assets = self._create_project_with_assets(temp_dir, config, 1)
|
||||
tab = ProductSuiteTab(config=config, db_path=config["db_path"])
|
||||
self.addCleanup(tab.close)
|
||||
state = tab._displayed_state
|
||||
state.account_alias = "alias-a"
|
||||
state.item_id = project.item_id
|
||||
state.project_id = project.id
|
||||
state.project_binding_state = project.binding_state
|
||||
state.prompt = "真实线程完成测试"
|
||||
tab._load_state(state)
|
||||
messages = []
|
||||
tab._message = lambda title, message, **kwargs: messages.append(
|
||||
(title, message)
|
||||
)
|
||||
|
||||
def fake_run_jobs(jobs, **kwargs):
|
||||
job_list = list(jobs)
|
||||
for job in job_list:
|
||||
image_studio.update_job_status(
|
||||
job.id,
|
||||
"succeeded",
|
||||
path=config["db_path"],
|
||||
)
|
||||
return {
|
||||
"total": len(job_list),
|
||||
"success": len(job_list),
|
||||
"failed": 0,
|
||||
"cancelled": 0,
|
||||
"jobs": [],
|
||||
}
|
||||
|
||||
with mock.patch(
|
||||
"app.gui.workers.image_studio_generation.run_jobs",
|
||||
side_effect=fake_run_jobs,
|
||||
):
|
||||
self.assertTrue(
|
||||
tab.start_generation(
|
||||
state,
|
||||
specs=[
|
||||
{
|
||||
"source_asset_id": assets[0].id,
|
||||
"job_type": "白底图",
|
||||
"prompt": "真实线程完成测试",
|
||||
}
|
||||
],
|
||||
)
|
||||
)
|
||||
deadline = time.monotonic() + 3
|
||||
while state.worker is not None and time.monotonic() < deadline:
|
||||
QTest.qWait(20)
|
||||
self.app.processEvents()
|
||||
|
||||
self.assertIsNone(state.worker)
|
||||
self.assertIsNone(state.thread)
|
||||
self.assertTrue(tab.generate_button.text().startswith("生成套图"))
|
||||
self.assertEqual(1, len(messages))
|
||||
self.assertEqual("商品套图生成完成", messages[0][0])
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_generation_thread_finished_reconciles_nonterminal_job(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
config = self._config(temp_dir)
|
||||
project, assets = self._create_project_with_assets(temp_dir, config, 1)
|
||||
job = image_studio.create_job(
|
||||
project.id,
|
||||
source_asset_id=assets[0].id,
|
||||
job_type="场景图",
|
||||
prompt="线程结束兜底测试",
|
||||
path=config["db_path"],
|
||||
)
|
||||
tab = ProductSuiteTab(config=config, db_path=config["db_path"])
|
||||
self.addCleanup(tab.close)
|
||||
state = tab._displayed_state
|
||||
state.account_alias = "alias-a"
|
||||
state.item_id = project.item_id
|
||||
state.project_id = project.id
|
||||
state.project_binding_state = project.binding_state
|
||||
state.worker = mock.Mock()
|
||||
state.thread = mock.Mock()
|
||||
state.generation_run_token = "thread-fallback"
|
||||
state.current_job_ids = [job.id]
|
||||
state.total = 1
|
||||
state.started_at = time.monotonic()
|
||||
tab._generation_run_states["thread-fallback"] = state.key
|
||||
tab._load_state(state)
|
||||
messages = []
|
||||
tab._message = lambda title, message, **kwargs: messages.append(
|
||||
(title, message)
|
||||
)
|
||||
|
||||
tab._handle_generation_thread_finished("thread-fallback")
|
||||
|
||||
stored = image_studio.get_job(job.id, path=config["db_path"])
|
||||
self.assertEqual("cancelled", stored.status)
|
||||
self.assertEqual(
|
||||
image_studio.JOB_RECOVERY_REGENERATE,
|
||||
stored.recovery_action,
|
||||
)
|
||||
self.assertIsNone(state.worker)
|
||||
self.assertEqual("商品套图生成未完整结束", messages[0][0])
|
||||
self.assertIn("稍后继续查询", messages[0][1])
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_generation_old_run_token_and_repeated_stop_are_ignored(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
config = self._config(temp_dir)
|
||||
accounts.create_account("主店", "alias-a", debug_port=9222, config=config)
|
||||
statuses = []
|
||||
tab = ProductSuiteTab(
|
||||
config=config,
|
||||
db_path=config["db_path"],
|
||||
status_callback=lambda message, level=None: statuses.append(
|
||||
(message, level)
|
||||
),
|
||||
)
|
||||
self.addCleanup(tab.close)
|
||||
state = tab._displayed_state
|
||||
state.worker = mock.Mock()
|
||||
state.thread = mock.Mock()
|
||||
state.generation_run_token = "current-run"
|
||||
state.generation_stop_requested = True
|
||||
state.total = 2
|
||||
tab._generation_run_states["current-run"] = state.key
|
||||
original_worker = state.worker
|
||||
|
||||
self.assertFalse(
|
||||
tab._finalize_generation(
|
||||
state,
|
||||
"old-run",
|
||||
{"total": 2, "success": 2},
|
||||
source="worker",
|
||||
)
|
||||
)
|
||||
self.assertIs(original_worker, state.worker)
|
||||
|
||||
confirm = mock.Mock(return_value=True)
|
||||
with mock.patch.object(tab, "_confirm", confirm):
|
||||
tab.toggle_generation()
|
||||
confirm.assert_not_called()
|
||||
original_worker.cancel.assert_not_called()
|
||||
self.assertEqual(("正在停止当前套图任务", "warning"), statuses[-1])
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_generation_immediate_stop_before_job_creation_finishes_cleanly(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
config = self._config(temp_dir)
|
||||
accounts.create_account("主店", "alias-a", debug_port=9222, config=config)
|
||||
tab = ProductSuiteTab(config=config, db_path=config["db_path"])
|
||||
self.addCleanup(tab.close)
|
||||
state = tab._displayed_state
|
||||
state.worker = mock.Mock()
|
||||
state.thread = mock.Mock()
|
||||
state.generation_run_token = "immediate-stop"
|
||||
state.generation_stop_requested = True
|
||||
state.total = 3
|
||||
state.started_at = time.monotonic()
|
||||
tab._generation_run_states["immediate-stop"] = state.key
|
||||
messages = []
|
||||
tab._message = lambda title, message, **kwargs: messages.append(
|
||||
(title, message)
|
||||
)
|
||||
|
||||
tab._on_generation_finished_signal(
|
||||
{
|
||||
"run_token": "immediate-stop",
|
||||
"cancelled": True,
|
||||
}
|
||||
)
|
||||
|
||||
self.assertIsNone(state.worker)
|
||||
self.assertEqual(3, state.done)
|
||||
self.assertEqual("商品套图生成已停止", messages[0][0])
|
||||
self.assertIn("停止3张", messages[0][1])
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_project_settings_and_result_history_use_existing_backend(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
config = self._config(temp_dir)
|
||||
|
||||
Reference in New Issue
Block a user