Allow setup scripts to define specific environment variables#3001
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3001 +/- ##
==========================================
+ Coverage 85.11% 85.20% +0.08%
==========================================
Files 157 158 +1
Lines 46125 46366 +241
==========================================
+ Hits 39259 39504 +245
+ Misses 6866 6862 -4 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
5dda85f to
b899406
Compare
968d296 to
aa6bf5c
Compare
sunshowers
left a comment
There was a problem hiding this comment.
Thanks for doing this!
if the setup script writes the NEXTEST prefixed variable to NEXTEST_ENV, it seems to cause all environment variables assigned to that file be ignored and thus not passed to the intended tests. I'd expect that it would rather only ignore values keyed under NEXTEST* and thus the test will see all other values under valid keys.
Interesting -- I think this should just error out loudly instead (this is an experimental feature so it's fine to change it). I'd collect all env vars that are bad and explicitly error out on them.
| env = { | ||
| MODE = "qux_mode", | ||
| } |
There was a problem hiding this comment.
env should be part of command (always requiring passing in as a map)
There was a problem hiding this comment.
Yep, and this makes a lot of sense given how command is effectively shared with wrapper scripts. Naturally having wrapper scripts support environment variables will take some additional work - should that be included with this pull request?
In any case I've pushed a commit (without flattening onto the original) to reflect this - please let me know if flattening this new commit onto the existing one is required.
| [[profile.default.scripts]] | ||
| platform = { host = "cfg(unix)" } | ||
| filter = "test(=test_setup_script_env_vars)" | ||
| setup = "my-script-unix-with-env" | ||
|
|
||
| [[profile.default.scripts]] | ||
| platform = { host = "cfg(windows)" } | ||
| filter = "test(=test_setup_script_env_vars)" | ||
| setup = "my-script-windows-with-env" | ||
|
|
There was a problem hiding this comment.
Do we need an extra test? Seems like we can combine this check into test_cargo_env_vars.
There was a problem hiding this comment.
I had this extra definition and separate test so that I don't touch the existing definition that is working without environment variables defined, as the idea of this change is to be able to specify different environment variables for the same underlying script (e.g. my-script.sh), hence why I ultimately split this out.
That said It should be possible to combine it into test_cargo_env_vars if this is what's desired. Please let me know if this is the case.
| NEXTEST = "0", | ||
| NEXTEST_PROFILE = "0", |
There was a problem hiding this comment.
I think these should error out similar to the environment that setup scripts set.
There was a problem hiding this comment.
By error out, should this be done at the point of parsing?
|
Thanks for the feedback on the pull request. I will be working on the suggested changes in about a weeks time once my partner and our newborn is settled back at home hopefully soon. The delay in response is due to this evolving personal circumstance, but please know that I do want to get this change in so I may get rid of the workaround using platform specific scripts for the other project where I'm a contributor of. |
|
Congratulations! Hope everyone's well 🩷 |
0fae3c2 to
8275c3c
Compare
I had a bit more time to chase this down (between newborn needs) and the following are just notes and observations that I've made. The current behavior of I also noted that when the setup script is finished it gets reported when the display reporter handles a In both options for So by "explicitly error out on them" (when a reserved key is set), should the behavior of this setup script be of an failure and trigger early abort the test run, much like how a non-zero exit code does? Or take a more lenient approach by collecting the env keys that are reserved and then ensure that they all show up under |
|
Sorry for the delay, work has kept me very busy.
Yeah, that is definitely what we want here -- fail loudly. Working on fixing this right now -- thanks for catching! edit: fixed in #3094. |
|
Thanks for the fix! Also the pull request you just did is also quite instructive with regards to project organization. I will tidy up this pull request in due time, and let me know if this plan works for you (to address the two remaining issues raised during the code review):
|
|
Sounds great! |
fbe6f7a to
8bbca44
Compare
|
I ended up squashing all the code commits into one, given the intrusive nature of the snapshot modifications from that one test that got added then removed (conflicting with the current main branch, the merge target) and that some of the later commits ended up being rather trivial (e.g. adding tests). Should the original commits are of interests, I pushed a separate branch with most of the individual commits based on the earlier commit that I was working on top of. Sorry for the wait, the needs of a newborn are many after all. |
| assert!(matches!( | ||
| std::env::var("CMD_ENV_VAR").as_deref(), | ||
| Err(&std::env::VarError::NotPresent) | Ok("not-set-in-conf"), | ||
| )); |
There was a problem hiding this comment.
Is this ever not present? If so, could this be written as an exhaustive match? If not, then just assert on the specific value.
There was a problem hiding this comment.
I ended up resorting to an additional environment variable specific to test_setup_script_defined_env for the disambiguation of this particular test, which is also part of the fix that addresses the location of test_list.cargo_env(), which is done in 0d00ce9.
| program, | ||
| args, | ||
| env: BTreeMap::new(), | ||
| relative_to: ScriptCommandRelativeTo::None, |
There was a problem hiding this comment.
Ah so I realized that ScriptCommand is used for both setup and wrapper scripts, but we only apply env to setup scripts. Could you also extend env to apply to wrapper scripts (and add a test)?
There was a problem hiding this comment.
Yeah I was thinking of doing this in a separate pull request, but we can do it here in one go also. It is now done in 68919b3.
| // Set the additional user-provided environment variables assigned to the setup | ||
| // script configuration after the global values assigned above but before the | ||
| // test runner controlled ones which are assigned below, as per above note. | ||
| cmd.envs(config.command.env.iter()); |
There was a problem hiding this comment.
To ensure that current and future keys are covered, can we use an exhaustive destructure here (and in wrapper scripts)?
There was a problem hiding this comment.
Sorry I am not following this particular comment, as the intention is to assign all arbitrary keys and values in this map to the command, the keys and values here should have been validated by this point given the deserializer provided that. I thought assignment of all values with an iterator is suitable, so I am not sure what is meant by an exhaustive destructure on an arbitrary map here.
Edit: Unless of course you mean I should use cmd.env on each of the key/value pairs obtained by destructuring every single entry inside config.command.env rather than simply using cmd.envs, though if we are going down this far, there should probably be a newtype for this BTreeMap and implement fn apply_env(cmd: &mut Command) for that with the additional filtering/validation logic if this is what you are truly after, though at this point that particular map, if deserialized through serde, should have the unexpected values filtered out.
That said, should we have this newtype, the needs from this comment can simply be addressed.
| if key.starts_with("NEXTEST") { | ||
| return Err(A::Error::invalid_value( | ||
| serde::de::Unexpected::Str(&key), | ||
| &"a key that does not begin with `NEXTEST`, which is reserved for internal use", | ||
| )); |
There was a problem hiding this comment.
I feel like we should apply some more checks to the environment key here (e.g. not supporting cases like ' ') though we have this issue elsewhere so that can be a followup.
There was a problem hiding this comment.
I ended up started working on this too as part of the above comment about environment keys, since I found relevant info about environment variables in POSIX.1-2024. Will push new commit hopefully in the next couple days.
There was a problem hiding this comment.
I added 6fba33b and c0f5273 to address the need to do this check. Refer to the lengthy comment before ENV_KEY_VALIDATION which was added in 6fba33b for the background I've included with why the restriction and checks are in place. In c0f5273 I added the minimal viable implementation to propagate the new error returned from the validation back up to where they may be consumed, so the level of detail captured might be lower than expected. Though given how the validation should have been done during deserialization, the code paths that can result in invalid environment variables keys triggering the error might not actually be hit under usage through the CLI/configuration files. Anyway I am looking forward to what you think, and please let me know if and what else I should improve.
| ); | ||
|
|
||
| assert_eq!(std::env::var("MY_ENV_VAR").as_deref(), Ok("my-env-var")); | ||
| // The command for the script has received the value |
There was a problem hiding this comment.
Please add a period to the end of this comment.
There was a problem hiding this comment.
Not sure how I managed to delete the rest of that sentence without noticing it myself, but this is now addressed with the fix to the ordering of test_list.cargo_env().apply_env in 0d00ce9.
| } | ||
| } | ||
|
|
||
| deserializer.deserialize_any(EnvMapVisitor) |
| @@ -314,6 +314,11 @@ impl SetupScriptCommand { | |||
| // `CARGO_*` and `NEXTEST_*` variables set directly on `cmd` below. | |||
There was a problem hiding this comment.
I believe this comment may not be true any more -- I think test_list.cargo_env().apply_env should move below cmd.envs.
There was a problem hiding this comment.
Yeah, I should have investigated what test_list.cargo_env() does in greater detail, indeed it does not inhibit how I want to apply command.env. Swapping it around as suggested does not change the intended behavior where command.env overrides standard environment variables, and on top of that, doing so will allow force = true to function as intended to override values specified in command.env. I've updated the code and extended the test case to account for this which is done in 0d00ce9.
35b376c to
9489b09
Compare
Allow specific environment variables be defined for a given setup script command such that they don't need to be wrapped by platform specific scripts (i.e. batch file vs. bash) to set them up when they may be platform agnostic. This also has the effect that allows specific environment variables be passed to specific tests via a simple wrapper program that may be configured directly on the nextest configuration. Ensure that environment variables (by extension, the `env` from Cargo's config files) will not override the values defined in the command's `env` section, and that supplied keys cannot begin with `NEXTEST`.
Use another special environment variable to test for expected values that are brought active by the integration test environment. This test also validates that the expected priority of variable assigments from Cargo's config are upheld.
Have wrapper scripts also support the passing of the map of envronment variables defined in its `command.env` to the underlying command.
Additional test to show where and what values are defined when a value is defined for both wrapper and setup scripts, that the setup script will still receive the correct value it has been defined for.
Rounding out the coverage report, might as well cover the similarly uncovered cases.
Provide `ScriptCommandEnvMap` which uses the `validate_env_var_key` helper to validate keys during deserialization and applying of the environment variables onto a `Command`. Existing usage of `.envs()` with the original `BTreeMap` are converted to using the `.apply_env()` method, but the validation errors currently ignored as the caller will need to be updated to handle this new error.
Have `TestCommand::new` return a result instead with the error being a `CommandSetupError`, where the only variant being the environment variable being invalid. The consequential errors are simply added as variant under the relevant error enums, with reuse of existing structs where possible to achieve a minimally viable implementation.
9489b09 to
c0f5273
Compare
|
Looks great, thanks. I ran into the same issue with hyphens in env vars a while ago: see https://nexte.st/docs/configuration/env-vars/#environment-variables-nextest-sets ( Will do a final review and then get this in for release next week. |
Make it so that if a `ScriptCommandEnvMap` is successfully created, it has valid environment variable keys.
We should not set the environment if the target runner is inactive. Fix and add a test.
|
Thank you for your time for the code review and merging this in! |
As per the comment I made on #978, I've gone ahead and implement exactly that, a way to set specific environment variables for setup scripts.
During the construction of the test for this, I've discovered a possibly undocumented silent failure (which got me down some rabbit holes, but I digress). if the setup script writes the
NEXTESTprefixed variable toNEXTEST_ENV, it seems to cause all environment variables assigned to that file be ignored and thus not passed to the intended tests. I'd expect that it would rather only ignore values keyed underNEXTEST*and thus the test will see all other values under valid keys. I haven't dived too deep into the code for that part but maybe this could also be documented with the setup scripts if this is intended.