Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor tests and setup #1968

Closed
wants to merge 32 commits into from
Closed

Conversation

ReimarBauer
Copy link
Member

@ReimarBauer ReimarBauer commented Aug 30, 2023

Purpose of PR?:

Fixes #1957 for the mscolab part

Does this PR introduce a breaking change?

If the changes in this PR are manually verified, list down the scenarios covered::

Additional information for reviewer? :
the PR reduces the amount of servers needed for mscolab tests. Also a bug in the decription for a category change fixed.
For develop the tests were not adjusted to the UI refactoring. The conftest has removed all QMessage boxes.
Some errors we had were related that we don't loaded an initial flight track to the window for tests. Other Time out Blockers occure when some of the Questions are not mocked.

Does this PR results in some Documentation changes?
If yes, include the list of Documentation changes

Checklist:

  • Bug fix. Fixes #
  • New feature (Non-API breaking changes that adds functionality)
  • PR Title follows the convention of <type>: <subject>
  • Commit has unit tests

@ReimarBauer ReimarBauer requested review from matrss and joernu76 August 30, 2023 19:23
@ReimarBauer
Copy link
Member Author

this should be synced with work of @matrss #1967

@@ -210,32 +210,6 @@ def _load_module(module_name, path):
_load_module("mscolab_settings", path)


Copy link
Member Author

@ReimarBauer ReimarBauer Aug 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing this will Error all tests with an unhandled QMessageBox

Copy link
Collaborator

@matrss matrss Aug 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that is true. I just had a test pass with this fixture removed even though it showed a message box and failed with my modified fixture.

@@ -248,3 +222,7 @@ def configure_testsetup(request):
VIRT_DISPLAY.stop()
else:
yield

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the new approach is not to start on any test a mscolab service but once per test class. We need at the end to terminate all processes

Copy link
Collaborator

@matrss matrss Aug 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that really a good idea? We could run into issues where one test affects the outcome of another due to the shared mscolab instance, which could be a nightmare to debug. Also, I do not really like the implementation with this global variable here, couldn't this be a fixture sort of like this (pseudocode):

@pytest.fixture()
def mscolab_server():
    server = start_mscolab_server()
    yield server
    server.shutdown()

?

Every test that needs a running mscolab server then should just use the fixture. That way we could even use the scope of the fixture to decide how "shared" we want the instance to be.

For what it's worth, I would prefer to get rid of test classes entirely, anyway. A yield fixture is much better suited for setup and teardown code, which leaves classes only as an organizational unit to group tests together. But modules, in turn, do that better.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is not a global variable, it is defined as module local variable.
That's a great article about https://betterprogramming.pub/alternatives-to-using-globals-in-python-a3b2a7d5411b

we need for xdist calls servers running on different ports,

try it.

what do you mean by removing test classes entirely, I think setup and teardown may become complicated. But lets see.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

each test cleans up the database, and xdist is currently called on --dist loadfile
The requirement by this is only different port per file.

I also like to have different servers on each run, which I introduced before.

Anyway any test needs independent from each other. So not having a test which creates data and another test which tries to vaildate something based on that.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you mean by removing test classes entirely, I think setup and teardown may become complicated. But lets see.

The recommended way (by pytest) to have setup and teardown code is using a yield fixture. You can read more about that here: https://docs.pytest.org/en/6.2.x/fixture.html#yield-fixtures-recommended
They have an example there to demonstrate how user creation and deletion would work with fixtures, in an example testing a hypothetical email server. No class needed.

Anyway any test needs independent from each other. So not having a test which creates data and another test which tries to vaildate something based on that.

Hypothetically, what would happen with these shared mscolab servers if we had a test for user deletion trying to delete user "UV10@uv10" in Test_Mscolab_connect_window, but also try to login to this user in test_login? My concern is that we might run into similar, but less obvious, race conditions in the future.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is not a global variable, it is defined as module local variable.

In

MSS/conftest.py

Lines 226 to 228 in e60909c

def pytest_sessionfinish(session, exitstatus):
for process in MSCOLAB_PROCESSES:
process.terminate()
we terminate all processes once when we are finished with running the test suite. Isn't MSCOLAB_PROCESSES a global variable in the scope of the test session, then? We have one instance of this variable in which we accumulate all the running mscolab processes while executing the tests.

If I understand that right, with this PR we would also keep all the processes running until the end of the session, instead of stopping them as soon as they are not needed anymore.

Copy link
Member Author

@ReimarBauer ReimarBauer Aug 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you mean by removing test classes entirely, I think setup and teardown may become complicated. But lets see.

The recommended way (by pytest) to have setup and teardown code is using a yield fixture. You can read more about that here: https://docs.pytest.org/en/6.2.x/fixture.html#yield-fixtures-recommended They have an example there to demonstrate how user creation and deletion would work with fixtures, in an example testing a hypothetical email server. No class needed.

Anyway any test needs independent from each other. So not having a test which creates data and another test which tries to vaildate something based on that.

