-
Notifications
You must be signed in to change notification settings - Fork 3
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
Adopt subprocess fixture for testing. #391
base: develop
Are you sure you want to change the base?
Adopt subprocess fixture for testing. #391
Conversation
# Conflicts: # source/fab/tools/compiler.py # tests/unit_tests/steps/test_link.py # tests/unit_tests/tools/test_compiler.py # tests/unit_tests/tools/test_compiler_wrapper.py # tests/unit_tests/tools/test_linker.py
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 only got through parts of it, will try to continue either later today or tomorrow.
@@ -82,7 +82,7 @@ def add(self, collection: Union[str, ArtefactSet], | |||
self[collection].update(files) | |||
|
|||
def update_dict(self, collection: Union[str, ArtefactSet], | |||
key: str, values: Union[str, Iterable]): | |||
key: Optional[str], values: Union[str, Iterable]): | |||
'''For ArtefactSets that are a dictionary of sets: update |
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.
You know that better than me: it appears that only one test supplies a 'None' as key. If None is otherwise not expected, I would suggest to change the test instead of allowing None here.
@@ -98,7 +98,7 @@ def from_dict(cls, d): | |||
return result | |||
|
|||
|
|||
def extract_sub_tree(source_tree: Dict[Path, AnalysedDependent], root: Path, verbose=False)\ | |||
def extract_sub_tree(source_tree: Mapping[Path, AnalysedDependent], root: Path, verbose=False)\ |
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.
Just for my own education: what is the advantage of using Mapping? I thought mapping is more generic than Dict, so if we only expect dict, shouldn't we use dict?
@@ -79,7 +79,8 @@ def run_mp_imap(config, items, func, result_handler): | |||
result_handler(analysis_results) | |||
|
|||
|
|||
def check_for_errors(results, caller_label=None): | |||
def check_for_errors(results: Iterable[Union[str, Exception]], |
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.
Thanks for adding more type hints!
@@ -56,6 +56,7 @@ def __init__(self, name: str, | |||
compile_flag: Optional[str] = None, | |||
output_flag: Optional[str] = None, | |||
openmp_flag: Optional[str] = None, | |||
version_argument: Optional[str] = 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 been thinking that I should have added this - thanks!
self._version_regex = version_regex | ||
|
||
@property | ||
def mpi(self) -> bool: | ||
''':returns: whether this compiler supports MPI or not.''' | ||
'''Returns whether this compiler supports MPI or not.''' |
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.
Why did you remove the ::
? Using :returns:
means that if we ever automatically create a reference guide using sphinx or so, the return value will be properly documented
(see e.g. https://psyclone-ref.readthedocs.io/en/latest/autogenerated/psyclone.core.signature.html#psyclone.core.signature.Signature.is_structure)
@@ -82,7 +84,7 @@ def openmp(self) -> bool: | |||
|
|||
@property | |||
def openmp_flag(self) -> str: | |||
''':returns: the flag to enable OpenMP.''' | |||
'''Returns the flag to enable OpenMP.''' |
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.
ditto
@@ -163,15 +162,15 @@ def compile_file(self, input_file: Path, | |||
# which also supports the syntax_only flag anyway) | |||
self._compiler = cast(FortranCompiler, self._compiler) | |||
self._compiler.compile_file(input_file, output_file, openmp=openmp, | |||
add_flags=self.flags + add_flags, | |||
add_flags=self._flags + add_flags, |
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 would prefer using the implemented property, not the private attribute. While this has a tiny bit of overhead, the advantage is that this can be overwritten by a derived class. Now, I find overwriting a property bad tbh, and I believe I fix this properly in the compiler profile support PR, where I turns this into a real function get_flags()
(iirc)
syntax_only=syntax_only, | ||
) | ||
else: | ||
if syntax_only is not None: | ||
raise RuntimeError(f"Syntax-only cannot be used with compiler " | ||
f"'{self.name}'.") | ||
self._compiler.compile_file(input_file, output_file, openmp=openmp, | ||
add_flags=self.flags+add_flags | ||
add_flags=self._flags+add_flags |
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.
Ditto - I would prefer this to stay, so I have less conflicts to resolve later ;)
@@ -182,7 +182,7 @@ def run(self, | |||
res = subprocess.run(command, capture_output=capture_output, | |||
env=env, cwd=cwd, check=False) | |||
except FileNotFoundError as err: | |||
raise RuntimeError(f"Command '{command}' could not be " | |||
raise RuntimeError(f"Command '{' '.join(command)}' could not be " |
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 think we should stay with the original version. Since our subprocess does not use a shell, that means that a space in a parameter will potentially result in errors (e.g. ["-l /some_lib"]
will not be recognised by the linker). While I actually prefer the new look, and it would make it easier for other develoeprs, this error message will then totally hide what the problem is, which will lead to frustrating debugging.
Alternatively, I would be happy to use a shell in subprocess, but you didn't want to do that.
@@ -75,7 +75,7 @@ def __init__(self): | |||
# Add the common shells. While Fab itself does not need this, | |||
# it is a very convenient tool for user configuration (e.g. to | |||
# query nc-config etc) | |||
for shell_name in ["sh", "bash", "ksh", "dash"]: | |||
for shell_name in ["sh", "bash", "ksh", "dash", 'zsh']: |
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.
OK, I am totally confused now. I am just preparing the shell PR (that you have already reviewed) to go into main, and one comment from you was to only support sh
. So I have modified this branch before submitting it to only support sh
, but you are adding more shells. What do you prefer? I am happy to go either way
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.
Still more to come, sorry
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.
Is that file required?
from fab.tools.tool_box import ToolBox | ||
|
||
|
||
def not_found_callback(process): |
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.
Could you please add some comments to all these functions.
psyclone_tool = Psyclone() | ||
psyclone_tool._version = (2, 4, 0) | ||
psyclone_tool._is_available = True | ||
|
||
mock_transformation_script = mock.Mock(return_value=__file__) | ||
with mock.patch('fab.tools.psyclone.Psyclone.run') as mock_run_command: | ||
mock_transformation_script.return_value = Path(__file__) | ||
psyclone_tool.process(api=psyclone_lfric_api, | ||
psyclone_tool.process(api='dynamo0.3', |
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.
While I am ok with removing the psyclone_lfric_api
definintion (which allowed them all to be changed at one location), you then need to switch to lfric
, not dynamo0.3
, which is deprecated.
@@ -219,7 +227,7 @@ def test_transformation_script(self, psyclone_lfric_api): | |||
mock_transformation_script.assert_called_once_with(Path(__file__), None) | |||
# check transformation_script is passed to psyclone command with '-s' | |||
mock_run_command.assert_called_with( | |||
additional_parameters=['-api', psyclone_lfric_api, | |||
additional_parameters=['-api', 'dynamo0.3', |
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.
lfric
mp_common_args = Mock(config=config) | ||
with raises(RuntimeError) as err: | ||
_compile_file((Mock(), mp_common_args)) | ||
assert str(err.value) == "Unexpected tool 'some C compiler' of category " \ |
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.
pep8 (https://peps.python.org/pep-0008/#maximum-line-length):
"Long lines can be broken over multiple lines by wrapping expressions in parentheses. These should be used in preference to using a backslash for line continuation."
""" | ||
config, _ = content | ||
|
||
monkeypatch.setenv('CFLAGS', '-Denv_flag') |
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.
Oh, I didn't know that - that's neat :)
kwargs = {'project_label': 'fortran explicit gfortran', 'fab_workspace': tmp_path, 'multiprocessing': False} | ||
ToDo: Fragility due to assumption of source donor. | ||
""" | ||
pytest.importorskip('clang', reason="Missing libclang bindings.") |
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.
Nice!
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.
OK, I am stopping here and pass it back to you (extra long weekend) - main reason: could you explain to me the advantage of using fs
? I have the feeling I am missing something :) So basically, it is a temporary directory, that is just automatically cleared at the end of a test. In my (PSyclone-heavy ;) ) experience, I often use the files in tmp to debug failing tests. So if files are instead created in tmp, I can't debug them to see e.g. what might be wrong. We also have a fixture for changing into tmpdir, which seems to be the equivalent of fs, except in case of failures you can analyse the files. I would also prefer slightly smaller PRs ;) I am aware that this is already a split of an even larger one, but I am sure I missed a fs
fixture a couple of times before wondering what that might be ;) I'll pass it back to you at least for clarification of fs
, and pick it up next week.
Tests the archive step. | ||
""" | ||
def test_for_exes(self, stub_tool_box: ToolBox, | ||
fs, fake_process: FakeProcess) -> 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.
Not your fault of course - but that is a HORRIBLE name. Took me forever to find out what on earth fs
might even be. Luckily there was one test that typed it :)
Oh, also bringing it up to recent |
Rather than variations of the same hand-mocking of shelling out to a tool this change takes advantage of an existing fixture.
It also pulls in a fixture to create a fake filing system in memory which should be faster and less prone to faults based on what the developer has in their file tree.