def test_that_process_selected_does_nothing_if_no_states_selected(self):
        presenter = RunTabPresenter(SANSFacility.ISIS)
        view = mock.MagicMock()
        view.get_selected_rows = mock.MagicMock(return_value=[])
        presenter.set_view(view)
        presenter._process_rows = mock.MagicMock()

        presenter.on_process_selected_clicked()
        self.assertEqual(
            presenter._process_rows.call_count, 0,
            "Expected presenter._process_rows to not have been called. Called {} times."
            .format(presenter._process_rows.call_count))
    def test_that_process_selected_does_nothing_if_no_states_selected(self):
        presenter = RunTabPresenter(SANSFacility.ISIS)
        view = mock.MagicMock()
        view.get_selected_rows = mock.MagicMock(return_value=[])
        presenter.set_view(view)
        presenter._process_rows = mock.MagicMock()

        presenter.on_process_selected_clicked()
        self.assertEqual(
            presenter._process_rows.call_count, 0,
            "Expected presenter._process_rows to not have been called. Called {} times.".format(
                presenter._process_rows.call_count))
Beispiel #3
0
    def test_that_process_selected_only_processes_selected_rows(self):
        # Naive test. Doesn't check that we are processing the correct processed rows,
        # just that we are processing the same number of rows as we have selected.
        # This would only really fail if on_process_selected_clicked and on_process_all_clicked 
        # get muddled-up
        presenter = RunTabPresenter(SANSFacility.ISIS)
        view = mock.MagicMock()
        view.get_selected_rows = mock.MagicMock(return_value=[0, 3, 4])
        
        presenter.set_view(view)
        presenter._table_model.reset_row_state = mock.MagicMock()

        presenter.on_process_selected_clicked()
        self.assertEqual(
            presenter._table_model.reset_row_state.call_count, 3,
            "Expected reset_row_state to have been called 3 times. Called {} times.".format(
                presenter._table_model.reset_row_state.call_count))
    def test_that_process_selected_ignores_all_empty_rows(self):
        presenter = RunTabPresenter(SANSFacility.ISIS)
        view = mock.MagicMock()
        view.get_selected_rows = mock.MagicMock(return_value=[0, 1])
        presenter.set_view(view)

        table_model = TableModel()
        row_entry0 = [''] * 16
        row_entry1 = [
            '74040', '', '74040', '', '74040', '', '74040', '', '74040', '',
            '74040', '', 'test_reduction', 'user_file', '1.2', ''
        ]
        table_model.add_table_entry(0, TableIndexModel(*row_entry0))
        table_model.add_table_entry(1, TableIndexModel(*row_entry1))

        presenter._table_model = table_model
        presenter._process_rows = mock.MagicMock()

        presenter.on_process_selected_clicked()
        presenter._process_rows.assert_called_with([1])
