-
Notifications
You must be signed in to change notification settings - Fork 4
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
Improve unit testing #99
Conversation
f8a064d
to
8f80a70
Compare
Currently, benchcab does not have extensive coverage in its unit tests. Parts of the code that have poor test coverage are dependent on system interaction (e.g. environment modules, running subprocess commands (SVN, build scripts, executables, PBS). This change refactors and adds unit testing for these parts of the code base. Move the code from the benchcab.run_cable_site module to the benchcab.task module. This is done so that we reduce coupling between modules that use fluxnet tasks. This also has the benefit that we can test the two modules easily by sharing the same test setup functions. We move the code from the benchcab.run_comparison module to benchcab.task module for the same reasons. Unit tests now check exception messages and standard output produced by benchcab for both non-verbose and verbose modes. **Beware:** standard output messages have been updated in some areas. These changes will need to be updated in the documentation. Refactor the default build so that environment modules are loaded via python instead of writing module load statements into the temporary build script. This is done to reduce the complexity of the unit tests. The stderr output from subprocess commands are now redirected to stdout so that stderr is suppressed in non-verbose mode. Fixes #58 #22 #86
8f80a70
to
e2813ac
Compare
88f2575
to
0e016ee
Compare
0e016ee
to
865da7a
Compare
73e04f0
to
5eb457c
Compare
This change refactors the code to be more object oriented so that we can better support mocking via dependency injection rather than resorting to the `unittest.mock.patch` function. This allows us to write unit tests that are simpler and that preserve the API layer (as opposed to using `unittest.mock.patch` which breaks the API layer). Fixes #102
5eb457c
to
ecc7940
Compare
Codecov Report
@@ Coverage Diff @@
## master #99 +/- ##
===========================================
+ Coverage 66.29% 88.26% +21.97%
===========================================
Files 20 26 +6
Lines 1062 1364 +302
===========================================
+ Hits 704 1204 +500
+ Misses 358 160 -198
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a few things but everything should be straight forward. Feel free to refuse on anything I've put in.
benchcab/utils/pbs.py
Outdated
"""Returns a PBS job that executes all computationally expensive commands. | ||
|
||
This includes things such as running CABLE and running bitwise comparison jobs | ||
between model output files. The PBS job script is written to the current | ||
working directory as a side effect. | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"""Returns a PBS job that executes all computationally expensive commands. | |
This includes things such as running CABLE and running bitwise comparison jobs | |
between model output files. The PBS job script is written to the current | |
working directory as a side effect. | |
""" | |
"""Returns the text for a PBS job script that executes all computationally expensive commands. | |
This includes things such as running CABLE and running bitwise comparison jobs | |
between model output files. | |
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 95f1871
tests/test_comparison.py
Outdated
stdout_file = bitwise_cmp_dir / "mock_comparison_task_name.txt" | ||
|
||
# Failure case: test failed comparison check (files differ) | ||
mock_subprocess = MockSubprocessWrapper() | ||
mock_subprocess.error_on_call = True | ||
task = get_mock_comparison_task(subprocess_handler=mock_subprocess) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
stdout_file = bitwise_cmp_dir / "mock_comparison_task_name.txt" | |
# Failure case: test failed comparison check (files differ) | |
mock_subprocess = MockSubprocessWrapper() | |
mock_subprocess.error_on_call = True | |
task = get_mock_comparison_task(subprocess_handler=mock_subprocess) | |
# Failure case: test failed comparison check (files differ) | |
mock_subprocess = MockSubprocessWrapper() | |
mock_subprocess.error_on_call = True | |
task = get_mock_comparison_task(subprocess_handler=mock_subprocess) | |
stdout_file = bitwise_cmp_dir / f"{task.task_name}.txt" | |
Simply moves the def. of stdout_file after creating the task and re-use task_name. Makes it easier to understand where the name of the stdout_file comes from and avoids breaking the test by changing get_mock_comparison_task().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 95f1871
tests/test_comparison.py
Outdated
task = get_mock_comparison_task(subprocess_handler=mock_subprocess) | ||
task.run() | ||
with open(stdout_file, "r", encoding="utf-8") as file: | ||
assert file.read() == "mock standard output" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assert file.read() == "mock standard output" | |
assert file.read() == mock_subprocess.stdout |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 95f1871
verbose=verbose, | ||
) | ||
|
||
def custom_build(self, verbose=False) -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have moved the issue to work on merging this with the default build to the top of the list. This way we don't have to worry about this method here.
tests/test_repository.py
Outdated
mock_subprocess = MockSubprocessWrapper() | ||
mock_subprocess.stdout = "mock standard output" | ||
repo = get_mock_repo(mock_subprocess) | ||
assert repo.svn_info_show_item("some-mock-item") == "mock standard output" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assert repo.svn_info_show_item("some-mock-item") == "mock standard output" | |
assert repo.svn_info_show_item("some-mock-item") == mock_subprocess.stdout |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 95f1871
project=self.config["project"], modules=self.config["modules"] | ||
) | ||
|
||
def _validate_environment(self, project: str, modules: list): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could write tests for this. pytest allows to mark tests and choose to run only some. So we could check all the failures on GitHub (which are the most interesting tests actually) and add a "Gadi-only" test for success that we disable from the automatic testing on GitHub.
I am happy to leave it for another pull request.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep that is possible. Although I would prefer if we didn't have Gadi
specific tests (this wouldn't count towards code coverage). I actually think it would be straight forward to refactor _validate_environment
so that it is testable using dependency injection. But we can probably save this for later 😄
benchcab/benchcab.py
Outdated
if not self.modules_handler.module_is_loaded("nccmp"): | ||
self.modules_handler.module_load( | ||
"nccmp" | ||
) # use `nccmp -df` for bitwise comparisons |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if not self.modules_handler.module_is_loaded("nccmp"): | |
self.modules_handler.module_load( | |
"nccmp" | |
) # use `nccmp -df` for bitwise comparisons | |
if not self.modules_handler.module_is_loaded("nccmp/1.8.5.0"): | |
self.modules_handler.module_load( | |
"nccmp/1.8.5.0" | |
) # use `nccmp -df` for bitwise comparisons |
Always specify the version of the module when loading a module. Other versions can be installed and default versions can change with time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 95f1871
tests/test_benchcab.py
Outdated
assert buf.getvalue() == ( | ||
"Creating PBS job script to run FLUXNET tasks on compute " | ||
f"nodes: {internal.QSUB_FNAME}\n" | ||
"Error when submitting job to NCI queue\nmock standard output\n" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Error when submitting job to NCI queue\nmock standard output\n" | |
f"Error when submitting job to NCI queue\n{mock_subprocess.stdout}\n" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 95f1871
tests/test_task.py
Outdated
patch_namelist(nml_path, {"cable": {"file": "/path/to/file", "bar": 123}}) | ||
assert f90nml.read(nml_path) == { | ||
"cable": { | ||
"file": "/path/to/file", | ||
"bar": 123, | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you put the patch dictionary in a variable and reuse that variable in the assert? Instead of rewriting the whole dictionary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 95f1871
tests/test_task.py
Outdated
assert atts["cable_branch"] == "mock standard output" | ||
assert atts["svn_revision_number"] == "mock standard output" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assert atts["cable_branch"] == "mock standard output" | |
assert atts["svn_revision_number"] == "mock standard output" | |
assert atts["cable_branch"] == mock_subprocess.stdout | |
assert atts["svn_revision_number"] == mock_subprocess.stdout |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 95f1871
Co-authored-by: Claire Carouge <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Finally, done. That took more time than expected but I think it was worth it, for benchcab and for us.
Currently, the CABLE executable is run from the current working directory instead of the task directory by using the absolute path to the executable. However, we should be running the executable from the task directory (otherwise CABLE will complain it cannot find the soil and PFT namelist files). This was a bug that was introduced in the #99. This change makes it so we change into the task directory before running the CABLE executable. Fixes #123
Currently, the CABLE executable is run from the current working directory instead of the task directory by using the absolute path to the executable. However, we should be running the executable from the task directory (otherwise CABLE will complain it cannot find the soil and PFT namelist files). This was a bug that was introduced in the #99. This change makes it so we change into the task directory before running the CABLE executable. Fixes #123
Currently,
benchcab
does not have extensive coverage in its unit tests. Parts of the code that have poor test coverage are dependent on system interaction (e.g. environment modules, running subprocess commands (SVN, build scripts, executables, PBS).This change refactors and adds unit testing for these parts of the code base. The pytest code coverage is now at 89%.
Move the code from the
benchcab.run_cable_site
module to thebenchcab.task
module. This is done so that we reduce coupling between modules that use fluxnet tasks. This also has the benefit that we can test the two modules easily by sharing the same test setup functions. We move the code from thebenchcab.run_comparison
module tobenchcab.task
module for the same reasons.Unit tests now check exception messages and standard output produced by
benchcab
for both non-verbose and verbose modes.Beware: standard output messages have been updated in some areas. These changes will need to be updated in the documentation.Changes have now been updated.Refactor the default build so that environment modules are loaded via python instead of writing module load statements into the temporary build script. This is done to reduce the complexity of the unit tests.
The stderr output from subprocess commands are now redirected to stdout so that stderr is suppressed in non-verbose mode.
Fixes #58 #22 #86 #102