Coverage for tests/ui/test_dl_execution_page.py: 100%
733 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-03 15:51 +0000
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-03 15:51 +0000
1import os
2import pytest
3from unittest.mock import Mock, patch, MagicMock, call
4from PyQt6.QtWidgets import QMessageBox, QListWidgetItem
5from PyQt6.QtCore import Qt, QCoreApplication
7from main.ui.dl_execution_page import DlExecutionPage
10@pytest.fixture
11def mock_dl_worker():
12 """Mock for DlWorker."""
13 with patch("main.ui.dl_execution_page.DlWorker") as mock:
14 worker_instance = Mock()
15 worker_instance.progressbar_update = Mock()
16 worker_instance.progressbar_update.connect = Mock()
17 worker_instance.file_update = Mock()
18 worker_instance.file_update.connect = Mock()
19 worker_instance.log_update = Mock()
20 worker_instance.log_update.connect = Mock()
21 worker_instance.finished = Mock()
22 worker_instance.finished.connect = Mock()
23 worker_instance.cancel_requested = Mock()
24 worker_instance.cancel_requested.emit = Mock()
25 worker_instance.start = Mock()
27 mock.return_value = worker_instance
28 yield mock
31@pytest.fixture
32def mock_context_dl(temp_workspace):
33 """Context for DlExecutionPage."""
34 context = {
35 "workspace_path": temp_workspace,
36 "selected_segmentation_files": [
37 os.path.join(temp_workspace, "sub-01", "anat", "sub-01_T1w.nii"),
38 os.path.join(temp_workspace, "sub-02", "anat", "sub-02_T1w.nii")
39 ],
40 "update_main_buttons": Mock(),
41 "language_changed": Mock(),
42 "history": []
43 }
44 return context
47class TestDlExecutionPageInitialization:
48 """Tests for page initialization."""
50 def test_initialization_basic(self, qtbot, mock_context_dl):
51 """Test basic initialization."""
52 page = DlExecutionPage(mock_context_dl)
53 qtbot.addWidget(page)
55 assert page.context == mock_context_dl
56 assert page.previous_page is None
57 assert page.next_page is None
58 assert page.worker is None
59 assert page.current_file is None
60 assert page.processing is False
61 assert page.processing_completed is False
63 def test_initialization_with_previous_page(self, qtbot, mock_context_dl):
64 """Test initialization with previous page."""
65 previous = Mock()
66 page = DlExecutionPage(mock_context_dl, previous_page=previous)
67 qtbot.addWidget(page)
69 assert page.previous_page == previous
71 def test_ui_elements_created(self, qtbot, mock_context_dl):
72 """Test that all UI elements are created."""
73 page = DlExecutionPage(mock_context_dl)
74 qtbot.addWidget(page)
76 assert page.header is not None
77 assert page.current_operation is not None
78 assert page.progress_bar is not None
79 assert page.files_group is not None
80 assert page.files_list is not None
81 assert page.log_label is not None
82 assert page.log_text is not None
83 assert page.start_button is not None
84 assert page.cancel_button is not None
86 def test_initial_button_visibility(self, qtbot, mock_context_dl):
87 """Test initial button visibility."""
88 page = DlExecutionPage(mock_context_dl)
89 qtbot.addWidget(page)
91 assert page.start_button.isVisibleTo(page)
92 # cancel_button visibility depends on implementation
95class TestOnEnter:
96 """Tests for the on_enter method."""
98 def test_on_enter_resets_state(self, qtbot, mock_context_dl):
99 """Test that on_enter resets the state."""
100 page = DlExecutionPage(mock_context_dl)
101 qtbot.addWidget(page)
103 page.processing = True
104 page.processing_completed = True
106 page.on_enter()
108 assert page.processing is False
109 assert page.processing_completed is False
111 def test_on_enter_populates_file_list(self, qtbot, mock_context_dl):
112 """Test that on_enter populates the file list."""
113 page = DlExecutionPage(mock_context_dl)
114 qtbot.addWidget(page)
116 page.on_enter()
118 assert page.files_list.count() == 2
120 item0 = page.files_list.item(0).text()
121 item1 = page.files_list.item(1).text()
123 assert "sub-01_T1w.nii" in item0
124 assert "sub-02_T1w.nii" in item1
125 assert "Waiting" in item0 or "In attesa" in item0
126 assert "Waiting" in item1 or "In attesa" in item1
128 def test_on_enter_clears_previous_list(self, qtbot, mock_context_dl):
129 """Test that on_enter clears the previous list."""
130 page = DlExecutionPage(mock_context_dl)
131 qtbot.addWidget(page)
133 page.files_list.addItem("Old item 1")
134 page.files_list.addItem("Old item 2")
136 assert page.files_list.count() > 2
138 page.on_enter()
140 assert page.files_list.count() == 2
142 def test_on_enter_without_files_in_context(self, qtbot):
143 """Test on_enter without files in context."""
144 context = {"workspace_path": "/fake/path"}
145 page = DlExecutionPage(context)
146 qtbot.addWidget(page)
148 page.on_enter()
150 assert page.files_list.count() == 0
152 def test_on_enter_with_empty_file_list(self, qtbot, temp_workspace):
153 """Test on_enter with an empty file list."""
154 context = {
155 "workspace_path": temp_workspace,
156 "selected_segmentation_files": []
157 }
158 page = DlExecutionPage(context)
159 qtbot.addWidget(page)
161 page.on_enter()
163 assert page.files_list.count() == 0
166class TestStartProcessing:
167 """Tests for start_processing."""
169 def test_start_processing_basic(self, qtbot, mock_context_dl, mock_dl_worker):
170 """Test basic processing start."""
171 page = DlExecutionPage(mock_context_dl)
172 qtbot.addWidget(page)
174 page.start_processing()
176 mock_dl_worker.assert_called_once()
177 assert page.worker is not None
179 page.worker.start.assert_called_once()
181 assert page.processing is True
182 assert not page.start_button.isVisibleTo(page)
183 assert page.cancel_button.isVisibleTo(page)
184 assert page.progress_bar.isVisibleTo(page)
186 def test_start_processing_no_context(self, qtbot):
187 """Test start without context."""
188 page = DlExecutionPage(context=None)
189 qtbot.addWidget(page)
191 with patch.object(QMessageBox, 'warning') as mock_warning:
192 page.start_processing()
194 mock_warning.assert_called_once()
195 assert page.worker is None
197 def test_start_processing_no_files_in_context(self, qtbot, temp_workspace):
198 """Test start without files in context."""
199 context = {"workspace_path": temp_workspace}
200 page = DlExecutionPage(context)
201 qtbot.addWidget(page)
203 with patch.object(QMessageBox, 'warning') as mock_warning:
204 page.start_processing()
206 mock_warning.assert_called_once()
207 assert page.worker is None
209 def test_start_processing_empty_file_list(self, qtbot, temp_workspace):
210 """Test start with an empty file list."""
211 context = {
212 "workspace_path": temp_workspace,
213 "selected_segmentation_files": []
214 }
215 page = DlExecutionPage(context)
216 qtbot.addWidget(page)
218 with patch.object(QMessageBox, 'warning') as mock_warning:
219 page.start_processing()
221 mock_warning.assert_called_once()
222 assert page.worker is None
224 def test_start_processing_connects_signals(self, qtbot, mock_context_dl, mock_dl_worker):
225 """Test that signals are connected correctly."""
226 page = DlExecutionPage(mock_context_dl)
227 qtbot.addWidget(page)
229 page.start_processing()
231 page.worker.progressbar_update.connect.assert_called_once()
232 page.worker.file_update.connect.assert_called_once()
233 page.worker.log_update.connect.assert_called_once()
234 page.worker.finished.connect.assert_called_once()
236 def test_start_processing_worker_parameters(self, qtbot, mock_context_dl, mock_dl_worker):
237 """Test that the worker receives correct parameters."""
238 page = DlExecutionPage(mock_context_dl)
239 qtbot.addWidget(page)
241 page.start_processing()
243 call_kwargs = mock_dl_worker.call_args[1]
244 assert call_kwargs['input_files'] == mock_context_dl["selected_segmentation_files"]
245 assert call_kwargs['workspace_path'] == mock_context_dl["workspace_path"]
247 def test_start_processing_clears_log(self, qtbot, mock_context_dl, mock_dl_worker):
248 """Test that the log is cleared on start."""
249 page = DlExecutionPage(mock_context_dl)
250 qtbot.addWidget(page)
252 page.log_text.setText("Old log content")
254 page.start_processing()
256 log_content = page.log_text.toPlainText()
257 assert "Old log content" not in log_content
260class TestUpdateProgress:
261 """Tests for update_progress."""
263 def test_update_progress_basic(self, qtbot, mock_context_dl):
264 """Test basic progress update."""
265 page = DlExecutionPage(mock_context_dl)
266 qtbot.addWidget(page)
268 page.update_progress(50)
270 assert page.progress_bar.value == 50
272 def test_update_progress_multiple_values(self, qtbot, mock_context_dl):
273 """Test multiple updates."""
274 page = DlExecutionPage(mock_context_dl)
275 qtbot.addWidget(page)
277 for i in range(0, 101, 10):
278 page.update_progress(i)
279 assert page.progress_bar.value == i
281 def test_update_progress_zero(self, qtbot, mock_context_dl):
282 """Test with value 0."""
283 page = DlExecutionPage(mock_context_dl)
284 qtbot.addWidget(page)
286 page.update_progress(0)
287 assert page.progress_bar.value == 0
289 def test_update_progress_hundred(self, qtbot, mock_context_dl):
290 """Test with value 100."""
291 page = DlExecutionPage(mock_context_dl)
292 qtbot.addWidget(page)
294 page.update_progress(100)
295 assert page.progress_bar.value == 100
298class TestAddLogMessage:
299 """Tests for add_log_message."""
301 def test_add_log_message_info(self, qtbot, mock_context_dl, mock_logger):
302 """Test adding info message."""
303 page = DlExecutionPage(mock_context_dl)
304 qtbot.addWidget(page)
306 with patch("main.ui.dl_execution_page.log", mock_logger):
307 page.add_log_message("Test info message", 'i')
309 log_content = page.log_text.toPlainText()
310 assert "Test info message" in log_content
311 mock_logger.info.assert_called_once()
313 def test_add_log_message_error(self, qtbot, mock_context_dl, mock_logger):
314 """Test adding error message."""
315 page = DlExecutionPage(mock_context_dl)
316 qtbot.addWidget(page)
318 with patch("main.ui.dl_execution_page.log", mock_logger):
319 page.add_log_message("Test error message", 'e')
321 log_content = page.log_text.toPlainText()
322 assert "Test error message" in log_content
323 mock_logger.error.assert_called_once()
325 def test_add_log_message_warning(self, qtbot, mock_context_dl, mock_logger):
326 """Test adding warning message."""
327 page = DlExecutionPage(mock_context_dl)
328 qtbot.addWidget(page)
330 with patch("main.ui.dl_execution_page.log", mock_logger):
331 page.add_log_message("Test warning message", 'w')
333 log_content = page.log_text.toPlainText()
334 assert "Test warning message" in log_content
335 mock_logger.warning.assert_called_once()
337 def test_add_log_message_debug(self, qtbot, mock_context_dl, mock_logger):
338 """Test adding debug message."""
339 page = DlExecutionPage(mock_context_dl)
340 qtbot.addWidget(page)
342 with patch("main.ui.dl_execution_page.log", mock_logger):
343 page.add_log_message("Test debug message", 'd')
345 log_content = page.log_text.toPlainText()
346 assert "Test debug message" in log_content
347 mock_logger.debug.assert_called_once()
349 def test_add_log_message_includes_timestamp(self, qtbot, mock_context_dl):
350 """Test that the message includes a timestamp."""
351 page = DlExecutionPage(mock_context_dl)
352 qtbot.addWidget(page)
354 page.add_log_message("Test message", 'i')
356 log_content = page.log_text.toPlainText()
357 assert "[" in log_content
358 assert "]" in log_content
359 assert ":" in log_content # Timestamp format
361 def test_add_log_message_autoscroll(self, qtbot, mock_context_dl):
362 """Test auto-scroll after adding a message."""
363 page = DlExecutionPage(mock_context_dl)
364 qtbot.addWidget(page)
366 for i in range(50):
367 page.add_log_message(f"Message {i}", 'i')
369 scrollbar = page.log_text.verticalScrollBar()
370 assert scrollbar.value() == scrollbar.maximum()
372 def test_add_log_message_multiple(self, qtbot, mock_context_dl):
373 """Test multiple messages."""
374 page = DlExecutionPage(mock_context_dl)
375 qtbot.addWidget(page)
377 messages = ["First", "Second", "Third"]
378 for msg in messages:
379 page.add_log_message(msg, 'i')
381 log_content = page.log_text.toPlainText()
382 for msg in messages:
383 assert msg in log_content
386class TestUpdateFileStatus:
387 """Tests for update_file_status."""
389 def test_update_file_status_basic(self, qtbot, mock_context_dl):
390 """Test file status update."""
391 page = DlExecutionPage(mock_context_dl)
392 qtbot.addWidget(page)
393 page.on_enter()
395 page.update_file_status("sub-01_T1w.nii", "Processing")
397 item = page.files_list.item(0)
398 assert "Processing" in item.text()
399 assert "sub-01_T1w.nii" in item.text()
401 def test_update_file_status_completed(self, qtbot, mock_context_dl):
402 """Test completed status."""
403 page = DlExecutionPage(mock_context_dl)
404 qtbot.addWidget(page)
405 page.on_enter()
407 page.update_file_status("sub-01_T1w.nii", "Completed")
409 item = page.files_list.item(0)
410 assert "Completed" in item.text()
412 def test_update_file_status_error(self, qtbot, mock_context_dl):
413 """Test error status."""
414 page = DlExecutionPage(mock_context_dl)
415 qtbot.addWidget(page)
416 page.on_enter()
418 page.update_file_status("sub-01_T1w.nii", "Error")
420 item = page.files_list.item(0)
421 assert "Error" in item.text()
423 def test_update_file_status_nonexistent_file(self, qtbot, mock_context_dl):
424 """Test updating a file not in the list."""
425 page = DlExecutionPage(mock_context_dl)
426 qtbot.addWidget(page)
427 page.on_enter()
429 initial_count = page.files_list.count()
431 page.update_file_status("nonexistent.nii", "Processing")
433 assert page.files_list.count() == initial_count
435 def test_update_file_status_multiple_updates(self, qtbot, mock_context_dl):
436 """Test multiple updates for the same file."""
437 page = DlExecutionPage(mock_context_dl)
438 qtbot.addWidget(page)
439 page.on_enter()
441 filename = "sub-01_T1w.nii"
443 page.update_file_status(filename, "Processing")
444 item = page.files_list.item(0)
445 assert "Processing" in item.text()
447 page.update_file_status(filename, "Completed")
448 item = page.files_list.item(0)
449 assert "Completed" in item.text()
450 assert "Processing" not in item.text()
453class TestCancelProcessing:
454 """Tests for cancel_processing."""
456 def test_cancel_processing_with_worker(self, qtbot, mock_context_dl, mock_dl_worker):
457 """Test cancellation with an active worker."""
458 page = DlExecutionPage(mock_context_dl)
459 qtbot.addWidget(page)
461 page.start_processing()
463 page.cancel_processing()
465 page.worker.cancel_requested.emit.assert_called_once()
467 def test_cancel_processing_without_worker(self, qtbot, mock_context_dl):
468 """Test cancellation without a worker."""
469 page = DlExecutionPage(mock_context_dl)
470 qtbot.addWidget(page)
472 page.cancel_processing()
474 assert page.worker is None
476 def test_cancel_processing_adds_log(self, qtbot, mock_context_dl, mock_dl_worker):
477 """Test that cancellation adds a log entry."""
478 page = DlExecutionPage(mock_context_dl)
479 qtbot.addWidget(page)
481 page.start_processing()
482 page.cancel_processing()
484 log_content = page.log_text.toPlainText()
485 assert "Cancellation requested" in log_content or "Annullamento richiesto" in log_content
488class TestProcessingFinished:
489 """Tests for processing_finished."""
491 def test_processing_finished_success(self, qtbot, mock_context_dl):
492 """Test successful completion."""
493 page = DlExecutionPage(mock_context_dl)
494 qtbot.addWidget(page)
496 with patch.object(QMessageBox, 'information') as mock_info:
497 page.processing_finished(True, "All files processed successfully")
499 assert page.processing is False
500 assert page.processing_completed is True
501 assert page.start_button.isVisibleTo(page)
502 assert not page.cancel_button.isVisibleTo(page)
503 assert not page.progress_bar.isVisibleTo(page)
505 mock_info.assert_called_once()
506 mock_context_dl["update_main_buttons"].assert_called_once()
508 def test_processing_finished_failure(self, qtbot, mock_context_dl):
509 """Test completion with failure."""
510 page = DlExecutionPage(mock_context_dl)
511 qtbot.addWidget(page)
513 with patch.object(QMessageBox, 'critical') as mock_critical:
514 page.processing_finished(False, "Processing failed")
516 assert page.processing is False
517 assert page.processing_completed is True
519 mock_critical.assert_called_once()
521 def test_processing_finished_updates_context(self, qtbot, mock_context_dl):
522 """Test that the context is updated."""
523 page = DlExecutionPage(mock_context_dl)
524 qtbot.addWidget(page)
526 with patch.object(QMessageBox, 'information'):
527 page.processing_finished(True, "Success")
529 assert "processing_output_dir" in mock_context_dl
531 def test_processing_finished_current_operation_text(self, qtbot, mock_context_dl):
532 """Test current operation text update."""
533 page = DlExecutionPage(mock_context_dl)
534 qtbot.addWidget(page)
536 with patch.object(QMessageBox, 'information'):
537 page.processing_finished(True, "Success")
539 assert "completed" in page.current_operation.text().lower() or "✓" in page.current_operation.text()
541 page2 = DlExecutionPage(mock_context_dl)
542 qtbot.addWidget(page2)
544 with patch.object(QMessageBox, 'critical'):
545 page2.processing_finished(False, "Error")
547 assert "failed" in page2.current_operation.text().lower() or "✗" in page2.current_operation.text()
549 def test_processing_finished_reprocess_button(self, qtbot, mock_context_dl):
550 """Test that the button becomes 'Reprocess' after success."""
551 page = DlExecutionPage(mock_context_dl)
552 qtbot.addWidget(page)
554 with patch.object(QMessageBox, 'information'):
555 page.processing_finished(True, "Success")
557 assert "Reprocess" in page.start_button.text()
560class TestResetProcessingState:
561 """Tests for reset_processing_state."""
563 def test_reset_processing_state_basic(self, qtbot, mock_context_dl):
564 """Test basic state reset."""
565 page = DlExecutionPage(mock_context_dl)
566 qtbot.addWidget(page)
568 page.processing = True
569 page.processing_completed = True
570 page.worker = Mock()
572 page.reset_processing_state()
574 assert page.processing is False
575 assert page.processing_completed is False
576 assert page.worker is None
577 assert page.start_button.isVisibleTo(page)
578 assert not page.cancel_button.isVisibleTo(page)
579 assert not page.progress_bar.isVisibleTo(page)
581 def test_reset_processing_state_button_text(self, qtbot, mock_context_dl):
582 """Test button text reset."""
583 page = DlExecutionPage(mock_context_dl)
584 qtbot.addWidget(page)
586 page.start_button.setText("Reprocess")
588 page.reset_processing_state()
590 assert "Start" in page.start_button.text() or "Avvio deep learning" in page.start_button.text()
593class TestBackNavigation:
594 """Tests for back navigation."""
596 def test_back_not_processing(self, qtbot, mock_context_dl):
597 """Test back when not processing."""
598 previous = Mock()
599 page = DlExecutionPage(mock_context_dl, previous_page=previous)
600 qtbot.addWidget(page)
602 result = page.back()
604 assert result == previous
605 previous.on_enter.assert_called_once()
607 def test_back_while_processing_cancelled(self, qtbot, mock_context_dl, mock_dl_worker):
608 """Test back during processing with cancellation."""
609 previous = Mock()
610 page = DlExecutionPage(mock_context_dl, previous_page=previous)
611 qtbot.addWidget(page)
613 page.start_processing()
615 with patch.object(QMessageBox, 'question', return_value=QMessageBox.StandardButton.Yes):
616 result = page.back()
618 page.worker.cancel_requested.emit.assert_called()
619 assert result == previous
621 def test_back_while_processing_not_cancelled(self, qtbot, mock_context_dl, mock_dl_worker):
622 """Test back during processing without cancellation."""
623 previous = Mock()
624 page = DlExecutionPage(mock_context_dl, previous_page=previous)
625 qtbot.addWidget(page)
627 page.start_processing()
629 with patch.object(QMessageBox, 'question', return_value=QMessageBox.StandardButton.No):
630 result = page.back()
632 assert result is None
633 previous.on_enter.assert_not_called()
635 def test_back_no_previous_page(self, qtbot, mock_context_dl):
636 """Test back without previous_page."""
637 page = DlExecutionPage(mock_context_dl)
638 qtbot.addWidget(page)
640 result = page.back()
642 assert result is None
645class TestNextNavigation:
646 """Tests for next navigation."""
648 def test_next_returns_none(self, qtbot, mock_context_dl):
649 """Test that next always returns None."""
650 page = DlExecutionPage(mock_context_dl)
651 qtbot.addWidget(page)
653 result = page.next(mock_context_dl)
655 assert result is None
658class TestReadyToAdvance:
659 """Tests for is_ready_to_advance."""
661 def test_is_ready_to_advance_always_false(self, qtbot, mock_context_dl):
662 """Test that is_ready_to_advance always returns False."""
663 page = DlExecutionPage(mock_context_dl)
664 qtbot.addWidget(page)
666 assert page.is_ready_to_advance() is False
668 # Even after completion
669 page.processing_completed = True
670 assert page.is_ready_to_advance() is False
673class TestReadyToGoBack:
674 """Tests for is_ready_to_go_back."""
676 def test_is_ready_to_go_back_always_true(self, qtbot, mock_context_dl):
677 """Test that is_ready_to_go_back always returns True."""
678 page = DlExecutionPage(mock_context_dl)
679 qtbot.addWidget(page)
681 assert page.is_ready_to_go_back() is True
683 # Even during processing
684 page.processing = True
685 assert page.is_ready_to_go_back() is True
688class TestTranslation:
689 """Tests for translations."""
691 def test_translate_ui_called_on_init(self, qtbot, mock_context_dl):
692 """Test that _translate_ui is called during init."""
693 with patch.object(DlExecutionPage, '_translate_ui') as mock_translate:
694 page = DlExecutionPage(mock_context_dl)
695 qtbot.addWidget(page)
697 mock_translate.assert_called()
699 def test_translate_ui_updates_labels(self, qtbot, mock_context_dl):
700 """Test that _translate_ui updates labels."""
701 page = DlExecutionPage(mock_context_dl)
702 qtbot.addWidget(page)
704 page._translate_ui()
706 assert page.header.text() is not None
707 assert page.current_operation.text() is not None
708 assert page.files_group.title() is not None
709 assert page.log_label.text() is not None
710 assert page.start_button.text() is not None
711 assert page.cancel_button.text() is not None
713 def test_translate_ui_repopulates_file_list(self, qtbot, mock_context_dl):
714 """Test that _translate_ui repopulates the file list."""
715 page = DlExecutionPage(mock_context_dl)
716 qtbot.addWidget(page)
718 page.on_enter()
719 initial_count = page.files_list.count()
721 page._translate_ui()
723 assert page.files_list.count() == initial_count
726class TestEdgeCases:
727 """Tests for edge cases."""
729 def test_rapid_start_cancel(self, qtbot, mock_context_dl, mock_dl_worker):
730 """Test rapid start and cancel."""
731 page = DlExecutionPage(mock_context_dl)
732 qtbot.addWidget(page)
734 for _ in range(5):
735 page.start_processing()
736 page.cancel_processing()
738 assert True
740 def test_multiple_processing_finished_calls(self, qtbot, mock_context_dl):
741 """Test multiple calls to processing_finished."""
742 page = DlExecutionPage(mock_context_dl)
743 qtbot.addWidget(page)
745 with patch.object(QMessageBox, 'information'):
746 page.processing_finished(True, "First")
747 page.processing_finished(True, "Second")
749 assert page.processing_completed is True
751 def test_update_file_status_empty_list(self, qtbot, mock_context_dl):
752 """Test update_file_status with an empty list."""
753 page = DlExecutionPage(mock_context_dl)
754 qtbot.addWidget(page)
756 page.update_file_status("file.nii", "Processing")
758 assert page.files_list.count() == 2
760 def test_very_long_log_messages(self, qtbot, mock_context_dl):
761 """Test with very long log messages."""
762 page = DlExecutionPage(mock_context_dl)
763 qtbot.addWidget(page)
765 long_message = "A" * 10000
766 page.add_log_message(long_message, 'i')
768 log_content = page.log_text.toPlainText()
769 assert long_message in log_content
771 def test_unicode_in_filenames(self, qtbot, temp_workspace):
772 """Test with unicode characters in filenames."""
773 context = {
774 "workspace_path": temp_workspace,
775 "selected_segmentation_files": [
776 os.path.join(temp_workspace, "sub-àèéìòù", "anat", "file.nii"),
777 os.path.join(temp_workspace, "sub-中文", "anat", "file.nii")
778 ],
779 "update_main_buttons": Mock()
780 }
782 page = DlExecutionPage(context)
783 qtbot.addWidget(page)
785 page.on_enter()
787 assert page.files_list.count() == 2
789 def test_progress_value_out_of_range(self, qtbot, mock_context_dl):
790 """Test with progress values out of range."""
791 page = DlExecutionPage(mock_context_dl)
792 qtbot.addWidget(page)
794 page.update_progress(-10)
795 page.update_progress(150)
797 assert True
799 def test_concurrent_file_updates(self, qtbot, mock_context_dl):
800 """Test concurrent file updates."""
801 page = DlExecutionPage(mock_context_dl)
802 qtbot.addWidget(page)
803 page.on_enter()
805 for i in range(100):
806 page.update_file_status("sub-01_T1w.nii", f"Status {i}")
807 page.update_file_status("sub-02_T1w.nii", f"Status {i}")
809 assert True
812class TestUIInteraction:
813 """Tests for UI interaction."""
815 def test_button_styling(self, qtbot, mock_context_dl):
816 """Test that buttons have the correct style."""
817 page = DlExecutionPage(mock_context_dl)
818 qtbot.addWidget(page)
820 start_style = page.start_button.styleSheet()
821 cancel_style = page.cancel_button.styleSheet()
823 assert "background-color" in start_style
824 assert "background-color" in cancel_style
826 def test_log_text_readonly(self, qtbot, mock_context_dl):
827 """Test that the log is read-only."""
828 page = DlExecutionPage(mock_context_dl)
829 qtbot.addWidget(page)
831 assert page.log_text.isReadOnly()
833 def test_files_list_max_height(self, qtbot, mock_context_dl):
834 """Test that the file list has a maximum height."""
835 page = DlExecutionPage(mock_context_dl)
836 qtbot.addWidget(page)
838 assert page.files_list.maximumHeight() == 150
840 def test_log_text_max_height(self, qtbot, mock_context_dl):
841 """Test that the log has a maximum height."""
842 page = DlExecutionPage(mock_context_dl)
843 qtbot.addWidget(page)
845 assert page.log_text.maximumHeight() == 200
847 def test_header_alignment(self, qtbot, mock_context_dl):
848 """Test that the header is centered."""
849 page = DlExecutionPage(mock_context_dl)
850 qtbot.addWidget(page)
852 assert page.header.alignment() == Qt.AlignmentFlag.AlignCenter
854 def test_current_operation_alignment(self, qtbot, mock_context_dl):
855 """Test that current_operation is centered."""
856 page = DlExecutionPage(mock_context_dl)
857 qtbot.addWidget(page)
859 assert page.current_operation.alignment() == Qt.AlignmentFlag.AlignCenter
862class TestWorkerIntegration:
863 """Tests for worker integration."""
865 def test_worker_signals_connected(self, qtbot, mock_context_dl, mock_dl_worker):
866 """Test that all worker signals are connected."""
867 page = DlExecutionPage(mock_context_dl)
868 qtbot.addWidget(page)
870 page.start_processing()
872 assert page.worker.progressbar_update.connect.called
873 assert page.worker.file_update.connect.called
874 assert page.worker.log_update.connect.called
875 assert page.worker.finished.connect.called
877 def test_worker_created_with_correct_parameters(self, qtbot, mock_context_dl, mock_dl_worker):
878 """Test that the worker is created with correct parameters."""
879 page = DlExecutionPage(mock_context_dl)
880 qtbot.addWidget(page)
882 page.start_processing()
884 call_kwargs = mock_dl_worker.call_args[1]
886 assert 'input_files' in call_kwargs
887 assert 'workspace_path' in call_kwargs
888 assert call_kwargs['workspace_path'] == mock_context_dl['workspace_path']
890 def test_worker_start_called(self, qtbot, mock_context_dl, mock_dl_worker):
891 """Test that worker.start() is called."""
892 page = DlExecutionPage(mock_context_dl)
893 qtbot.addWidget(page)
895 page.start_processing()
897 page.worker.start.assert_called_once()
900class TestContextHandling:
901 """Tests for context handling."""
903 def test_context_none_handling(self, qtbot):
904 """Test handling None context."""
905 page = DlExecutionPage(context=None)
906 qtbot.addWidget(page)
908 page.on_enter()
910 with patch.object(QMessageBox, 'warning'):
911 page.start_processing()
913 def test_context_without_workspace_path(self, qtbot):
914 """Test context without workspace_path."""
915 context = {"selected_segmentation_files": ["file.nii"]}
916 page = DlExecutionPage(context)
917 qtbot.addWidget(page)
919 with patch.object(QMessageBox, 'information'):
920 page.processing_finished(True, "Success")
922 def test_context_without_update_main_buttons(self, qtbot, temp_workspace):
923 """Test context without update_main_buttons."""
924 context = {
925 "workspace_path": temp_workspace,
926 "selected_segmentation_files": ["file.nii"]
927 }
928 page = DlExecutionPage(context)
929 qtbot.addWidget(page)
931 with patch.object(QMessageBox, 'information'):
932 page.processing_finished(True, "Success")
934 def test_processing_output_dir_set(self, qtbot, mock_context_dl):
935 """Test that processing_output_dir is set in the context."""
936 page = DlExecutionPage(mock_context_dl)
937 qtbot.addWidget(page)
939 with patch.object(QMessageBox, 'information'):
940 page.processing_finished(True, "Success")
942 assert "processing_output_dir" in mock_context_dl
943 expected_dir = os.path.join(mock_context_dl["workspace_path"], "outputs")
944 assert mock_context_dl["processing_output_dir"] == expected_dir
947class TestStateTransitions:
948 """Tests for state transitions."""
950 def test_state_idle_to_processing(self, qtbot, mock_context_dl, mock_dl_worker):
951 """Test transition from idle to processing."""
952 page = DlExecutionPage(mock_context_dl)
953 qtbot.addWidget(page)
955 assert page.processing is False
957 page.start_processing()
959 assert page.processing is True
960 assert not page.start_button.isVisibleTo(page)
961 assert page.cancel_button.isVisibleTo(page)
963 def test_state_processing_to_completed(self, qtbot, mock_context_dl, mock_dl_worker):
964 """Test transition from processing to completed."""
965 page = DlExecutionPage(mock_context_dl)
966 qtbot.addWidget(page)
968 page.start_processing()
969 assert page.processing is True
971 with patch.object(QMessageBox, 'information'):
972 page.processing_finished(True, "Success")
974 assert page.processing is False
975 assert page.processing_completed is True
976 assert page.start_button.isVisibleTo(page)
978 def test_state_processing_to_cancelled(self, qtbot, mock_context_dl, mock_dl_worker):
979 """Test transition from processing to cancelled."""
980 page = DlExecutionPage(mock_context_dl)
981 qtbot.addWidget(page)
983 page.start_processing()
984 page.cancel_processing()
986 with patch.object(QMessageBox, 'critical'):
987 page.processing_finished(False, "Cancelled")
989 assert page.processing is False
991 def test_state_completed_to_reset(self, qtbot, mock_context_dl, mock_dl_worker):
992 """Test transition from completed to reset."""
993 page = DlExecutionPage(mock_context_dl)
994 qtbot.addWidget(page)
996 page.start_processing()
998 with patch.object(QMessageBox, 'information'):
999 page.processing_finished(True, "Success")
1001 assert page.processing_completed is True
1003 page.reset_processing_state()
1005 assert page.processing is False
1006 assert page.processing_completed is False
1009class TestIntegration:
1010 """Integration tests for complete flows."""
1012 def test_full_processing_flow_success(self, qtbot, mock_context_dl, mock_dl_worker):
1013 """Test full flow: start -> processing -> success."""
1014 page = DlExecutionPage(mock_context_dl)
1015 qtbot.addWidget(page)
1017 page.on_enter()
1018 assert page.files_list.count() == 2
1020 page.start_processing()
1021 assert page.processing is True
1022 assert page.worker is not None
1024 page.update_progress(25)
1025 page.update_file_status("sub-01_T1w.nii", "Processing")
1026 page.add_log_message("Processing file 1", 'i')
1028 page.update_progress(50)
1029 page.update_file_status("sub-01_T1w.nii", "Completed")
1030 page.update_file_status("sub-02_T1w.nii", "Processing")
1032 page.update_progress(100)
1033 page.update_file_status("sub-02_T1nii", "Completed")
1035 with patch.object(QMessageBox, 'information'):
1036 page.processing_finished(True, "All files processed")
1038 assert page.processing_completed is True
1039 assert not page.processing
1041 def test_full_processing_flow_with_cancel(self, qtbot, mock_context_dl, mock_dl_worker):
1042 """Test flow with cancellation."""
1043 page = DlExecutionPage(mock_context_dl)
1044 qtbot.addWidget(page)
1046 page.on_enter()
1047 page.start_processing()
1049 page.update_progress(30)
1050 page.add_log_message("Processing...", 'i')
1052 page.cancel_processing()
1054 with patch.object(QMessageBox, 'critical'):
1055 page.processing_finished(False, "Cancelled by user")
1057 assert not page.processing
1058 assert page.processing_completed
1060 def test_full_processing_flow_with_error(self, qtbot, mock_context_dl, mock_dl_worker):
1061 """Test flow with error."""
1062 page = DlExecutionPage(mock_context_dl)
1063 qtbot.addWidget(page)
1065 page.on_enter()
1066 page.start_processing()
1068 page.update_progress(20)
1069 page.update_file_status("sub-01_T1w.nii", "Error")
1070 page.add_log_message("Error processing file", 'e')
1072 with patch.object(QMessageBox, 'critical'):
1073 page.processing_finished(False, "Processing error")
1075 assert not page.processing
1076 assert page.processing_completed
1078 def test_reprocess_flow(self, qtbot, mock_context_dl, mock_dl_worker):
1079 """Test flow: processing -> completed -> reprocess."""
1080 page = DlExecutionPage(mock_context_dl)
1081 qtbot.addWidget(page)
1083 page.on_enter()
1084 page.start_processing()
1086 with patch.object(QMessageBox, 'information'):
1087 page.processing_finished(True, "Success")
1089 assert "Reprocess" in page.start_button.text()
1091 page.reset_processing_state()
1092 page.on_enter()
1094 mock_dl_worker.reset_mock()
1095 page.start_processing()
1097 assert page.processing is True
1098 mock_dl_worker.assert_called()
1101class TestErrorHandling:
1102 """Tests for error handling."""
1104 def test_start_processing_handles_worker_creation_error(self, qtbot, mock_context_dl):
1105 """Test worker creation error handling."""
1106 page = DlExecutionPage(mock_context_dl)
1107 qtbot.addWidget(page)
1109 with patch("main.ui.dl_execution_page.DlWorker") as mock_worker:
1110 mock_worker.side_effect = Exception("Worker creation failed")
1112 try:
1113 page.start_processing()
1114 except Exception:
1115 pass
1117 def test_processing_finished_without_workspace_path(self, qtbot):
1118 """Test processing_finished without workspace_path."""
1119 context = {"selected_segmentation_files": ["file.nii"]}
1120 page = DlExecutionPage(context)
1121 qtbot.addWidget(page)
1123 with patch.object(QMessageBox, 'information'):
1124 page.processing_finished(True, "Success")
1127class TestBoundaryConditions:
1128 """Tests for boundary conditions."""
1130 def test_many_files_in_list(self, qtbot, temp_workspace):
1131 """Test with many files in the list."""
1132 files = [os.path.join(temp_workspace, f"sub-{i:03d}", "anat", "file.nii")
1133 for i in range(100)]
1135 context = {
1136 "workspace_path": temp_workspace,
1137 "selected_segmentation_files": files,
1138 "update_main_buttons": Mock()
1139 }
1141 page = DlExecutionPage(context)
1142 qtbot.addWidget(page)
1144 page.on_enter()
1146 assert page.files_list.count() == 100
1148 def test_empty_filename(self, qtbot, mock_context_dl):
1149 """Test with empty filename."""
1150 page = DlExecutionPage(mock_context_dl)
1151 qtbot.addWidget(page)
1152 page.on_enter()
1154 page.update_file_status("", "Processing")
1156 def test_very_long_filename(self, qtbot, temp_workspace):
1157 """Test with very long filename."""
1158 long_name = "a" * 500 + ".nii"
1159 context = {
1160 "workspace_path": temp_workspace,
1161 "selected_segmentation_files": [os.path.join(temp_workspace, long_name)],
1162 "update_main_buttons": Mock()
1163 }
1165 page = DlExecutionPage(context)
1166 qtbot.addWidget(page)
1168 page.on_enter()
1170 assert page.files_list.count() == 1
1172 def test_rapid_log_messages(self, qtbot, mock_context_dl):
1173 """Test with very rapid log messages."""
1174 page = DlExecutionPage(mock_context_dl)
1175 qtbot.addWidget(page)
1177 for i in range(1000):
1178 page.add_log_message(f"Message {i}", 'i')
1180 assert "Message 999" in page.log_text.toPlainText()
1183class TestMemoryAndCleanup:
1184 """Tests for memory management and cleanup."""
1186 def test_worker_cleanup_after_finish(self, qtbot, mock_context_dl, mock_dl_worker):
1187 """Test that the worker is cleaned up after finish."""
1188 page = DlExecutionPage(mock_context_dl)
1189 qtbot.addWidget(page)
1191 page.start_processing()
1192 assert page.worker is not None
1194 page.reset_processing_state()
1195 assert page.worker is None
1197 def test_multiple_processing_cycles(self, qtbot, mock_context_dl, mock_dl_worker):
1198 """Test multiple processing cycles."""
1199 page = DlExecutionPage(mock_context_dl)
1200 qtbot.addWidget(page)
1202 for cycle in range(3):
1203 page.on_enter()
1204 page.start_processing()
1206 with patch.object(QMessageBox, 'information'):
1207 page.processing_finished(True, f"Cycle {cycle} complete")
1209 page.reset_processing_state()
1211 assert True
1214class TestDocumentation:
1215 """Tests for documentation."""
1217 def test_class_docstring(self):
1218 """Test that the class has a docstring."""
1219 assert DlExecutionPage.__doc__ is not None
1221 def test_on_enter_docstring(self, qtbot, mock_context_dl):
1222 """Test that on_enter has a docstring."""
1223 page = DlExecutionPage(mock_context_dl)
1224 qtbot.addWidget(page)
1226 assert page.on_enter.__doc__ is not None
1229class TestAccessibility:
1230 """Tests for accessibility."""
1232 def test_buttons_have_text(self, qtbot, mock_context_dl):
1233 """Test that buttons have text."""
1234 page = DlExecutionPage(mock_context_dl)
1235 qtbot.addWidget(page)
1237 assert len(page.start_button.text()) > 0
1238 assert len(page.cancel_button.text()) > 0
1240 def test_labels_have_text(self, qtbot, mock_context_dl):
1241 """Test that labels have text."""
1242 page = DlExecutionPage(mock_context_dl)
1243 qtbot.addWidget(page)
1245 assert len(page.header.text()) > 0
1246 assert len(page.current_operation.text()) > 0
1247 assert len(page.log_label.text()) > 0