class RunTabPresenterTest(unittest.TestCase):
    def setUp(self):
        # Backup properties that the tests or GUI may change
        self._backup_facility = config["default.facility"]
        self._backup_instrument = config["default.instrument"]
        self._backup_datasearch_dirs = config["datasearch.directories"]
        self._backup_save_dir = config["defaultsave.directory"]

        config["default.facility"] = "ISIS"

        self._mock_model = mock.create_autospec(StateGuiModel, spec_set=True)
        self.mock_run_tab_model = mock.create_autospec(RunTabModel(),
                                                       spec_set=True)
        self._mock_table = mock.create_autospec(TableModel, spec_set=True)
        self._mock_csv_parser = mock.create_autospec(BatchCsvParser,
                                                     spec_set=True)
        self._mock_view = mock.Mock()

        self.view_observers = SansGuiObservable()
        self._mock_view.get_observable = mock.Mock(
            return_value=self.view_observers)

        self._mock_model.instrument = SANSInstrument.SANS2D
        # TODO, this top level presenter should not be creating sub presenters, instead we should use
        # TODO  an observer pattern and common interface to exchange messages. However for the moment
        # TODO  we will skip patching each
        self.presenter = RunTabPresenter(SANSFacility.ISIS,
                                         run_tab_model=self.mock_run_tab_model,
                                         model=self._mock_model,
                                         table_model=self._mock_table,
                                         view=self._mock_view)

        # The beam centre presenter will run QThreads which leads to flaky tests, so mock out
        self.presenter._beam_centre_presenter = mock.Mock()
        self.presenter._masking_table_presenter = mock.Mock()
        self.presenter._workspace_diagnostic_presenter = mock.Mock()

        self.presenter._csv_parser = self._mock_csv_parser
        # Allows us to use mock objs as the method tries to directly use int/floats
        self.presenter.update_view_from_table_model = mock.Mock()

    def tearDown(self):
        config["default.facility"] = self._backup_facility
        config["default.instrument"] = self._backup_instrument
        config["datasearch.directories"] = self._backup_datasearch_dirs
        config["defaultsave.directory2"] = self._backup_save_dir

    def test_that_will_load_user_file(self):
        # Setup self.presenter.and mock view
        user_file_path = create_user_file(sample_user_file)
        view, settings_diagnostic_tab, _ = create_mock_view(user_file_path)

        self.presenter.set_view(view)

        # Mock out methods which should be called
        self.presenter._beam_centre_presenter = mock.Mock()
        self.presenter._masking_table_presenter = mock.Mock()
        self.presenter.update_view_from_model = mock.Mock()
        self.presenter._workspace_diagnostic_presenter = mock.Mock()

        with mock.patch(
                "sans.gui_logic.presenter.run_tab_presenter.FileLoading"
        ) as mocked_loader:
            mocked_loader.load_user_file.return_value = AllStates()
            self.presenter.on_user_file_load()
            mocked_loader.load_user_file.assert_called_once_with(
                file_path=user_file_path, file_information=mock.ANY)

        self.presenter._beam_centre_presenter.copy_centre_positions.assert_called(
        )
        self.presenter._beam_centre_presenter.update_centre_positions.assert_called(
        )
        self.presenter._masking_table_presenter.on_update_rows.assert_called()
        self.presenter._workspace_diagnostic_presenter.on_user_file_loadassert_called(
        )

        self.presenter.update_view_from_model.assert_called()

        # Assert
        # Note that the event slices are not set in the user file

        # clean up
        remove_file(user_file_path)

    @mock.patch("sans.gui_logic.presenter.run_tab_presenter.FileLoading")
    @mock.patch("sans.gui_logic.presenter.run_tab_presenter.FileFinder")
    def test_user_file_loading_handles_exceptions(self, _, mocked_loader):
        for e in [UserFileLoadException("e"), TomlValidationError("e")]:
            self._mock_view.display_message_box.reset_mock()
            self._mock_view.on_user_file_load_failure.reset_mock()

            mocked_loader.load_user_file.side_effect = e
            self.presenter.on_user_file_load()

            self._mock_view.on_user_file_load_failure.assert_called_once()
            self._mock_view.display_message_box.assert_called_once_with(
                mock.ANY, mock.ANY, "e")

    def test_that_checks_default_user_file(self):
        # Setup self.presenter.and mock view
        view, settings_diagnostic_tab, _ = create_mock_view("")

        self.presenter.set_view(view)

        self.assertEqual(
            self.presenter._view.set_out_default_user_file.call_count, 1,
            "Expected mock to have been called once. Called {} times.".format(
                self.presenter._view.set_out_default_user_file.call_count))

        self.assertEqual(
            self.presenter._view._call_settings_listeners.call_count, 0,
            "Expected mock to not have been called. Called {} times.".format(
                self.presenter._view._call_settings_listeners.call_count))

    def test_fails_silently_when_user_file_does_not_exist(self):
        view, _, _ = create_mock_view("non_existent_user_file")
        self.presenter.set_view(view)

        self.assertIsNone(self.presenter.on_user_file_load())

    def test_file_information_exceptions_handled(self):
        for e in [ValueError, RuntimeError, OSError]:
            mocked_row = mock.Mock()
            type(mocked_row).file_information = mock.PropertyMock(
                side_effect=e)
            exception_rows = [mocked_row, mocked_row]
            self._mock_table.get_non_empty_rows = mock.Mock(
                return_value=exception_rows)

            self.presenter.on_update_rows()  # should not throw

    def test_that_gets_states_from_view(self):
        # Arrange
        batch_file_path, user_file_path, _ = self._get_files_and_mock_presenter(
            BATCH_FILE_TEST_CONTENT_2)
        self.presenter.on_update_rows = mock.Mock()
        self.presenter.on_user_file_load()
        self.presenter.on_batch_file_load()

        # Act
        states, errors = self.presenter.get_states(
            row_entries=BATCH_FILE_TEST_CONTENT_2)

        # Assert
        self.assertEqual(len(states), 2)

        # Check state 0
        state0 = states[BATCH_FILE_TEST_CONTENT_2[0]]
        self.assertEqual(state0.all_states.data.sample_scatter,
                         "SANS2D00022024")
        self.assertEqual(state0.all_states.data.sample_transmission,
                         "SANS2D00022048")
        self.assertEqual(state0.all_states.data.sample_direct,
                         "SANS2D00022048")
        self.assertEqual(state0.all_states.data.can_scatter, None)
        self.assertEqual(state0.all_states.data.can_transmission, None)
        self.assertEqual(state0.all_states.data.can_direct, None)

        # Check state 1
        state1 = states[BATCH_FILE_TEST_CONTENT_2[1]]
        self.assertEqual(state1.all_states.data.sample_scatter,
                         "SANS2D00022024")
        self.assertEqual(state1.all_states.data.sample_transmission, None)
        self.assertEqual(state1.all_states.data.sample_direct, None)
        self.assertEqual(state1.all_states.data.can_scatter, None)
        self.assertEqual(state1.all_states.data.can_transmission, None)
        self.assertEqual(state1.all_states.data.can_direct, None)

        # Check some entries
        self.assertEqual(state0.all_states.slice.start_time, None)
        self.assertEqual(state0.all_states.slice.end_time, None)

        self.assertEqual(state0.all_states.reduction.reduction_dimensionality,
                         ReductionDimensionality.ONE_DIM)
        self.assertEqual(
            state0.all_states.move.detectors['LAB'].sample_centre_pos1,
            0.15544999999999998)

        self.presenter.on_update_rows.assert_called_once()

        # Clean up
        self._remove_files(user_file_path=user_file_path,
                           batch_file_path=batch_file_path)

    def test_state_retrieved_from_model(self):
        self._mock_view.q_1d_step = 1.0

        expected = self.mock_run_tab_model.get_save_types().to_all_states()
        all_states = self.presenter.update_model_from_view()
        self.assertEqual(all_states.save_types, expected)

    def test_view_updated_from_model(self):
        # Avoid an error with magic mock not having __round__
        self.presenter._set_on_view_to_custom_view = mock.Mock()
        self.presenter.update_view_from_model()
        self.assertEqual(self.mock_run_tab_model.get_save_types.return_value,
                         self._mock_view.save_types)

    def test_observers_subscribed_to(self):
        mocked_view_observers = mock.create_autospec(SansGuiObservable())
        self._mock_view.get_observable = mock.Mock(
            return_value=mocked_view_observers)
        presenter = RunTabPresenter(facility=SANSFacility.ISIS,
                                    run_tab_model=self.mock_run_tab_model,
                                    view=self._mock_view)

        self._mock_view.get_observable.assert_called_once()
        mocked_view_observers.reduction_dim.add_subscriber.assert_called_once_with(
            presenter._observers.reduction_dim)
        mocked_view_observers.save_options.add_subscriber.assert_called_once_with(
            presenter._observers.save_options)

    def test_on_save_options_changed_called(self):
        self.view_observers.save_options.notify_subscribers()
        self.mock_run_tab_model.update_save_types.assert_called_once_with(
            self._mock_view.save_types)

    def test_on_reduction_options_changed_called(self):
        self.view_observers.reduction_dim.notify_subscribers()
        self.mock_run_tab_model.update_reduction_mode.assert_called_once_with(
            self._mock_view.reduction_dimensionality)

    def test_on_reduction_options_changed_updates_save_opts(self):
        self.view_observers.reduction_dim.notify_subscribers()
        self.mock_run_tab_model.get_save_types.assert_called_once()
        get_save_types_ret_val = self.mock_run_tab_model.get_save_types.return_value
        self.assertEqual(get_save_types_ret_val, self._mock_view.save_types)

    def test_that_can_get_state_for_index_if_index_exists(self):
        state_key = mock.NonCallableMock()
        self._mock_table.get_row.return_value = state_key
        expected_states, expected_errs = {
            state_key: mock.NonCallableMock(spec=StateGuiModel)
        }, None
        self.presenter.get_states = mock.Mock(return_value=(expected_states,
                                                            expected_errs))
        self.presenter.sans_logger = mock.Mock()

        state = self.presenter.get_state_for_row(0)

        self.assertEqual(expected_states[state_key].all_states, state)

    def test_get_state_for_row_returns_empty_for_empty(self):
        self.presenter.get_states = mock.Mock(return_value=({}, None))
        self.presenter.sans_logger = mock.Mock()
        state = self.presenter.get_state_for_row(0)

        self.assertIsNone(state)
        self.presenter.sans_logger.warning.assert_called_once()

    def test_on_data_changed_calls_update_rows(self):
        batch_file_path, user_file_path, _ = self._get_files_and_mock_presenter(
            BATCH_FILE_TEST_CONTENT_1)
        self.presenter._masking_table_presenter = mock.MagicMock()
        self.presenter._table_model.subscribe_to_model_changes(
            self.presenter._masking_table_presenter)
        self.presenter._beam_centre_presenter = mock.MagicMock()
        self.presenter._table_model.subscribe_to_model_changes(
            self.presenter._beam_centre_presenter)

        updated_entries = mock.NonCallableMock()
        self.presenter.on_data_changed(0, updated_entries)

        self.presenter._masking_table_presenter.on_update_rows.assert_called_with(
        )
        self.presenter._beam_centre_presenter.on_update_rows.assert_called_with(
        )
        self.presenter.update_view_from_table_model.assert_called_with()

    def test_on_save_dir_changed_calls_set_out_file_directory(self):
        batch_file_path, user_file_path, view = self._get_files_and_mock_presenter(
            BATCH_FILE_TEST_CONTENT_3, is_multi_period=False)
        config["defaultsave.directory"] = "test/path"
        self.presenter._view.set_out_file_directory.assert_called_with(
            "test/path")

    def test_on_insert_row_updates_table_model(self):
        self.presenter.on_row_appended()

        self._mock_table.append_table_entry.assert_called_with(mock.ANY)
        self.presenter.update_view_from_table_model.assert_called_with()

    def test_that_all_columns_shown_when_multi_period_is_true(self):

        self.presenter.set_view(mock.MagicMock())

        self.presenter.on_multiperiod_changed(True)

        self.presenter._view.show_period_columns.assert_called_once_with()

    def test_that_period_columns_hidden_when_multi_period_is_false(self):

        self.presenter.set_view(mock.MagicMock())

        self.presenter.on_multiperiod_changed(False)

        self.presenter._view.hide_period_columns.assert_called_once_with()

    def test_on_data_changed_updates_table_model(self):
        self.presenter._beam_centre_presenter = mock.Mock()
        self.presenter._masking_table_presenter = mock.Mock()

        expected_new_entry = mock.NonCallableMock()
        self._mock_table.get_all_rows.return_value = [expected_new_entry]

        self.presenter.on_data_changed(0, expected_new_entry)
        self._mock_table.replace_table_entry.assert_called_with(
            0, expected_new_entry)

        self.presenter._beam_centre_presenter.on_update_rows.assert_called_with(
        )
        self.presenter._masking_table_presenter.on_update_rows.assert_called_with(
        )
        self.presenter.update_view_from_table_model.assert_called_with()

    def test_on_row_removed_removes_correct_row(self):
        rows = [0]
        self.presenter.on_rows_removed(rows)

        self._mock_table.remove_table_entries.assert_called_with(rows)
        self.presenter.update_view_from_table_model.assert_called_with()

    def test_setup_instrument_specific_settings(self):

        self.presenter.set_view(mock.MagicMock())
        self.presenter._beam_centre_presenter = mock.MagicMock()
        self.presenter._workspace_diagnostic_presenter = mock.MagicMock()
        instrument = SANSInstrument.LOQ

        self.presenter._setup_instrument_specific_settings(SANSInstrument.LOQ)

        self.presenter._view.set_instrument_settings.called_once_with(
            instrument)
        self.presenter._beam_centre_presenter.on_update_instrument.called_once_with(
            instrument)
        self.presenter._workspace_diagnostic_presenter.called_once_with(
            instrument)

    def test_on_instrument_change_notifies_child_presenters(self):
        self.presenter._beam_centre_presenter = mock.Mock()
        self.presenter._setup_instrument_specific_settings = mock.Mock()
        self.presenter.on_instrument_changed()
        self.presenter._setup_instrument_specific_settings.assert_called_once()
        self.presenter._beam_centre_presenter.on_update_instrument.assert_called_once(
        )

    def test_setup_instrument_specific_settings_sets_facility_in_config(self):
        config.setFacility('TEST_LIVE')

        self.presenter.set_view(mock.MagicMock())
        self.presenter._beam_centre_presenter = mock.MagicMock()
        self.presenter._workspace_diagnostic_presenter = mock.MagicMock()
        instrument = SANSInstrument.LOQ

        self.presenter._setup_instrument_specific_settings(instrument)
        self.assertEqual(config.getFacility().name(), 'ISIS')

    def test_setup_instrument_specific_settings_sets_instrument_in_config(
            self):
        config['default.instrument'] = 'ALF'

        self.presenter.set_view(mock.MagicMock())
        self.presenter._beam_centre_presenter = mock.MagicMock()
        self.presenter._workspace_diagnostic_presenter = mock.MagicMock()
        instrument = SANSInstrument.LOQ

        self.presenter._setup_instrument_specific_settings(instrument)
        self.assertEqual(config['default.instrument'], 'LOQ')

    def test_on_copy_rows_requested_adds_correct_rows_to_clipboard(self):
        self._mock_view.get_selected_rows.return_value = [0]
        expected_clipboard = mock.NonCallableMock()
        self._mock_table.get_row.return_value = expected_clipboard

        self.presenter.on_copy_rows_requested()

        self.assertTrue(expected_clipboard in self.presenter._clipboard)

    def test_on_paste_rows_requested_appends_new_row_if_no_row_selected(self):
        self._mock_view.get_selected_rows.return_value = []
        pos = 101
        self._mock_table.get_number_of_rows.return_value = pos

        expected = mock.NonCallableMock()
        self.presenter._clipboard = [expected]
        self.presenter.update_view_from_table_model = mock.MagicMock()

        self.presenter.on_paste_rows_requested()

        self.presenter.update_view_from_table_model.assert_called_with()
        self._mock_table.replace_table_entries.assert_called_with([pos],
                                                                  [expected])

    def test_on_paste_rows_requested_replaces_row_if_one_row_is_selected(self):
        pos = [1]
        self._mock_view.get_selected_rows.return_value = pos

        test_row_0 = mock.NonCallableMock()
        self.presenter._clipboard = [test_row_0]
        self.presenter.update_view_from_table_model = mock.MagicMock()

        self.presenter.on_paste_rows_requested()

        self.presenter.update_view_from_table_model.assert_called_with()
        self._mock_table.replace_table_entries.assert_called_with(
            pos, [test_row_0])

    def test_on_paste_rows_requested_replaces_first_row_and_removes_rest_if_multiple_rows_selected(
            self):
        expected_pos = [0, 2]
        self._mock_view.get_selected_rows.return_value = expected_pos

        expected = mock.NonCallableMock()
        self.presenter._clipboard = [expected]
        self.presenter.update_view_from_table_model = mock.MagicMock()

        self.presenter.on_paste_rows_requested()

        self.presenter.update_view_from_table_model.assert_called_with()
        self._mock_table.replace_table_entries.assert_called_with(
            expected_pos, [expected])

    def test_on_insert_row_adds_row_to_table_model_after_selected_row(self):
        self._mock_view.get_selected_rows.return_value = [100]

        self.presenter.on_insert_row()

        self._mock_table.insert_row_at(101, mock.ANY)
        self.presenter.update_view_from_table_model.assert_called_with()

    def test_on_insert_row_updates_view(self):
        self._mock_view.get_selected_rows.return_value = [0]

        self.presenter.on_insert_row()
        self.presenter.update_view_from_table_model.assert_called_once_with()

    def test_on_erase_rows_clears_specific_rows_from_table_model(self):
        selected_rows = [1, 2]
        self._mock_table.get_number_of_rows.return_value = 3
        self._mock_view.get_selected_rows.return_value = selected_rows

        self.presenter.on_erase_rows()
        self.assertEqual(len(selected_rows),
                         self._mock_table.replace_table_entries.call_count)
        self.presenter.update_view_from_table_model.assert_called_with()

    def test_on_erase_rows_clears_table(self):
        selected_rows = [0, 1, 2]
        self._mock_table.get_number_of_rows.return_value = 3
        self._mock_view.get_selected_rows.return_value = selected_rows

        self.presenter.on_erase_rows()

        self._mock_table.clear_table_entries.assert_called_with()

        self._mock_view.get_selected_rows.return_value = []
        self._mock_table.clear_table_entries.reset_mock()

        self.presenter.on_erase_rows()
        self._mock_table.clear_table_entries.assert_called_with()
        self.presenter.update_view_from_table_model.assert_called_with()

    def test_on_cut_rows_requested_updates_clipboard(self):
        self._mock_view.get_selected_rows.return_value = [0]
        expected_clipboard = mock.NonCallableMock()
        self._mock_table.get_row.return_value = expected_clipboard

        self.presenter.on_cut_rows_requested()

        self.assertTrue(expected_clipboard in self.presenter._clipboard)

    def test_on_cut_rows_requested_removes_selected_rows(self):
        selected_rows = [0]
        self._mock_view.get_selected_rows.return_value = selected_rows

        expected = "expected_clipboard_val"
        self._mock_table.get_row.return_value = expected

        self.presenter.on_cut_rows_requested()

        self._mock_table.remove_table_entries.assert_called_with(selected_rows)
        self.assertTrue(expected in self.presenter._clipboard)

    def test_notify_progress_increments_progress(self):
        self.presenter.notify_progress(0, [0.0], [1.0])

        self.assertEqual(self.presenter.progress, 1)
        self.assertEqual(self.presenter._view.progress_bar_value, 1)

    def test_that_notify_progress_updates_state_and_tooltip_of_row_for_scale_and_shift(
            self):
        mocked_row = mock.Mock()

        self._mock_table.get_row.return_value = mocked_row

        self.presenter.notify_progress(0, [0.0], [1.0])

        self.assertEqual(RowState.PROCESSED, mocked_row.state)
        self.assertIsNone(mocked_row.tool_tip)
        self.presenter.update_view_from_table_model.assert_called_with()

        mocked_row.options.set_developer_option.assert_called_with(
            'MergeShift', 0.0)

        self.assertEqual(self.presenter.progress, 1)
        self.assertEqual(self.presenter._view.progress_bar_value, 1)

    def test_that_update_progress_sets_correctly(self):

        view = mock.MagicMock()
        self.presenter.set_view(view)

        self.presenter._set_progress_bar(current=100, number_steps=200)
        self.assertEqual(self.presenter.progress, 100)
        self.assertEqual(view.progress_bar_value, 100)
        self.assertEqual(view.progress_bar_maximum, 200)

    def test_that_notify_progress_updates_state_and_tooltip_of_row(self):

        view = mock.MagicMock()
        self.presenter.set_view(view)
        self.presenter.on_row_appended()

        self.presenter.notify_progress(0, [], [])

        self.assertEqual(
            self.presenter._table_model.get_row(0).state, RowState.PROCESSED)
        self.assertEqual(self.presenter._table_model.get_row(0).tool_tip, None)

    def test_that_process_selected_does_nothing_if_no_states_selected(self):

        view = mock.MagicMock()
        view.get_selected_rows = mock.MagicMock(return_value=[])
        self.presenter.set_view(view)
        self.presenter._process_rows = mock.MagicMock()

        self.presenter.on_process_selected_clicked()
        self.assertEqual(
            self.presenter._process_rows.call_count, 0,
            "Expected self.presenter._process_rows to not have been called. Called {} times."
            .format(self.presenter._process_rows.call_count))

    def test_that_process_selected_only_processes_selected_rows(self):
        # Naive test. Doesn't check that we are processing the correct processed rows,
        # just that we are processing the same number of rows as we have selected.
        # This would only really fail if on_process_selected_clicked and on_process_all_clicked
        # get muddled-up

        view = mock.MagicMock()
        view.get_selected_rows = mock.MagicMock(return_value=[0, 2, 3])

        self.presenter.set_view(view)
        self.presenter._process_rows = mock.Mock()

        table_model = TableModel()

        for i in range(5):
            table_model.append_table_entry(RowEntries(sample_scatter='74040'))
        self.presenter._table_model = table_model

        self.presenter.on_process_selected_clicked()

        expected = [
            table_model.get_row(0),
            table_model.get_row(2),
            table_model.get_row(3)
        ]

        self.presenter._process_rows.assert_called_with(expected)

    def test_that_process_selected_ignores_all_empty_rows(self):

        view = mock.MagicMock()
        view.get_selected_rows = mock.MagicMock(return_value=[0, 1])
        self.presenter.set_view(view)

        table_model = TableModel()
        empty_row = RowEntries()
        populated_row = RowEntries(sample_scatter='74040')

        table_model.replace_table_entry(0, empty_row)
        table_model.replace_table_entry(1, populated_row)

        self.presenter._table_model = table_model
        self.presenter._process_rows = mock.MagicMock()

        self.presenter.on_process_selected_clicked()
        self.presenter._process_rows.assert_called_with([populated_row])

    def test_that_process_all_ignores_empty_rows(self):

        view = mock.MagicMock()
        view.get_selected_rows = mock.MagicMock(return_value=[0, 1])
        self.presenter.set_view(view)

        table_model = TableModel()
        populated_row = RowEntries(sample_scatter=1)
        table_model.append_table_entry(populated_row)
        table_model.append_table_entry(RowEntries())

        self.presenter._table_model = table_model
        self.presenter._process_rows = mock.MagicMock()

        self.presenter.on_process_selected_clicked()
        self.presenter._process_rows.assert_called_with([populated_row])

    def test_that_csv_file_is_saved_when_export_table_button_is_clicked(self):
        self.presenter._export_table = mock.MagicMock()
        self._mock_table.get_non_empty_rows.return_value = BATCH_FILE_TEST_CONTENT_1
        self._mock_model.batch_file = ""
        self.presenter.display_save_file_box = mock.Mock(
            return_value="mocked_file_path")
        self.presenter.on_export_table_clicked()
        self.assertEqual(
            self._mock_csv_parser.save_batch_file.call_count, 1,
            "_save_batch_file should have been called but was not")

    def test_save_csv_handles_exceptions(self):
        for exception in [PermissionError(), IOError()]:
            self.presenter.display_errors = mock.Mock()
            self._mock_csv_parser.save_batch_file.side_effect = exception
            self._mock_table.get_non_empty_rows.return_value = BATCH_FILE_TEST_CONTENT_1
            self._mock_model.batch_file = ""
            self.presenter.display_save_file_box = mock.Mock(
                return_value="mocked_file_path")

            self.presenter.on_export_table_clicked()
            self.presenter.display_errors.assert_called_once()

    def test_that_table_not_exported_if_table_is_empty(self):
        self.presenter._export_table = mock.MagicMock()
        self._mock_table.get_non_empty_rows.return_value = []

        self.presenter.on_export_table_clicked()
        self.assertEqual(
            self._mock_csv_parser.save_batch_file.call_count, 0,
            "_save_batch_file should not have been called."
            " It was called {} times.".format(
                self.presenter._export_table.call_count))

    def test_buttons_enabled_after_export_table_fails(self):
        self._mock_table.get_non_empty_rows.return_value = [RowEntries()]
        self._mock_model.batch_file = ""

        self.presenter.display_save_file_box = mock.Mock(
            return_value="mocked_file_path")

        self.presenter._view.enable_buttons = mock.Mock()
        self.presenter._view.disable_buttons = mock.Mock()

        # Mock error throw on disable buttons so export fails
        self.presenter._csv_parser = mock.Mock()
        self.presenter._csv_parser.save_batch_file = mock.Mock(
            side_effect=RuntimeError(""))

        with self.assertRaises(RuntimeError):
            self.presenter.on_export_table_clicked()

        self.assertEqual(self.presenter._view.enable_buttons.call_count, 1)

    def test_that_default_name_is_used_when_export_table(self):
        self.presenter._export_table = mock.MagicMock()
        self._mock_table.get_non_empty_rows.return_value = BATCH_FILE_TEST_CONTENT_1
        self._mock_model.batch_file = 'test_filename'
        self.presenter.display_save_file_box = mock.Mock(
            return_value="mocked_file_path")
        self.presenter.on_export_table_clicked()
        self.assertEqual(
            self.presenter.display_save_file_box.call_count, 1,
            "display_save_file_box should have been called but was not")
        expected_args = ['Save table as', 'test_filename', '*.csv']
        args = [
            arg for arg in self.presenter.display_save_file_box.call_args[0]
        ]
        self.assertEqual(args, expected_args)

    def test_that_updating_default_save_directory_also_updates_add_runs_save_directory(
            self):
        """This test checks that add runs self.presenter.s save directory update method is called
        when the defaultsave directory is updated."""

        view = mock.MagicMock()
        self.presenter.set_view(view)

        self.presenter._handle_output_directory_changed("a_new_directory")
        self.presenter._view.add_runs_presenter.handle_new_save_directory.assert_called_once_with(
            "a_new_directory")

    def test_that_validate_output_modes_raises_if_no_file_types_selected_for_file_mode(
            self):

        view = mock.MagicMock()

        view.save_types = [SaveType.NO_TYPE]

        view.output_mode_memory_radio_button.isChecked = mock.MagicMock(
            return_value=False)
        view.output_mode_file_radio_button.isChecked = mock.MagicMock(
            return_value=True)
        view.output_mode_both_radio_button.isChecked = mock.MagicMock(
            return_value=False)
        self.presenter.set_view(view)

        self.assertRaises(ValueError, self.presenter._validate_output_modes)

    def test_that_validate_output_modes_raises_if_no_file_types_selected_for_both_mode(
            self):
        view = mock.MagicMock()

        view.save_types = [SaveType.NO_TYPE]

        view.output_mode_memory_radio_button.isChecked = mock.MagicMock(
            return_value=False)
        view.output_mode_file_radio_button.isChecked = mock.MagicMock(
            return_value=False)
        view.output_mode_both_radio_button.isChecked = mock.MagicMock(
            return_value=True)
        self.presenter.set_view(view)

        self.assertRaises(ValueError, self.presenter._validate_output_modes)

    def test_that_validate_output_modes_does_not_raise_if_no_file_types_selected_for_memory_mode(
            self):

        view = mock.MagicMock()
        view.save_types = [SaveType.NO_TYPE]

        view.output_mode_memory_radio_button.isChecked = mock.Mock(
            return_value=True)
        view.output_mode_file_radio_button.isChecked = mock.Mock(
            return_value=False)
        view.output_mode_both_radio_button.isChecked = mock.Mock(
            return_value=False)
        self.presenter.set_view(view)

        try:
            self.presenter._validate_output_modes()
        except RuntimeError:
            self.fail(
                "Did not expect _validate_output_modes to fail when no file types are selected "
                "for memory output mode.")

    def test_that_switching_to_memory_mode_disables_all_file_type_buttons(
            self):
        """This tests that all file type buttons are disabled when memory mode is selected."""
        self._mock_view.output_mode_memory_radio_button.isChecked = mock.Mock(
            return_value=True)

        self.presenter.on_output_mode_changed()
        self.presenter._view.disable_file_type_buttons.assert_called_once()

    def test_that_switching_off_memory_mode_enables_all_file_type_buttons(
            self):
        """This tests that all file type buttons are enabled if switching to file or both mode, when reduction
        dimensionality is 1D"""

        self._mock_view.output_mode_memory_radio_button.isChecked = mock.Mock(
            return_value=False)

        self.presenter.on_output_mode_changed()
        self.presenter._view.enable_file_type_buttons.assert_called_once()

    def test_that_on_reduction_mode_changed_calls_update_front_if_selection_is_HAB(
            self):

        self.presenter._beam_centre_presenter = mock.MagicMock()

        self.presenter.on_reduction_mode_selection_has_changed("Hab")
        self.presenter._beam_centre_presenter.update_front_selected.assert_called_once_with(
        )

        self.presenter._beam_centre_presenter.reset_mock()
        self.presenter.on_reduction_mode_selection_has_changed("front")
        self.presenter._beam_centre_presenter.update_front_selected.assert_called_once_with(
        )

    def test_that_on_reduction_mode_changed_calls_update_rear_if_selection_is_LAB(
            self):

        self.presenter._beam_centre_presenter = mock.MagicMock()

        self.presenter.on_reduction_mode_selection_has_changed("rear")
        self.presenter._beam_centre_presenter.update_rear_selected.assert_called_once_with(
        )

        self.presenter._beam_centre_presenter.reset_mock()
        self.presenter.on_reduction_mode_selection_has_changed("main-detector")
        self.presenter._beam_centre_presenter.update_rear_selected.assert_called_once_with(
        )

        self.presenter._beam_centre_presenter.reset_mock()
        self.presenter.on_reduction_mode_selection_has_changed("DetectorBench")
        self.presenter._beam_centre_presenter.update_rear_selected.assert_called_once_with(
        )

        self.presenter._beam_centre_presenter.reset_mock()
        self.presenter.on_reduction_mode_selection_has_changed("rear-detector")
        self.presenter._beam_centre_presenter.update_rear_selected.assert_called_once_with(
        )

    @staticmethod
    def _clear_property_manager_data_service():
        for element in PropertyManagerDataService.getObjectNames():
            if PropertyManagerDataService.doesExist(element):
                PropertyManagerDataService.remove(element)

    def _get_files_and_mock_presenter(self,
                                      content,
                                      is_multi_period=True,
                                      row_user_file_path=""):
        if row_user_file_path:
            content[1].user_file = row_user_file_path

        batch_parser = mock.MagicMock()
        batch_parser.parse_batch_file = mock.MagicMock(return_value=content)
        self._mock_csv_parser.return_value = batch_parser
        batch_file_path = 'batch_file_path'

        user_file_path = create_user_file(sample_user_file)
        view, _, _ = create_mock_view(user_file_path, batch_file_path,
                                      row_user_file_path)
        # We just use the sample_user_file since it exists.
        view.get_mask_file = mock.MagicMock(return_value=user_file_path)
        view.is_multi_period_view = mock.MagicMock(
            return_value=is_multi_period)
        self.presenter.set_view(view)
        return batch_file_path, user_file_path, view

    @staticmethod
    def _remove_files(user_file_path=None, batch_file_path=None):
        if user_file_path:
            remove_file(user_file_path)
        if batch_file_path:
            remove_file(batch_file_path)
