Skip to content
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/trivial: add assertMsg and assertMsgOneOf #44136

Merged
merged 4 commits into from Sep 6, 2018

Conversation

Profpatsch
Copy link
Member

@Profpatsch Profpatsch commented Jul 26, 2018

This is a cute little trick to make nix print an error message if an assertion fails.
It was lingering in my local repo for quite some time.

lib/trivial.nix Outdated
assertMsg = pred: msg:
if pred
then true
else builtins.trace msg false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not make the assert part of the function?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, it has to do with the fact that assert is not really an expression (it doesn’t return a value) but a statement (that influences the state of evaluation) of sorts.

We could change it to include an assert, but that means it can’t appear in statement position any longer. Maybe that’s not bad, though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let
  assertMsgIf = pred: msg: v:
    if pred v then v else builtins.trace msg; assert false; /*never reached*/ {};
  assertMsg = pred: assertMsgIf (const pred)
in
  (assertMsg (bar != borz)) /*where to put the derivation?*/

It kind of inverts the flow; one could use reverse application, but it’s not clear to me what values to pipe through.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right, makes sense

Copy link
Member

@infinisil infinisil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me then

Copy link
Member

@zimbatm zimbatm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there places where it could be used in nixpkgs?

@Profpatsch
Copy link
Member Author

Are there places where it could be used in nixpkgs?

Every time you want to throw a more helpful message for a failing assert. I did have some examples in code that was not pushed ultimately.

@zimbatm
Copy link
Member

zimbatm commented Aug 3, 2018

It would be good to start using it in nixpkgs, otherwise it will be removed by dead code elimination analysis.

@Profpatsch Profpatsch changed the title lib/trivial: add assertMsg lib/trivial: add assertMsg and assertMsgOneOf Aug 5, 2018
@Profpatsch
Copy link
Member Author

I’ve added a few examples for the asserts in lib, also another helper for enumerations called assertOneOf.

Copy link
Member

@zimbatm zimbatm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! It's the start of a good assertion-testing library.

lib/trivial.nix Outdated
(lib.elem val xs)
"${name} must be one of ${
lib.generators.toPretty {} xs}, but is: ${
lib.generators.toPretty {} val}";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about creating a more general assertType, which takes a type and calls .check on the value. Then this function is a specialization of it with type = types.enum xs.

@edolstra
Copy link
Member

edolstra commented Aug 6, 2018

IMHO, these functions should not be in trivial.nix since they are not, in fact, trivial. (For example, they call lib.generators.toPretty which is very non-trivial.)

@zimbatm
Copy link
Member

zimbatm commented Aug 6, 2018

good point. if it's the start of a new assertion system it might make sense to group them in a new lib/assets.nix file

@Profpatsch
Copy link
Member Author

@edolstra @zimbatm done.

@oxij
Copy link
Member

oxij commented Aug 9, 2018 via email

@Profpatsch
Copy link
Member Author

rename assertMsg to withMessage

I thought about that and am open to changing the name(s).

I think assertOneOf should not exists

Yeah, it’s pretty specific and should be generalized sometime, but I think it’s a good and pragmatic start. We can deprecate it or supersede it once we have figured out something more general.

assert = p: if p then null else throw "assert";
assertWithMsg = p: m: if p then null else throw m;

Can you create a minimal working example? I don’t see how that could work in practice.

@oxij
Copy link
Member

oxij commented Aug 10, 2018 via email

Since the `assertOneOf` uses `lib.generators`, they are not really trivial
anymore and should go into their own library file.
@Profpatsch
Copy link
Member Author

Amended the last commit, apparently I missed a few unqualified uses after moving it out of lib/trivial.nix.

@Profpatsch
Copy link
Member Author

Any more comments? Otherwise I’ll merge in a bit.

@oxij
Copy link
Member

oxij commented Sep 4, 2018 via email

@Profpatsch
Copy link
Member Author

@oxij You are free to generalize it, but I wouldn’t want to block this issue until somebody does.

@Profpatsch Profpatsch merged commit efdf618 into NixOS:master Sep 6, 2018
@kalbasit
Copy link
Member

kalbasit commented Oct 9, 2018

@Profpatsch @infinisil can you cherry-pick this to release-18.09?

@Profpatsch
Copy link
Member Author

According to the IRC bot, it was backported (to somewhere at least, can’t tell).

@kalbasit
Copy link
Member

kalbasit commented Oct 9, 2018

Hmm, I don't see the lib/asserts.nix file over on release-18.09 branch.

assertMsg = pred: msg:
if pred
then true
else builtins.trace msg false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about else throw "Assertion failed: ${msg}" instead? Using trace will log with lvlError and thus might be filtered out (eg. on Hydra, where only C++ exceptions are captured), throw on the other hand is fatal (at least until used with tryEval, but the same can be used for assertions anyway, so no need to abort).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like a good improvement to me.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, how would that look in practice? Would it still be used like

assert (assertMsg ("foo" == "bar") "foo is not bar, silly"); ""

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, the difference is just that assert here essentially becomes a no-op if pred doesn't match, because throw already takes care of raising an error before the actual assertion fails.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then the API stays the same, I see. I think we should do that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants