-
Notifications
You must be signed in to change notification settings - Fork 57
Enhance tests for 'sh' and 'bat' shells #713
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
base: master
Are you sure you want to change the base?
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #713 +/- ##
==========================================
+ Coverage 86.98% 87.18% +0.19%
==========================================
Files 69 69
Lines 4088 4088
Branches 706 706
==========================================
+ Hits 3556 3564 +8
+ Misses 421 413 -8
Partials 111 111 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
d4b31a7 to
cba4e28
Compare
The existing tests exercise much of the code for implementing colcon's shell subsystem, but it doesn't validate the results. If the platform supports the shell, we should check that the code for appending and prepending paths to lists function as intended.
cba4e28 to
e31f37f
Compare
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.
Hi! I've tried to dig through this PR. Most of these comments are just more comments if I understand what is actually happening. So feel free to comment on those or just give a thumbs up to those that I don't say anything completely off what is actually happening.
- I've focused mostly on the .bat scripts (as that is what I'll mostly look at as committer for windows efforts)
- The tests of this PR are failing as it is actually now seeing trailing colons (on macOS), which you already indicated in #712 . So that PR indeed would need to be merged first.
- I actually found 2 potentially missed asserts (
prefix_script.exists()) so if that is indeed a mistake then I actually contributed something to this PR 😄
| extension.create_prefix_script(prefix_path, False) | ||
| assert (prefix_path / 'local_setup.bat').exists() | ||
|
|
||
| # create_hook_append_value |
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.
As I understood it, these are tests for create_hook_append_value() and create_hook_prepend_value() that were already included, but the only thing that changed was that now the hook_path is changed to 2 separate values to be used later.
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.
Yes. The original test only exercised the code that generates the hooks. In order to actually invoke the hooks to check that they do the right thing, I had to change the call site a little bit.
| # create_hook_prepend_value | ||
| prepend_hook_path = extension.create_hook_prepend_value( | ||
| 'prepend_env_hook_name', prefix_path, 'pkg_name', | ||
| 'PREPEND_NAME', 'subdirectory') |
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.
To rename "NAME" and "env_hook_name.bat" would also be a better cleanup indeed.
Just a thought... would it make sense to also have a different name for the subdirectory? like 'append_subdirectory' or 'prepend_subdirectory' ? Probably won't matter in practice but it might make things clearer at the later tests.
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.
To rename "NAME" and "env_hook_name.bat" would also be a better cleanup indeed.
👍
Just a thought... would it make sense to also have a different name for the subdirectory? like 'append_subdirectory' or 'prepend_subdirectory' ? Probably won't matter in practice but it might make things clearer at the later tests.
I think I'll decline that change for now. It would validate that we never "cross the streams" between the hooks, but I don't think there's a strong risk of that happening. The added complexity to this (already complicated) test probably doesn't justify the additional coverage.
| extension.create_package_script( | ||
| prefix_path, 'pkg_name', [ | ||
| ('hookA.bat', '/some/path/hookA.bat'), | ||
| (append_hook_path.relative_to(prefix_path), ()), |
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.
Good efficient use of the earlier made temporary hookfiles to use for this test, which also tests if no 'non bat files' are added to the path as well.
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.
👍
| subdirectory_path = str(prefix_path / 'subdirectory') | ||
|
|
||
| # validate appending/prepending without existing values | ||
| with patch.dict(os.environ) as env_patch: |
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.
So these tests checks if nothing weird is added both the APPEND_NAME and PREPEND_NAME path (where the hookscripts are located) if only none is added (then it should be just the initial path names). I haven't worked with mock.patch myself but I looked it up and learned these changes to the environment paths here in the with will not actually happen in the real environment path (hence it comes from mock). So I hope I've understood this correctly.
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.
So these tests checks if nothing weird is added both the APPEND_NAME and PREPEND_NAME path...
Not quite. I'm using dict.pop to remove the values from os.environ (or rather, the mock of os.environ). I'm not 100% certain, but I think this is just a safeguard just in case someone invokes the test with those environment variables set to something already. There doesn't seem to be a good way to use patch.dict to remove items in a single call, so I had to make follow-up calls to "set up" the mock for the actual test.
I haven't worked with mock.patch myself but I looked it up and learned these changes to the environment paths here in the with will not actually happen in the real environment path (hence it comes from mock).
Yeah, we're essentially replacing the os.environ Python object with a shim. In this case, the mocked os.environ is the same as the original, but with APPEND_NAME and PREPEND_NAME removed (if they were present at all). When the mock scope exits, the original os.environ object is restored.
I've encountered issues with Python's handling of environment variables in other contexts, if you're interested. In short, the os.environ dict is really just a cache of the process' actual environment table, and it's possible for them to get out of sync.
test/test_shell_bat.py
Outdated
| # create_prefix_script | ||
| extension.create_prefix_script(prefix_path, True) | ||
| prefix_script = prefix_path / 'local_setup.bat' | ||
| prefix_script.exists() |
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.
Wait... This might be mistake. Shouldn't prefix_script.exists() be an assert as well?
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.
| prefix_script.exists() | |
| assert prefix_script.exists() |
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.
Well spotted! I'll update.
| if sys.platform == 'win32': | ||
| subdirectory_path = str(prefix_path / 'subdirectory') | ||
|
|
||
| # validate appending/prepending without existing values |
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.
Same test as the one int _test_extensions, but now with local_setup.bat. As are the 'existing values' and 'unique values' tests.
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.
👍 Same assertions, different process.
| subdirectory_path, | ||
| 'control', | ||
| )) | ||
| # TODO: The DsvShell behavior doesn't align with BatShell! |
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.
Ah oke! Does that mean that this test will assert due to this behavior?
I know too little about dsv-shells... but will this assert always fail even though the behavior is correct?
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.
This test is asserting the existing behavior. If DsvShell is what we decide to change, then this test will need to be updated when the behavior is reconciled.
I'd guess we could no-op or comment out the check until that time, but even given the differing behavior the test is still useful to prevent regression.
test/test_shell_sh.py
Outdated
| # create_prefix_script | ||
| extension.create_prefix_script(prefix_path, True) | ||
| prefix_script = prefix_path / 'local_setup.sh' | ||
| prefix_script.exists() |
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.
Same here as in the test_shell_bat_.py: Shouldn't this be an assert?
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.
| prefix_script.exists() | |
| assert prefix_script.exists() |
| )) | ||
|
|
||
|
|
||
| async def _run_prefix_script(prefix_script): |
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.
Ah oke, Didn't know that this was a difference between sh and bat, that in command prompt (bat) the seperation of environment variables worked differently.
Wouldn't this try except work for the .bat test script as well? and if so, then could these test_files for both bat and sh files also result from a template file?
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.
The only reason this try/except is here is to deal with implementations of env which don't support null separation. The null separation is useful for dealing with environment variable values which contain newlines, so we prefer that method when supported.
For bat, we're using set and not env. I don't believe set can output with null separation. On that note, it would be a good idea to validate that we're handling newlines properly in bat at some point. Maybe there's a similar concept we can leverage but I haven't actually looked.
|
Also another question, perhaps more of a design question than about this PR... how about the local_setup.ps1 scripts for powershell? those are generated but not tested I see. |
Shells other than They should have their own tests. It's very likely that the additional checks I'm adding here are appropriate for those other shells as well, but I'm not sure I have the bandwidth to port them over any time soon. Help Wanted 😁 |
Co-authored-by: knmcguire <kimberleymcguire@gmail.com>
cottsay
left a comment
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.
This was a really helpful and thorough review, @knmcguire. Thank you.
Sorry it took so long to get back to you.
| # create_hook_prepend_value | ||
| prepend_hook_path = extension.create_hook_prepend_value( | ||
| 'prepend_env_hook_name', prefix_path, 'pkg_name', | ||
| 'PREPEND_NAME', 'subdirectory') |
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.
To rename "NAME" and "env_hook_name.bat" would also be a better cleanup indeed.
👍
Just a thought... would it make sense to also have a different name for the subdirectory? like 'append_subdirectory' or 'prepend_subdirectory' ? Probably won't matter in practice but it might make things clearer at the later tests.
I think I'll decline that change for now. It would validate that we never "cross the streams" between the hooks, but I don't think there's a strong risk of that happening. The added complexity to this (already complicated) test probably doesn't justify the additional coverage.
| extension.create_package_script( | ||
| prefix_path, 'pkg_name', [ | ||
| ('hookA.bat', '/some/path/hookA.bat'), | ||
| (append_hook_path.relative_to(prefix_path), ()), |
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.
👍
| subdirectory_path = str(prefix_path / 'subdirectory') | ||
|
|
||
| # validate appending/prepending without existing values | ||
| with patch.dict(os.environ) as env_patch: |
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.
So these tests checks if nothing weird is added both the APPEND_NAME and PREPEND_NAME path...
Not quite. I'm using dict.pop to remove the values from os.environ (or rather, the mock of os.environ). I'm not 100% certain, but I think this is just a safeguard just in case someone invokes the test with those environment variables set to something already. There doesn't seem to be a good way to use patch.dict to remove items in a single call, so I had to make follow-up calls to "set up" the mock for the actual test.
I haven't worked with mock.patch myself but I looked it up and learned these changes to the environment paths here in the with will not actually happen in the real environment path (hence it comes from mock).
Yeah, we're essentially replacing the os.environ Python object with a shim. In this case, the mocked os.environ is the same as the original, but with APPEND_NAME and PREPEND_NAME removed (if they were present at all). When the mock scope exits, the original os.environ object is restored.
I've encountered issues with Python's handling of environment variables in other contexts, if you're interested. In short, the os.environ dict is really just a cache of the process' actual environment table, and it's possible for them to get out of sync.
| assert env.get('APPEND_NAME') == subdirectory_path | ||
| assert env.get('PREPEND_NAME') == subdirectory_path | ||
|
|
||
| # validate appending/prepending with existing values |
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.
the append and prepend hook scripts are supposed to add the subdirectory path to the append and prepend paths.
💯
| dsv_extension = DsvShell() | ||
|
|
||
| # create_hook_append_value | ||
| append_hook_path = dsv_extension.create_hook_append_value( |
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.
Yeah, the two extensions operate in parallel to support two different scenarios. See my other comment on how they differ. In short, it should be possible to transition away from the native hooks and only produce dsv hooks for these well-defined operations like appending/prepending, and it would probably be more performant. Not something I'm currently tracking though.
| if sys.platform == 'win32': | ||
| subdirectory_path = str(prefix_path / 'subdirectory') | ||
|
|
||
| # validate appending/prepending without existing values |
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.
👍 Same assertions, different process.
| subdirectory_path, | ||
| 'control', | ||
| )) | ||
| # TODO: The DsvShell behavior doesn't align with BatShell! |
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.
This test is asserting the existing behavior. If DsvShell is what we decide to change, then this test will need to be updated when the behavior is reconciled.
I'd guess we could no-op or comment out the check until that time, but even given the differing behavior the test is still useful to prevent regression.
| )) | ||
|
|
||
|
|
||
| async def _run_prefix_script(prefix_script): |
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.
The only reason this try/except is here is to deal with implementations of env which don't support null separation. The null separation is useful for dealing with environment variable values which contain newlines, so we prefer that method when supported.
For bat, we're using set and not env. I don't believe set can output with null separation. On that note, it would be a good idea to validate that we're handling newlines properly in bat at some point. Maybe there's a similar concept we can leverage but I haven't actually looked.
| extension.create_prefix_script(prefix_path, False) | ||
| assert (prefix_path / 'local_setup.bat').exists() | ||
|
|
||
| # create_hook_append_value |
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.
Yes. The original test only exercised the code that generates the hooks. In order to actually invoke the hooks to check that they do the right thing, I had to change the call site a little bit.
4cff594 to
0a2f7f9
Compare
Previously the test was setting the variables to an empty value, which isn't the same on POSIX.
The existing tests exercise much of the code for implementing colcon's shell subsystem, but it doesn't validate the results. If the platform supports the shell, we should check that the code for appending and prepending paths to lists function as intended.
In each test module, the first block of changes is just a refactor so that we reference the hooks generated by the calls to
create_hook_*. Previously, the test only validated that the hook could be generated, and didn't attempt to actually use it.The second block adds a handful of assertions about the behavior of the hooks when executed.