Beispiel #6
0
class RunTabPresenterTest(unittest.TestCase):
    def setUp(self):
        # Backup properties that the tests or GUI may change
        self._backup_facility = config["default.facility"]
        self._backup_instrument = config["default.instrument"]
        self._backup_datasearch_dirs = config["datasearch.directories"]
        self._backup_save_dir = config["defaultsave.directory"]

        config["default.facility"] = "ISIS"

        self._mock_model = mock.Mock(spec=StateGuiModel)
        self._mock_table = mock.Mock(spec=TableModel)
        self._mock_csv_parser = mock.Mock(spec=BatchCsvParser)
        self._mock_view = mock.Mock()

        # TODO, this top level presenter should not be creating sub presenters, instead we should use
        # TODO  an observer pattern and common interface to exchange messages. However for the moment
        # TODO  we will skip patching each
        self.presenter = RunTabPresenter(SANSFacility.ISIS,
                                         model=self._mock_model,
                                         table_model=self._mock_table,
                                         view=self._mock_view)

        # The beam centre presenter will run QThreads which leads to flaky tests, so mock out
        self.presenter._beam_centre_presenter = mock.Mock()

        self.presenter._csv_parser = self._mock_csv_parser
        # Allows us to use mock objs as the method tries to directly use int/floats
        self.presenter.update_view_from_table_model = mock.Mock()

    def tearDown(self):
        config["default.facility"] = self._backup_facility
        config["default.instrument"] = self._backup_instrument
        config["datasearch.directories"] = self._backup_datasearch_dirs
        config["defaultsave.directory2"] = self._backup_save_dir

    def test_that_will_load_user_file(self):
        # Setup self.presenter.and mock view
        user_file_path = create_user_file(sample_user_file)
        view, settings_diagnostic_tab, _ = create_mock_view(user_file_path)

        self.presenter.set_view(view)

        # Act
        try:
            self.presenter.on_user_file_load()
        except RuntimeError:
            # Assert that RuntimeError from no instrument is caught
            self.fail(
                "on_user_file_load raises a RuntimeError which should be caught"
            )

        # Assert
        # Note that the event slices are not set in the user file
        self.assertFalse(view.event_slices)
        self.assertEqual(view.reduction_dimensionality,
                         ReductionDimensionality.ONE_DIM)
        self.assertEqual(view.save_types[0], SaveType.NX_CAN_SAS)
        self.assertTrue(view.zero_error_free)
        self.assertTrue(view.use_optimizations)
        self.assertEqual(view.reduction_mode, ReductionMode.LAB)
        self.assertEqual(view.merge_scale, 1.)
        self.assertEqual(view.merge_shift, 0.)
        self.assertFalse(view.merge_scale_fit)
        self.assertFalse(view.merge_shift_fit)
        self.assertEqual(view.event_binning, "7000.0,500.0,60000.0")
        self.assertEqual(view.wavelength_step_type, RangeStepType.LIN)
        self.assertEqual(view.wavelength_min, 1.5)
        self.assertEqual(view.wavelength_max, 12.5)
        self.assertEqual(view.wavelength_step, 0.125)
        self.assertEqual(view.absolute_scale, 0.074)
        self.assertEqual(view.z_offset, 53.)
        self.assertEqual(view.normalization_incident_monitor, 1)
        self.assertTrue(view.normalization_interpolate)
        self.assertEqual(view.transmission_incident_monitor, 1)
        self.assertTrue(view.transmission_interpolate)
        self.assertEqual(view.transmission_roi_files, "test2.xml")
        self.assertEqual(view.transmission_mask_files, "test4.xml")
        self.assertEqual(view.transmission_radius, 7.)
        self.assertEqual(view.transmission_monitor, 4)
        self.assertEqual(view.transmission_mn_4_shift, -70)
        self.assertTrue(view.transmission_sample_use_fit)
        self.assertEqual(view.transmission_sample_fit_type,
                         FitType.LOGARITHMIC)
        self.assertEqual(view.transmission_sample_polynomial_order, 2)
        self.assertEqual(view.transmission_sample_wavelength_min, 1.5)
        self.assertEqual(view.transmission_sample_wavelength_max, 12.5)
        self.assertTrue(view.transmission_sample_use_wavelength)
        self.assertFalse(view.pixel_adjustment_det_1)
        self.assertFalse(view.pixel_adjustment_det_2)
        self.assertFalse(view.wavelength_adjustment_det_1)
        self.assertFalse(view.wavelength_adjustment_det_2)
        self.assertEqual(view.q_1d_min_or_rebin_string,
                         "0.001,0.001,0.0126,-0.08,0.2")
        self.assertEqual(view.q_xy_max, 0.05)
        self.assertEqual(view.q_xy_step, 0.001)
        self.assertEqual(view.q_xy_step_type, RangeStepType.LIN)
        self.assertTrue(view.gravity_on_off)
        self.assertTrue(view.use_q_resolution)
        self.assertEqual(view.q_resolution_sample_a, 14.)
        self.assertEqual(view.q_resolution_source_a, 13.)
        self.assertEqual(view.q_resolution_delta_r, 11.)
        self.assertEqual(view.q_resolution_collimation_length, 12.)
        self.assertEqual(view.q_resolution_moderator_file,
                         "moderator_rkh_file.txt")
        self.assertTrue(view.phi_limit_use_mirror)
        self.assertEqual(view.radius_limit_min, 12.)
        self.assertEqual(view.radius_limit_min, 12.)
        self.assertEqual(view.radius_limit_max, 15.)
        self.assertTrue(view.compatibility_mode)

        # clean up
        remove_file(user_file_path)

    def test_that_checks_default_user_file(self):
        # Setup self.presenter.and mock view
        view, settings_diagnostic_tab, _ = create_mock_view("")

        self.presenter.set_view(view)

        self.assertEqual(
            self.presenter._view.set_out_default_user_file.call_count, 1,
            "Expected mock to have been called once. Called {} times.".format(
                self.presenter._view.set_out_default_user_file.call_count))

        self.assertEqual(
            self.presenter._view._call_settings_listeners.call_count, 0,
            "Expected mock to not have been called. Called {} times.".format(
                self.presenter._view._call_settings_listeners.call_count))

    def test_fails_silently_when_user_file_does_not_exist(self):
        view, _, _ = create_mock_view("non_existent_user_file")
        self.presenter.set_view(view)

        self.assertIsNone(self.presenter.on_user_file_load())

    def test_that_gets_states_from_view(self):
        # Arrange
        batch_file_path, user_file_path, _ = self._get_files_and_mock_presenter(
            BATCH_FILE_TEST_CONTENT_2)
        self.presenter.on_user_file_load()
        self.presenter.on_batch_file_load()

        # Act
        states, errors = self.presenter.get_states(
            row_entries=BATCH_FILE_TEST_CONTENT_2)

        # Assert
        self.assertEqual(len(states), 2)
        for _, state in states.items():
            state.validate()

        # Check state 0
        state0 = states[BATCH_FILE_TEST_CONTENT_2[0]]
        self.assertEqual(state0.data.sample_scatter, "SANS2D00022024")
        self.assertEqual(state0.data.sample_transmission, "SANS2D00022048")
        self.assertEqual(state0.data.sample_direct, "SANS2D00022048")
        self.assertEqual(state0.data.can_scatter, None)
        self.assertEqual(state0.data.can_transmission, None)
        self.assertEqual(state0.data.can_direct, None)

        # Check state 1
        state1 = states[BATCH_FILE_TEST_CONTENT_2[1]]
        self.assertEqual(state1.data.sample_scatter, "SANS2D00022024")
        self.assertEqual(state1.data.sample_transmission, None)
        self.assertEqual(state1.data.sample_direct, None)
        self.assertEqual(state1.data.can_scatter, None)
        self.assertEqual(state1.data.can_transmission, None)
        self.assertEqual(state1.data.can_direct, None)

        # Check some entries
        self.assertEqual(state0.slice.start_time, None)
        self.assertEqual(state0.slice.end_time, None)

        self.assertEqual(state0.reduction.reduction_dimensionality,
                         ReductionDimensionality.ONE_DIM)
        self.assertEqual(state0.move.detectors['LAB'].sample_centre_pos1,
                         0.15544999999999998)

        # Clean up
        self._remove_files(user_file_path=user_file_path,
                           batch_file_path=batch_file_path)

    def test_that_can_get_state_for_index_if_index_exists(self):
        state_key = mock.NonCallableMock()
        self._mock_table.get_row.return_value = state_key
        expected_states, expected_errs = {state_key: "state"}, None
        self.presenter.get_states = mock.Mock(return_value=(expected_states,
                                                            expected_errs))
        self.presenter.sans_logger = mock.Mock()

        state = self.presenter.get_state_for_row(0)

        self.assertEqual(expected_states[state_key], state)

    def test_on_data_changed_calls_update_rows(self):
        batch_file_path, user_file_path, _ = self._get_files_and_mock_presenter(
            BATCH_FILE_TEST_CONTENT_1)
        self.presenter._masking_table_presenter = mock.MagicMock()
        self.presenter._table_model.subscribe_to_model_changes(
            self.presenter._masking_table_presenter)
        self.presenter._beam_centre_presenter = mock.MagicMock()
        self.presenter._table_model.subscribe_to_model_changes(
            self.presenter._beam_centre_presenter)

        updated_entries = mock.NonCallableMock()
        self.presenter.on_data_changed(0, updated_entries)

        self.presenter._masking_table_presenter.on_update_rows.assert_called_with(
        )
        self.presenter._beam_centre_presenter.on_update_rows.assert_called_with(
        )
        self.presenter.update_view_from_table_model.assert_called_with()

    def test_on_save_dir_changed_calls_set_out_file_directory(self):
        batch_file_path, user_file_path, view = self._get_files_and_mock_presenter(
            BATCH_FILE_TEST_CONTENT_3, is_multi_period=False)
        config["defaultsave.directory"] = "test/path"
        self.presenter._view.set_out_file_directory.assert_called_with(
            "test/path")

    def test_on_insert_row_updates_table_model(self):
        self.presenter.on_row_appended()

        self._mock_table.append_table_entry.assert_called_with(mock.ANY)
        self.presenter.update_view_from_table_model.assert_called_with()

    def test_that_all_columns_shown_when_multi_period_is_true(self):

        self.presenter.set_view(mock.MagicMock())

        self.presenter.on_multiperiod_changed(True)

        self.presenter._view.show_period_columns.assert_called_once_with()

    def test_that_period_columns_hidden_when_multi_period_is_false(self):

        self.presenter.set_view(mock.MagicMock())

        self.presenter.on_multiperiod_changed(False)

        self.presenter._view.hide_period_columns.assert_called_once_with()

    def test_on_data_changed_updates_table_model(self):
        self.presenter._beam_centre_presenter = mock.Mock()
        self.presenter._masking_table_presenter = mock.Mock()

        expected_new_entry = mock.NonCallableMock()
        self._mock_table.get_all_rows.return_value = [expected_new_entry]

        self.presenter.on_data_changed(0, expected_new_entry)
        self._mock_table.replace_table_entry.assert_called_with(
            0, expected_new_entry)

        self.presenter._beam_centre_presenter.on_update_rows.assert_called_with(
        )
        self.presenter._masking_table_presenter.on_update_rows.assert_called_with(
        )
        self.presenter.update_view_from_table_model.assert_called_with()

    def test_on_row_removed_removes_correct_row(self):
        rows = [0]
        self.presenter.on_rows_removed(rows)

        self._mock_table.remove_table_entries.assert_called_with(rows)
        self.presenter.update_view_from_table_model.assert_called_with()

    def test_setup_instrument_specific_settings(self):

        self.presenter.set_view(mock.MagicMock())
        self.presenter._beam_centre_presenter = mock.MagicMock()
        self.presenter._workspace_diagnostic_presenter = mock.MagicMock()
        instrument = SANSInstrument.LOQ

        self.presenter._setup_instrument_specific_settings(SANSInstrument.LOQ)

        self.presenter._view.set_instrument_settings.called_once_with(
            instrument)
        self.presenter._beam_centre_presenter.on_update_instrument.called_once_with(
            instrument)
        self.presenter._workspace_diagnostic_presenter.called_once_with(
            instrument)

    def test_setup_instrument_specific_settings_sets_facility_in_config(self):
        config.setFacility('TEST_LIVE')

        self.presenter.set_view(mock.MagicMock())
        self.presenter._beam_centre_presenter = mock.MagicMock()
        self.presenter._workspace_diagnostic_presenter = mock.MagicMock()
        instrument = SANSInstrument.LOQ

        self.presenter._setup_instrument_specific_settings(instrument)
        self.assertEqual(config.getFacility().name(), 'ISIS')

    def test_setup_instrument_specific_settings_sets_instrument_in_config(
            self):
        config['default.instrument'] = 'ALF'

        self.presenter.set_view(mock.MagicMock())
        self.presenter._beam_centre_presenter = mock.MagicMock()
        self.presenter._workspace_diagnostic_presenter = mock.MagicMock()
        instrument = SANSInstrument.LOQ

        self.presenter._setup_instrument_specific_settings(instrument)
        self.assertEqual(config['default.instrument'], 'LOQ')

    def test_on_copy_rows_requested_adds_correct_rows_to_clipboard(self):
        self._mock_view.get_selected_rows.return_value = [0]
        expected_clipboard = mock.NonCallableMock()
        self._mock_table.get_row.return_value = expected_clipboard

        self.presenter.on_copy_rows_requested()

        self.assertTrue(expected_clipboard in self.presenter._clipboard)

    def test_on_paste_rows_requested_appends_new_row_if_no_row_selected(self):
        self._mock_view.get_selected_rows.return_value = []
        pos = 101
        self._mock_table.get_number_of_rows.return_value = pos

        expected = mock.NonCallableMock()
        self.presenter._clipboard = [expected]
        self.presenter.update_view_from_table_model = mock.MagicMock()

        self.presenter.on_paste_rows_requested()

        self.presenter.update_view_from_table_model.assert_called_with()
        self._mock_table.replace_table_entries.assert_called_with([pos],
                                                                  [expected])

    def test_on_paste_rows_requested_replaces_row_if_one_row_is_selected(self):
        pos = [1]
        self._mock_view.get_selected_rows.return_value = pos

        test_row_0 = mock.NonCallableMock()
        self.presenter._clipboard = [test_row_0]
        self.presenter.update_view_from_table_model = mock.MagicMock()

        self.presenter.on_paste_rows_requested()

        self.presenter.update_view_from_table_model.assert_called_with()
        self._mock_table.replace_table_entries.assert_called_with(
            pos, [test_row_0])

    def test_on_paste_rows_requested_replaces_first_row_and_removes_rest_if_multiple_rows_selected(
            self):
        expected_pos = [0, 2]
        self._mock_view.get_selected_rows.return_value = expected_pos

        expected = mock.NonCallableMock()
        self.presenter._clipboard = [expected]
        self.presenter.update_view_from_table_model = mock.MagicMock()

        self.presenter.on_paste_rows_requested()

        self.presenter.update_view_from_table_model.assert_called_with()
        self._mock_table.replace_table_entries.assert_called_with(
            expected_pos, [expected])

    def test_on_insert_row_adds_row_to_table_model_after_selected_row(self):
        self._mock_view.get_selected_rows.return_value = [100]

        self.presenter.on_insert_row()

        self._mock_table.insert_row_at(101, mock.ANY)
        self.presenter.update_view_from_table_model.assert_called_with()

    def test_on_insert_row_updates_view(self):
        self._mock_view.get_selected_rows.return_value = [0]

        self.presenter.on_insert_row()
        self.presenter.update_view_from_table_model.assert_called_once_with()

    def test_on_erase_rows_clears_specific_rows_from_table_model(self):
        selected_rows = [1, 2]
        self._mock_table.get_number_of_rows.return_value = 3
        self._mock_view.get_selected_rows.return_value = selected_rows

        self.presenter.on_erase_rows()
        self.assertEqual(len(selected_rows),
                         self._mock_table.replace_table_entries.call_count)
        self.presenter.update_view_from_table_model.assert_called_with()

    def test_on_erase_rows_clears_table(self):
        selected_rows = [0, 1, 2]
        self._mock_table.get_number_of_rows.return_value = 3
        self._mock_view.get_selected_rows.return_value = selected_rows

        self.presenter.on_erase_rows()

        self._mock_table.clear_table_entries.assert_called_with()

        self._mock_view.get_selected_rows.return_value = []
        self._mock_table.clear_table_entries.reset_mock()

        self.presenter.on_erase_rows()
        self._mock_table.clear_table_entries.assert_called_with()
        self.presenter.update_view_from_table_model.assert_called_with()

    def test_on_cut_rows_requested_updates_clipboard(self):
        self._mock_view.get_selected_rows.return_value = [0]
        expected_clipboard = mock.NonCallableMock()
        self._mock_table.get_row.return_value = expected_clipboard

        self.presenter.on_cut_rows_requested()

        self.assertTrue(expected_clipboard in self.presenter._clipboard)

    def test_on_cut_rows_requested_removes_selected_rows(self):
        selected_rows = [0]
        self._mock_view.get_selected_rows.return_value = selected_rows

        expected = "expected_clipboard_val"
        self._mock_table.get_row.return_value = expected

        self.presenter.on_cut_rows_requested()

        self._mock_table.remove_table_entries.assert_called_with(selected_rows)
        self.assertTrue(expected in self.presenter._clipboard)

    def test_notify_progress_increments_progress(self):
        self.presenter.notify_progress(0, [0.0], [1.0])

        self.assertEqual(self.presenter.progress, 1)
        self.assertEqual(self.presenter._view.progress_bar_value, 1)

    def test_that_notify_progress_updates_state_and_tooltip_of_row_for_scale_and_shift(
            self):
        mocked_row = mock.Mock()

        self._mock_table.get_row.return_value = mocked_row

        self.presenter.notify_progress(0, [0.0], [1.0])

        self.assertEqual(RowState.PROCESSED, mocked_row.state)
        self.assertIsNone(mocked_row.tool_tip)
        self.presenter.update_view_from_table_model.assert_called_with()

        mocked_row.options.set_developer_option.assert_called_with(
            'MergeShift', 0.0)

        self.assertEqual(self.presenter.progress, 1)
        self.assertEqual(self.presenter._view.progress_bar_value, 1)

    def test_that_update_progress_sets_correctly(self):

        view = mock.MagicMock()
        self.presenter.set_view(view)

        self.presenter._set_progress_bar(current=100, number_steps=200)
        self.assertEqual(self.presenter.progress, 100)
        self.assertEqual(view.progress_bar_value, 100)
        self.assertEqual(view.progress_bar_maximum, 200)

    def test_that_notify_progress_updates_state_and_tooltip_of_row(self):

        view = mock.MagicMock()
        self.presenter.set_view(view)
        self.presenter.on_row_appended()

        self.presenter.notify_progress(0, [], [])

        self.assertEqual(
            self.presenter._table_model.get_row(0).state, RowState.PROCESSED)
        self.assertEqual(self.presenter._table_model.get_row(0).tool_tip, None)

    def test_that_process_selected_does_nothing_if_no_states_selected(self):

        view = mock.MagicMock()
        view.get_selected_rows = mock.MagicMock(return_value=[])
        self.presenter.set_view(view)
        self.presenter._process_rows = mock.MagicMock()

        self.presenter.on_process_selected_clicked()
        self.assertEqual(
            self.presenter._process_rows.call_count, 0,
            "Expected self.presenter._process_rows to not have been called. Called {} times."
            .format(self.presenter._process_rows.call_count))

    def test_that_process_selected_only_processes_selected_rows(self):
        # Naive test. Doesn't check that we are processing the correct processed rows,
        # just that we are processing the same number of rows as we have selected.
        # This would only really fail if on_process_selected_clicked and on_process_all_clicked
        # get muddled-up

        view = mock.MagicMock()
        view.get_selected_rows = mock.MagicMock(return_value=[0, 2, 3])

        self.presenter.set_view(view)
        self.presenter._process_rows = mock.Mock()

        table_model = TableModel()

        for i in range(5):
            table_model.append_table_entry(RowEntries(sample_scatter='74040'))
        self.presenter._table_model = table_model

        self.presenter.on_process_selected_clicked()

        expected = [
            table_model.get_row(0),
            table_model.get_row(2),
            table_model.get_row(3)
        ]

        self.presenter._process_rows.assert_called_with(expected)

    def test_that_process_selected_ignores_all_empty_rows(self):

        view = mock.MagicMock()
        view.get_selected_rows = mock.MagicMock(return_value=[0, 1])
        self.presenter.set_view(view)

        table_model = TableModel()
        empty_row = RowEntries()
        populated_row = RowEntries(sample_scatter='74040')

        table_model.replace_table_entry(0, empty_row)
        table_model.replace_table_entry(1, populated_row)

        self.presenter._table_model = table_model
        self.presenter._process_rows = mock.MagicMock()

        self.presenter.on_process_selected_clicked()
        self.presenter._process_rows.assert_called_with([populated_row])

    def test_that_process_all_ignores_empty_rows(self):

        view = mock.MagicMock()
        view.get_selected_rows = mock.MagicMock(return_value=[0, 1])
        self.presenter.set_view(view)

        table_model = TableModel()
        populated_row = RowEntries(sample_scatter=1)
        table_model.append_table_entry(populated_row)
        table_model.append_table_entry(RowEntries())

        self.presenter._table_model = table_model
        self.presenter._process_rows = mock.MagicMock()

        self.presenter.on_process_selected_clicked()
        self.presenter._process_rows.assert_called_with([populated_row])

    def test_that_table_not_exported_if_table_is_empty(self):
        self.presenter._export_table = mock.MagicMock()
        self._mock_table.get_non_empty_rows.return_value = []

        self.presenter.on_export_table_clicked()
        self.assertEqual(
            self.presenter._export_table.call_count, 0,
            "_export table should not have been called."
            " It was called {} times.".format(
                self.presenter._export_table.call_count))

    def test_buttons_enabled_after_export_table_fails(self):
        self._mock_table.get_non_empty_rows.return_value = [RowEntries()]
        self._mock_table.batch_file = ""

        self.presenter.display_save_file_box = mock.Mock(
            return_value="mocked_file_path")

        self.presenter._view.enable_buttons = mock.Mock()
        self.presenter._view.disable_buttons = mock.Mock()

        # Mock error throw on disable buttons so export fails
        self.presenter._csv_parser = mock.Mock()
        self.presenter._csv_parser.save_batch_file = mock.Mock(
            side_effect=RuntimeError(""))

        with self.assertRaises(RuntimeError):
            self.presenter.on_export_table_clicked()

        self.assertEqual(self.presenter._view.enable_buttons.call_count, 1)

    def test_that_canSAS_is_disabled_if_2D_reduction(self):
        """This test checks that if you are running a 2D reduction and have canSAS output mode checked,
        the GUI will automatically uncheck canSAS to avoid data dimension errors."""

        view = mock.MagicMock()
        view.can_sas_checkbox.isChecked = mock.Mock(return_value=True)
        view.can_sas_checkbox.setChecked = mock.Mock()
        view.can_sas_checkbox.setEnabled = mock.Mock()
        view.output_mode_memory_radio_button.isChecked = mock.Mock(
            return_value=False)

        self.presenter.set_view(view)
        self.presenter.on_reduction_dimensionality_changed(False)
        self.presenter._view.can_sas_checkbox.setEnabled.assert_called_once_with(
            False)

    def test_that_canSAS_is_unchecked_if_2D_reduction(self):
        """This tests that the canSAS checkbox is unchecked when switching from 1D to 2D reduction"""

        view = mock.MagicMock()
        view.can_sas_checkbox.isChecked = mock.Mock(return_value=True)
        view.can_sas_checkbox.setChecked = mock.Mock()
        view.can_sas_checkbox.setEnabled = mock.Mock()
        view.output_mode_memory_radio_button.isChecked = mock.Mock(
            return_value=False)

        self.presenter.set_view(view)
        self.presenter.on_reduction_dimensionality_changed(False)
        self.presenter._view.can_sas_checkbox.setChecked.assert_called_once_with(
            False)

    def test_that_canSAS_is_enabled_if_1D_reduction_and_not_in_memory_mode(
            self):
        """This test checks that if you are not in memory mode and switch to 1D reduction, then
        can sas file type is enabled."""

        view = mock.MagicMock()
        view.can_sas_checkbox.isChecked = mock.Mock(return_value=True)
        view.can_sas_checkbox.setChecked = mock.Mock()
        view.can_sas_checkbox.setEnabled = mock.Mock()
        view.output_mode_memory_radio_button.isChecked = mock.Mock(
            return_value=False)

        self.presenter.set_view(view)
        self.presenter.on_reduction_dimensionality_changed(True)

        self.presenter._view.can_sas_checkbox.setEnabled.assert_called_once_with(
            True)
        self.presenter._view.can_sas_checkbox.setChecked.assert_not_called()

    def test_that_canSAS_is_not_enabled_if_switch_to_1D_reduction_and_in_memory_mode(
            self):
        """This test checks that if you are in memory mode, the can sas file type is not
        re-enabled if you switch to 1D reduction."""

        view = mock.MagicMock()
        view.can_sas_checkbox.isChecked = mock.Mock(return_value=True)
        view.can_sas_checkbox.setChecked = mock.Mock()
        view.can_sas_checkbox.setEnabled = mock.Mock()
        view.output_mode_memory_radio_button.isChecked = mock.Mock(
            return_value=True)

        self.presenter.set_view(view)
        self.presenter.on_reduction_dimensionality_changed(True)

        self.presenter._view.can_sas_checkbox.setChecked.assert_not_called()
        self.presenter._view.can_sas_checkbox.setEnabled.assert_not_called()

    def test_that_updating_default_save_directory_also_updates_add_runs_save_directory(
            self):
        """This test checks that add runs self.presenter.s save directory update method is called
        when the defaultsave directory is updated."""

        view = mock.MagicMock()
        self.presenter.set_view(view)

        self.presenter._handle_output_directory_changed("a_new_directory")
        self.presenter._view.add_runs_presenter.handle_new_save_directory.assert_called_once_with(
            "a_new_directory")

    def test_that_validate_output_modes_raises_if_no_file_types_selected_for_file_mode(
            self):

        view = mock.MagicMock()

        view.save_types = [SaveType.NO_TYPE]

        view.output_mode_memory_radio_button.isChecked = mock.MagicMock(
            return_value=False)
        view.output_mode_file_radio_button.isChecked = mock.MagicMock(
            return_value=True)
        view.output_mode_both_radio_button.isChecked = mock.MagicMock(
            return_value=False)
        self.presenter.set_view(view)

        self.assertRaises(RuntimeError, self.presenter._validate_output_modes)

    def test_that_validate_output_modes_raises_if_no_file_types_selected_for_both_mode(
            self):

        view = mock.MagicMock()

        view.save_types = [SaveType.NO_TYPE]

        view.output_mode_memory_radio_button.isChecked = mock.MagicMock(
            return_value=False)
        view.output_mode_file_radio_button.isChecked = mock.MagicMock(
            return_value=False)
        view.output_mode_both_radio_button.isChecked = mock.MagicMock(
            return_value=True)
        self.presenter.set_view(view)

        self.assertRaises(RuntimeError, self.presenter._validate_output_modes)

    def test_that_validate_output_modes_does_not_raise_if_no_file_types_selected_for_memory_mode(
            self):

        view = mock.MagicMock()
        view.save_types = [SaveType.NO_TYPE]

        view.output_mode_memory_radio_button.isChecked = mock.Mock(
            return_value=True)
        view.output_mode_file_radio_button.isChecked = mock.Mock(
            return_value=False)
        view.output_mode_both_radio_button.isChecked = mock.Mock(
            return_value=False)
        self.presenter.set_view(view)

        try:
            self.presenter._validate_output_modes()
        except RuntimeError:
            self.fail(
                "Did not expect _validate_output_modes to fail when no file types are selected "
                "for memory output mode.")

    def test_that_switching_to_memory_mode_disables_all_file_type_buttons(
            self):
        """This tests that all file type buttons are disabled when memory mode is selected."""

        view = mock.MagicMock()

        view.output_mode_memory_radio_button.isChecked = mock.Mock(
            return_value=True)
        view.disable_file_type_buttons = mock.Mock()

        self.presenter.set_view(view)

        self.presenter.on_output_mode_changed()
        self.presenter._view.disable_file_type_buttons.assert_called_once_with(
        )

    def test_that_all_file_type_buttons_are_enabled_if_switching_to_non_memory_mode_and_in_1D_reduction_mode(
            self):
        """This tests that all file type buttons are enabled if switching to file or both mode, when reduction
        dimensionality is 1D"""

        view = mock.MagicMock()

        view.output_mode_memory_radio_button.isChecked = mock.Mock(
            return_value=False)
        view.reduction_dimensionality_1D.isChecked = mock.Mock(
            return_value=True)
        view.can_sas_checkbox.setEnabled = mock.Mock()
        view.nx_can_sas_checkbox.setEnabled = mock.Mock()
        view.rkh_checkbox.setEnabled = mock.Mock()

        self.presenter.set_view(view)

        self.presenter.on_output_mode_changed()
        self.presenter._view.can_sas_checkbox.setEnabled.assert_called_once_with(
            True)
        self.presenter._view.nx_can_sas_checkbox.setEnabled.assert_called_once_with(
            True)
        self.presenter._view.rkh_checkbox.setEnabled.assert_called_once_with(
            True)

    def test_that_rkh_and_nx_can_sas_are_enabled_if_switching_to_non_memory_mode_and_in_2D_reduction_mode(
            self):
        """This tests that nx_can_sas and rkh file type buttons are enabled if switching to file or both mode, when
         reduction dimensionality is 1D, but can sas is not enabled"""

        view = mock.MagicMock()

        view.output_mode_memory_radio_button.isChecked = mock.Mock(
            return_value=False)
        view.reduction_dimensionality_1D.isChecked = mock.Mock(
            return_value=False)
        view.can_sas_checkbox.setEnabled = mock.Mock()
        view.nx_can_sas_checkbox.setEnabled = mock.Mock()
        view.rkh_checkbox.setEnabled = mock.Mock()

        self.presenter.set_view(view)

        self.presenter.on_output_mode_changed()
        self.presenter._view.can_sas_checkbox.setEnabled.assert_not_called()
        self.presenter._view.nx_can_sas_checkbox.setEnabled.assert_called_once_with(
            True)
        self.presenter._view.rkh_checkbox.setEnabled.assert_called_once_with(
            True)

    def test_that_on_reduction_mode_changed_calls_update_hab_if_selection_is_HAB(
            self):

        self.presenter._beam_centre_presenter = mock.MagicMock()

        self.presenter.on_reduction_mode_selection_has_changed("Hab")
        self.presenter._beam_centre_presenter.update_hab_selected.assert_called_once_with(
        )

        self.presenter._beam_centre_presenter.reset_mock()
        self.presenter.on_reduction_mode_selection_has_changed("front")
        self.presenter._beam_centre_presenter.update_hab_selected.assert_called_once_with(
        )

    def test_that_on_reduction_mode_changed_calls_update_lab_if_selection_is_LAB(
            self):

        self.presenter._beam_centre_presenter = mock.MagicMock()

        self.presenter.on_reduction_mode_selection_has_changed("rear")
        self.presenter._beam_centre_presenter.update_lab_selected.assert_called_once_with(
        )

        self.presenter._beam_centre_presenter.reset_mock()
        self.presenter.on_reduction_mode_selection_has_changed("main-detector")
        self.presenter._beam_centre_presenter.update_lab_selected.assert_called_once_with(
        )

        self.presenter._beam_centre_presenter.reset_mock()
        self.presenter.on_reduction_mode_selection_has_changed("DetectorBench")
        self.presenter._beam_centre_presenter.update_lab_selected.assert_called_once_with(
        )

        self.presenter._beam_centre_presenter.reset_mock()
        self.presenter.on_reduction_mode_selection_has_changed("rear-detector")
        self.presenter._beam_centre_presenter.update_lab_selected.assert_called_once_with(
        )

    @staticmethod
    def _clear_property_manager_data_service():
        for element in PropertyManagerDataService.getObjectNames():
            if PropertyManagerDataService.doesExist(element):
                PropertyManagerDataService.remove(element)

    def _get_files_and_mock_presenter(self,
                                      content,
                                      is_multi_period=True,
                                      row_user_file_path=""):
        if row_user_file_path:
            content[1].user_file = row_user_file_path

        batch_parser = mock.MagicMock()
        batch_parser.parse_batch_file = mock.MagicMock(return_value=content)
        self._mock_csv_parser.return_value = batch_parser
        batch_file_path = 'batch_file_path'

        user_file_path = create_user_file(sample_user_file)
        view, _, _ = create_mock_view(user_file_path, batch_file_path,
                                      row_user_file_path)
        # We just use the sample_user_file since it exists.
        view.get_mask_file = mock.MagicMock(return_value=user_file_path)
        view.is_multi_period_view = mock.MagicMock(
            return_value=is_multi_period)
        self.presenter.set_view(view)
        return batch_file_path, user_file_path, view

    @staticmethod
    def _remove_files(user_file_path=None, batch_file_path=None):
        if user_file_path:
            remove_file(user_file_path)
        if batch_file_path:
            remove_file(batch_file_path)

    @staticmethod
    def get_file_information_mock():
        return SANSFileInformationMock(instrument=SANSInstrument.SANS2D)