feat(tests): add test utils (tlib)#439
Conversation
|
Yes that would be where to pass them I didnt think about it needing all the |
Yes, I sighed when I saw that, but it was less annoying than anticipated using quickfix list and a small macro :-) Cool, I will finalize the PR soon then. |
|
oh actually. Maybe we could just export tlib from the ci flake? It will warn about that not following the schema, I kinda don't care, but also maybe we could just update { inherit tlib; } into the lib output in the ci flake and then that solves that part too? Then ppl could just grab self.tlib (or self.lib.tlib) like they currently just grab self.lib for wlib. Honestly, Im not sure if that is better though. It is just another idea, fully up to you, it doesn't matter to me. Not really sure what the best way to pass them in is, but, that is where they are called, so, whatever way it gets passed would have to go through that same place in some way. I am completely fine with the way currently shown in the PR for passing them in, so, yeah IDK, just throwing out more ideas. |
| runTests = | ||
| wrapperModule: scripts: | ||
| let | ||
| wrapper = wrapperModule.apply { inherit pkgs; }; |
There was a problem hiding this comment.
Passing the module here would be enough for the platforms guard, but I would also like to grab wrapper.binName for the testname. Hence, I need to apply pkgs here. Do you have an issue with that @BirdeeHub ?
And now since I basically have a default wrapper available, I was thinking of passing it to runTest such that they can use and extend this instead of each runTest having to define a wrapper from scratch using let ... in as I currently do in direnv/check.nix.
Thoughts @BirdeeHub ?
There was a problem hiding this comment.
As far as passing pkgs goes, sure, that is fine. They get the same pkgs given to them.
You should create the test drv with the initial cases/scripts and module they pass and stuff. I like the idea of not passing the module a bunch of times and only passing it once per test drv.
You should allow the helper to also recieve a package if you can, the stuff is exposed under passthru.configuration if you get a package.
You should try to use wlib.makeCustomizable in order to allow them to test.addTest or test.addScript or test.addCase or whatever you wanna call it afterwards too! (Not necessary though. But it would be cool right?) It is a generalization of lib.mkOverrideable that allows that function to be whatever you want.
|
I implemented the wrapper-passing-solution on a separate branch, because I am not sure whether I am happy with it: The idea is as follows: runTests self.wrappers.direnv [ # <-- 1. specify the wrapper module here
(runTest "if nix-direnv is enabled then lib/nix-direnv.sh should exists"
{ nix-direnv.enable = true; } # <-- 2. specify the test-specific wrapper config
(wrapper: [ # <-- 3. receive the wrapper package with the config from (2.) already applied
(isDirectory (getDotdir wrapper)) # <-- 4. define assertions
(isFile "${getDotdir wrapper}/lib/nix-direnv.sh")
])
)
...The example above, everything seems pretty clean imo. (
let
libScriptContent = "echo foo"; # needs to be outside of `runTest` as the scope needs to cover the config and assertions
in
(runTest "if a lib-script is set then it should be generated" { lib."foo.sh" = libScriptContent; } (
wrapper:
let
libScriptFile = "${getDotdir wrapper}/lib/foo.sh";
in
[
(isDirectory (getDotdir wrapper))
(isFile libScriptFile)
(fileContains libScriptFile libScriptContent)
]
))
)Maybe I should scratch the idea of passing the config and simply receive the wrapper (with only My issue with this is, that I have to invent a new wrapper name: (runTest "if nix-direnv is enabled then lib/nix-direnv.sh should exists" # <-- no more config param here
(wrapper:
let
wrapper2 = wrapper.wrap { nix-direnv.enable = true; }; # <-- here I need a new name
in
[
(isDirectory (getDotdir wrapper))
(isFile "${getDotdir wrapper}/lib/nix-direnv.sh")
])
)There are of course better ways to than just picking "wrapper2" but it is still odd. |
|
Ah I see. So, because the whole test owns a copy of the module, but you may want variations of it for each case. Im not sure. Maybe them owning a copy of it is the wrong abstraction? Or, you could have each case be allowed to provide its own module if they want? Then the question with that would be, should the next test get the updated state of that, or should it receive the original copy? The user should probably be able to specify that? Also, you meant this, right? (runTest "if nix-direnv is enabled then lib/nix-direnv.sh should exists" # <-- no more config param here
(wrapper:
let
wrapper2 = wrapper.wrap { nix-direnv.enable = true; }; # <-- here I need a new name
in
[
(isDirectory (getDotdir wrapper2))
(isFile "${getDotdir wrapper2}/lib/nix-direnv.sh")
])
) |
Yes, this is the most flexible solution (and basically what I had in my first draft): (runTest "if nix-direnv is disabled then lib/nix-direnv.sh should not exist" (
let
wrapper = self.wrappers.direnv.wrap {
inherit pkgs;
nix-direnv.enable = false;
};
in
[
(isDirectory (getDotdir wrapper))
(notIsFile "${getDotdir wrapper}/lib/nix-direnv.sh")
]
))Maybe I will make
I'm not sure if I understand what you mean. I think each
Yes, exactly. Forgot to update All in all, I think the initial solution was the most KISS aproach. I will see if I find a solution that keeps the KISS approach by default but allows to opt-in to declarative config solution. |
(runTest "if nix-direnv is disabled then lib/nix-direnv.sh should not exist" (
let
wrapper = self.wrappers.direnv.wrap {
inherit pkgs;
nix-direnv.enable = false;
};
in
[
(isDirectory (getDotdir wrapper))
(notIsFile "${getDotdir wrapper}/lib/nix-direnv.sh")
]
))^ I mean, they could already do that with literally any of these solutions, this isn't really what I meant. This is what you were doing with the wrapper2 thing too. I more meant, should we be allowing someone to pass an EXTRA module per each individual test case. As in, they would pass a module that extends the base one and we handle that somehow. That being said, I am starting to think that would be too much.
This is not the worst idea I have ever heard, but its still basically just doing If we only need it for name and
^ This part of the statement doesn't make sense without the first part I was trying to say. That being said, I am not opposed to simpler. I still need to try this out, I haven't tried it yet. I don't always notice what needs to be done unless I try to use it for something myself. |
|
I have the opt-in solution almost ready. I will push it soon.
Yes of course. This would be very valuable. Try it once its ready. We can still strip out the opt-in part if turns out to be unnecessary. |
|
What if instead of passing in a wrapper module to runTest at all, we just do the runCommand templating you have for runTest, and then expose a Then they can be in charge of what happens with the module and stuff and we can just focus on making helpers for turning those into tests. mkTestIfPlatform = cond: test: let
platforms = if builtins.isList then cond
else cond.passthru.configuration.meta.platforms or cond.config.meta.platforms or cond.meta.platforms or (self.lib.evalModule cond).config.meta.platforms;
in
if builtins.elem pkgs.stdenv.hostPlatform.system platforms then test else null;^? something like that? And then we don't need |
|
I created a separate PR with the changes: I've also written a section in CONTRIBUTING.md on its usage: This is more or less my proposal as of now. About your inputs in your latest comment:
Can you give me a "sketch" of your ideas in the form of a review comment somewhere here: Just like how it would look like to specify the tests with those ideas - to have a better basis for discussion? |
|
So, I will hopefully get an answer back to you about this today or tomorrow. I have a slight addition to v2 basically just this for expected instead of regular toJSON. toSanitizedJSON =
value:
if builtins.isAttrs value then
builtins.toJSON (
lib.mapAttrsRecursive (
path: v:
if builtins.isFunction v then
let
id = toString (
builtins.unsafeGetAttrPos (lib.last path) (
lib.getAttrFromPath (lib.sublist 0 (builtins.length path - 1) path) value
)
);
in
"<function${if id != "" then " ${id}" else ""}>"
else
v
) value
)
else
builtins.toJSON value;And I have another direction we could take it much like the first version which didn't accept the wrapper module as an argument, but with some slight differences. But I have been busy the last few days and haven't found the time to give a well thought-through reply. I hope to get you an example implementation and usage of what I was talking about for that other direction sometime today though. We will see. |
|
Thanks for the sanitize fn. I think I know what it does as I yesterday noticed it cannot transform arbitrary nix sets. |
You don't need to hurry. I again generalized the v2 approach a lot yesterday to also make it suitable for non-wrapper tests but general ones (like core/types/etc). Better wait until I pushed this and I will ping you once it ready. @BirdeeHub (pinging you now too, to make sure you see it and not waste time reviewing outdated code :-)) That said, the changes are really just a generalization of the current v2: It generalizes the conditional execution for platforms to generic conditions, and the passing of config (which gets transformed to a configured wrapper) to passing generic contexts |
09fb59b to
00d0454
Compare
0b7a772 to
5f70120
Compare
BirdeeHub
left a comment
There was a problem hiding this comment.
I am much happier with this design.
^ that's fine. It was kinda overkill and of limited utility. But, It calls the thing, and adds the function back to the result of the thing so you can call it again basically, and it merges the args for the calls. It works like well, that, and if you add more on top, you should set It could always be added later if we do want it. |
Oh I did check it out already and yes it is pretty cool. I honestly was just to lazy (and exhausted) to re-implement it because the cleanup turned out to be more work than anticipated and wanted to call it a day. I think we should still be able to add in in a non-breaking way, should we ever have a need for it. |
|
@BirdeeHub thanks for the review. I implemented you're ideas. Can you check again? |
| and the test will only be run if the system it is running on is supported. | ||
| If `settings.wrapper` is not set, the test will always be run. This makes sense if you | ||
| are testing core options or library functions. | ||
| If only `settings.wrapper` is set, the name will be derived from this value by suffixing it with `-test`. |
There was a problem hiding this comment.
| If only `settings.wrapper` is set, the name will be derived from this value by suffixing it with `-test`. | |
| If only `settings.wrapper` is set, the name will be derived from this value by suffixing it with `-test`. | |
| If `settings.wrapper` is not set, the test will always be run. This makes sense if you | ||
| are testing core options or library functions. | ||
| If only `settings.wrapper` is set, the name will be derived from this value by suffixing it with `-test`. | ||
| If `settings.name` is set, it will be taken as the name of the derivation. |
There was a problem hiding this comment.
| If `settings.name` is set, it will be taken as the name of the derivation. | |
| If `settings.name` is set, it will be taken as the name of the derivation. | |
|
Just fixes to the nixdoc args and some (minor) formatting nitpicks left I think!
The mdbook module is pretty nice 😸 |
|
wait what changed to make it fail? |
|
oh the fish module got added and the checks for it need |
|
I added ... to the fish tests now :) You might have to rebase on top of that somehow |
|
Also I apparently forgot a semicolon in my suggestion for enabled (which I did not test btw) |
|
@BirdeeHub don't worry, I'm still updating stuff :-) |
|
About the enable option: If I run a wrapper test on an unsupported platform, the test should not be run - independent of the enable flag, right? |
If I do Otherwise, it goes based on platform, or is enabled if not a wrapper test. So, in this case, enable does nothing unless it is false, in which case, it disables the test for sure. |
|
https://github.com/BirdeeHub/nix-wrapper-modules/pull/431/changes Also ^ this is why the enable option needs to disable and why I want it, for context. (basically, claude-code...) |
feat(wrapperModules.direnv): init style(wrapperModues.direnv): apply nix fmt fix(wrapperModules.direnv): add maintainers feat: use key direnv: add checks refactor tests rework tests use a single drv for tests rework: use nix functions as assertions This way we have a way to join assertions using '&&' and can group assertions. If any assertion fails it will print the error message of the given assertin as well as the name of the test make all tests assertion-based rename functions add documentation add mise integration fix maintainers add env.CONFIG_DIRENV remove "key"-transform hack construcFiles sanitizes the key by default now add tlib cleanup pass wrapperModule to runTests (wip) add recursive test suites (wip) wrap in 'run' function fmt add indentBlock nice indents remove level param add busted syntax adapt runTest bckp cleanup fmt add debug option support passing str assertions rewrite direnv tests to set representation remove describe/it display test name in error log simplify render introduce errMsg bckp rework settings parsing rename testWrapper -> test getting there add debug flag use toSanitizedJSON use repeatStr from wlib use exit instead of return at top level handle multiline messages formatting document test-lib.test add example for wrapper tests formatting handle rendering multi-line conditions add docs improve writing Make comment nixdoc compatible generate nixdoc comments for the predefined assertions reformat documentation for 'test' Add contributing chapter to mdbook Apply suggestion from @BirdeeHub Co-authored-by: Birdee <85372418+BirdeeHub@users.noreply.github.com> Add enable option Co-authored-by: Birdee <85372418+BirdeeHub@users.noreply.github.com> fix enable option document enable option remove direnv module Will be added in a separate PR
|
Ok, I think I'm done with the changes. Rebased & squashed + removed direnv module. Anything else @BirdeeHub ? |
|
No I think that is it! I think you did a good job with these! Will wait for the mac tests to run. Github is being crazy today, it has been very hard for me to use it I have had to pull every single thing locally. |
|
Thanks! And thanks for the various inputs. Took more work than initially anticipated, but learned a lot about nix along the way :-) Mac tests are through. |
|
Final version, combining the good stuff from this original draft (V1) and #452 (V3).
A brief overview of what changed:
runTests/runTestordescribe/it. (Version A from [this comment])(WIP feat(tests): add test utils v3 #452 (comment))meta.platformsof a wrapper module, if the wrapper name is passed to the test in the first argument (settings.wrapper).@BirdeeHub the one thing I did not re-implement from your version is the
extendfeature. If you want this I will have to study it in more detail. I think it should be possible to add if it is really needed. Just let me know.