Hypothetically, what would happen with these shared mscolab servers if we had a test for user deletion trying to delete user "UV10@uv10" in Test_Mscolab_connect_window, but also try to login to this user in test_login? My concern is that we might run into similar, but less obvious, race conditions in the future.

connect_window and
test_login has to setup the user,
after each test per teardown the database is cleared.
Any test has to do all the steps for testing an approach.

when the tests are paralell running by xdist, any worker creates new tmp dirs
two workers don't access the same database

We had the described problems before, that's why tests currently not based on some others.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer more functional tests, without using the UI. At least everything what can be tested without the UI should be done.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At least everything what can be tested without the UI should be done.

I have to disagree on that. We should test at the "level" at which users interact with the application as well, and that is the UI. There is no point in testing if a login works if we don't test that the actual login button works also.

Or to phrase it differently: everything that can be done with the UI cannot be tested without it.

Copy link
Member Author

@ReimarBauer ReimarBauer Aug 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we do UI tests already in many ways also by the tutorials, But we also have a lot helper functions which also work without the UI and these should become tested if possibile without the UI.

@@ -96,7 +96,7 @@ jobs:
&& source /opt/conda/etc/profile.d/conda.sh \
&& source /opt/conda/etc/profile.d/mamba.sh \
&& mamba activate mss-${{ inputs.branch_name }}-env \
&& pytest -vv -n 6 --dist loadfile --max-worker-restart 0 tests \
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

currently will loose all tests assigned to a worker when that crashes

@@ -1056,8 +1056,11 @@ def handle_delete_operation(self):
self.reload_operations()
self.signal_operation_removed.emit(self.active_op_id)
logging.debug("activate local")
self.ui.listFlightTracks.setCurrentRow(0)
self.ui.activate_selected_flight_track()
if self.ui.listFlightTracks.count() == 0:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in mscolab we can remove "all" operations from the list, while we can't in the local flight path (left site of the UI)
When we do this, we have to switch to an active operation, which is done by switch_to_local

@@ -1986,11 +1989,9 @@ def logout(self):

self.operation_archive_browser.hide()

# Don't try to activate local flighttrack while testing
if "pytest" not in sys.modules:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not needed when the tests and code are updated

@ReimarBauer
Copy link
Member Author

504 passed, 27 skipped, 208 warnings in 217.48s (0:03:37)

Comment on lines 123 to 134
def test_login(self):
@mock.patch("PyQt5.QtWidgets.QMessageBox.question", return_value=QtWidgets.QMessageBox.No)
def test_login(self, mockquestion):
self._connect_to_mscolab()
self._login(self.userdata[0], self.userdata[2])
Copy link
Collaborator

@matrss matrss Aug 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree with the mocking here, we shouldn't just "click no" and be done with it. The issue here, why the message box pops up at all, is that in the msui config used in the test the "MSS_auth" does not have self.userdata[0] set as the username/email for the mscolab server we connect to here. With my approach to this:

def test_login(self):
self._connect_to_mscolab()
modify_config_file({"MSS_auth": {self.url: self.userdata[0]}})
self._login(self.userdata[0], self.userdata[2])
the message box disappears entirely. We could also test that the message box pops up as expected when there is this mismatch in the config, but then we would have to also add a mockquestion.assert_called_once_with(<args>) in every test. I think that is something we should just do once in a separate test case.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Case in point: there was a message box appearing in test_get_airspaces saying there was no airspace data in the file. That made no sense. It led me to discover this bug: https://github.com/Open-MSS/MSS/pull/1967/files#diff-ce023988bd24cea5863ed80b6ef4058629448da2f64480cd8dc31f06c5186a57R254-R256

On the other hand, in test_get_airspaces_missing_data we expected exactly this message box, so there we should mock the message box but then also assert that we get what we expect: https://github.com/Open-MSS/MSS/pull/1967/files#diff-d76a934985b02e5c8cafb478f5e5221eacd80d770c69798e9370ebb2232d8f9dR206-R215

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Obviously this argument applies to most of the other locations where this message box is mocked here as well.

Copy link
Member Author

@ReimarBauer ReimarBauer Aug 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's why I thought when you incorporate changes of this into your branch you can decide this on the merge or cherry pick, afterwards we close this PR and you can verify the other ideas.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should combine this larger architectural change with my, I'd argue, "less invasive" PR. I almost have my PR in a state in which I think it would be merge-able, getting rid of these warnings now and cleaning up the CI outputs, and then we could have a look at this more fundamental PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

@ReimarBauer ReimarBauer marked this pull request as draft August 31, 2023 13:11
@ReimarBauer
Copy link
Member Author

won't be done

@ReimarBauer ReimarBauer closed this Oct 8, 2023
@ReimarBauer ReimarBauer deleted the refactor_tests_and_setup branch May 25, 2024 07:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

What to do with "UserWarning: An unhandled message box popped up during your test!"?
2 participants