-
-
Notifications
You must be signed in to change notification settings - Fork 14.5k
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
lib.path.subpath.join: init #209099
lib.path.subpath.join: init #209099
Conversation
lib/path/tests/prop.nix
Outdated
# Check the properties for all lists of strings | ||
subpathJoinCheckAll = | ||
assert all subpathJoinCheck lists; | ||
null; | ||
|
||
in builtins.seq subpathJoinCheckAll | ||
(genAttrs strings normaliseAndCheck) |
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 is a bit ugly. Given that we will extend the test suite anyway (I haven't looked at the other PRs yet), maybe we should do a little bit of generalisation. I feel uneasy about the change to the randomly generated paths, because it makes the whole setup more complicated and significantly harder to understand before getting into the actual testing. Have you considered approaching this differently? Instead of producing lists of paths in the random generator, produce a sequence of random numbers (which can be reused for other purposes), pass that and do the chunking in Nix.
Maybe we could also have one Nix file per test, and import each from an entry point that does the pre-processing of the input? Then each file can take whatever data type it cares about, directly. The main file will then only combine the tests in a way that produces output as needed for further processing back in the shell. I suppose, with the other tests, we will have a series of asserts and then produce a value, but having that in one place without the other noise will allow for easier changes of factoring.
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.
It would be nice not to have the generation logic in Nix expressions, to decouple the Nix tests from the test runner. However, we also use this pattern for invoking external commands that we model in Nix, such as realpath
, so we can't easily get away from all coupling between framework and tests.
I do think it's worth exploring for the sake of writing Nix-only tests in a consistent style where all domain-specific testing logic is in the same language.
Instead of generating examples or a large chunk of randomness ahead of time, we could easily create a pseudorandom number generator by invoking builtins.hashString "sha256"
repeatedly on a state variable, starting from a small seed that's generated outside of Nix. This achieves the goal without ever running out of randomness, and therefore without any coupling with the non-Nix world. The state variable can be handled by a simplified Nix equivalent of the quickcheck Gen
library. Instead of applicative we could have a single function that takes an attrset of "gens" and a function matching those attr names, etc.
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 don't agree we should push toward implementing more things in the Nix language, even if it's possible and not all that hard to do. It will happen anyway, but should happen much more slowly and deliberately. That is, I wouldn't oppose a QuickCheck implementation or PRs to migrate existing tests to it. But for the sake of testing this library, simple Nix expressions that batch-process inputs should do.
I'm just concerned with adding too many things at once and losing track of what we want to achieve in a given PR.
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.
Since this is test code, we could maybe even allow using the cursed builtins.exec
:
$ nix-instantiate --eval -E 'builtins.exec [ "realpath" "-m" "//foo/./bar/." ]' --allow-unsafe-native-code-during-evaluation
/foo/bar
But no I don't think it would be a great idea to write all tests in Nix itself. Nix is just not made for this kind of thing. One of the main restrictions is that we can't check for all kinds of evaluation failures. builtins.tryEval
only works for throw
and assert
, no other failures, and it doesn't allow checking for the error message either. Randomness too is not something Nix was made for. Yes we can hack around it with builtins.hashString
, custom PRNG's (I even wrote one in Nix myself some time ago) and monadic generator passing, but that's gonna be painful.
I think something like https://github.com/Mic92/pythonix is the right way to go. Unfortunately that project is archived now, but it works by linking to libexpr
directly and using that to evaluate expressions. This way failures can be caught and checked. The main problem with this is that libexpr
isn't stable, so it requires continuous maintenance. But using that approach testing libraries of generic programming languages can be used.
The alternative of just using the CLI as a stable API has the problem that it's way too slow (this is why I opted for just a single CLI call in these tests here).
I'll see if I can do some more decoupling for the property tests here though. Maybe having a separate file/folder for every property to check and using the .nix
file only for batch evaluation and no checking.
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 started this: #212858
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 it would be better to just drop the property tests from this PR. I don't want to have this be blocked on improved property tests. There's still unit tests as well.
lib/path/tests/generate.awk
Outdated
uppernewstring = upperslash + 1 + extranullweight | ||
|
||
total = uppernewstring + 1 + extraelemweight |
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'm really not fond of this extra complexity, see the comment at the end of the Nix file.
620a295
to
02b79c8
Compare
- Use isValid when possible instead of subpathInvalidReason: NixOS#209099 (comment) - Add documentation to function arguments - Use newlines for error messages: NixOS#208887 (comment) - Add short comments for the unit test groups: NixOS#208887 (comment) - Slight formatting improvement for laws: NixOS#209099 (comment)
02b79c8
to
bbc4bfc
Compare
bbc4bfc
to
325009d
Compare
@fricklerhandwerk @roberth I'm now working on NixOS/nix#7735 to have better Nix testing support in the future. For now I removed the more hacky property tests from this PR so it's not blocked on that, there's still unit tests in any case. I also rebased this PR after the merge of #208887, to me this PR looks good like this |
lib/path/default.nix
Outdated
assert assertAll isValid subpaths (i: path: '' | ||
lib.path.subpath.join: Element at index ${toString i} is not a valid subpath string: | ||
${subpathInvalidReason 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.
I simplified this assertion a lot by introducing lib.asserts.assertAll
, now included in this PR. #209375 will also be able to make use of that.
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.
Never mind, I split that function into #215166
4930120
to
1fc4e26
Compare
lib/path/default.nix
Outdated
assert assertMsg | ||
# Fast in case all paths are valid | ||
(all isValid subpaths) | ||
# Otherwise we take our time to gather more info for a better error message | ||
# Strictly go through each path, throwing on the first invalid one | ||
# Tracks the list index in the fold accumulator | ||
(foldl' (i: path: | ||
if isValid path | ||
then i + 1 | ||
else throw '' | ||
lib.path.subpath.join: Element at index ${toString i} is not a valid subpath string: | ||
${subpathInvalidReason path}'' | ||
) 0 subpaths); |
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 also simplified this a bit to remove the seq
and abort
fallthru thing, I think that was overengineered.
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 really don't like the abuse of the exception "side effect" here.
It starts with assert
, which is never triggered and then it continues with assertMsg
, which is also never triggered. The final error happens to be in the error message of assertMsg
, evaluated when it almost triggers.
This is enough reason to just use an if
expression instead.
Final nitpick: assertions are for checking the invariants of self-contained software units, and not for input validation across a public interface. See assert (verb)
1. To declare with assurance or plainly and strongly; to state positively.
[...]
(programming) To specify that a condition or expression is true at a certain point in the code.
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 point, using an if
now.
Regarding assertions in general, yeah maybe assert
is being abused in that way in Nix, just because it's somewhat easier to type. For now it seems to be the pattern though, so I still think that with #215166 it's okay to replace this if
here with an assert assertAll isValid subpaths
- Use isValid when possible instead of subpathInvalidReason: NixOS/nixpkgs#209099 (comment) - Add documentation to function arguments - Use newlines for error messages: NixOS/nixpkgs#208887 (comment) - Add short comments for the unit test groups: NixOS/nixpkgs#208887 (comment) - Slight formatting improvement for laws: NixOS/nixpkgs#209099 (comment)
- Use isValid when possible instead of subpathInvalidReason: NixOS/nixpkgs#209099 (comment) - Add documentation to function arguments - Use newlines for error messages: NixOS/nixpkgs#208887 (comment) - Add short comments for the unit test groups: NixOS/nixpkgs#208887 (comment) - Slight formatting improvement for laws: NixOS/nixpkgs#209099 (comment)
This function can be used to safely join subpaths together
1fc4e26
to
1a2c284
Compare
- Use isValid when possible instead of subpathInvalidReason: NixOS#209099 (comment) - Add documentation to function arguments - Use newlines for error messages: NixOS#208887 (comment) - Add short comments for the unit test groups: NixOS#208887 (comment) - Slight formatting improvement for laws: NixOS#209099 (comment)
This PR isn't strictly blocked by anything, so I'm going to merge this soon |
- Use isValid when possible instead of subpathInvalidReason: NixOS/nixpkgs#209099 (comment) - Add documentation to function arguments - Use newlines for error messages: NixOS/nixpkgs#208887 (comment) - Add short comments for the unit test groups: NixOS/nixpkgs#208887 (comment) - Slight formatting improvement for laws: NixOS/nixpkgs#209099 (comment)
Description of changes
This function can be used to safely join subpaths together, a bit like
concatStringsSep "/"
, but safer. Relates to other work in the path library effort.This work is sponsored by Antithesis ✨
Things done