3
$\begingroup$

I really like this form of assertion failure:

On[Assert];
Assert[1 + 1 == True];

Assert::asrttf: Assertion test 1+1==True evaluated to 2==True that is neither True nor False. Assert::asrtf: Assertion 1+1==True failed.

I would like something like Assert::asrttf to be generated every time, even say in this case:

Assert[1 + 1 == "2"];

instead of just

Assert::asrtf: Assertion 1+1==2 failed.

I would like to see the intermediate stage

Assert::asrtfr: Assertion 1+1=="2" failed. Assertion test 1+1=="2" evaluated to 2=="2" which is False.

As I have indicated above, I'd also like to see the expression in InputForm, not StandardForm.

Do I have to do this extension manually? What have others come up with?

$\endgroup$
3
$\begingroup$

Defining $AssertFunction might be a place to get started, but it is not ideal since when it gets called, the evaluation will have already happened. You will change the program behaviour if you evaluate things again.

Here's an alternative which only generates one message per assertion and has standard evaluation behaviour (I believe)

(* -- Extend Assert --*)
Unprotect@Assert;

Assert::asrttf = 
  "Assertion test `1` evaluated to `2` that is neither True nor \
False. `3`";
Assert::asrtfr = 
  "Assertion failed. Assertion test `1` evaluated to `2` which is \
False. `3`";
Assert::asrtf = "Assertion `1` failed. `2`";

ClearAll[AssertSub, HeldInputForm, formatExtra];


AssertSub~SetAttributes~HoldAllComplete;(*HoldAll to avoid undesired \
re-evaluations*)

HeldInputForm~SetAttributes~HoldAllComplete;
HeldInputForm[x_] := RawBoxes@MakeBoxes@HoldForm@InputForm@x;

formatExtra[extra___] := Row[(*HeldInputForm/@*){extra}];
call$AssertFunction~SetAttributes~HoldAllComplete;
call$AssertFunction[test_, extra___] := $AssertFunction[
  HoldComplete[Assert[test, extra]]]

AssertSub[True, ___] := Null;

AssertSub[False, test_, HoldComplete@op_, 
  HoldComplete@dummy_[ops___], {extra___}] := (Message[Assert::asrtfr,
    HeldInputForm@test, HeldInputForm@op[ops], formatExtra@extra]; 
  call$AssertFunction[test, extra];)
AssertSub[False, 
  test_, {extra___}] := (Message[Assert::asrtf, HeldInputForm@test, 
   formatExtra@extra]; call$AssertFunction[test, extra];)
AssertSub[result_, 
  test_, _HoldComplete | PatternSequence[], _HoldComplete | 
   PatternSequence[], {extra___}] := (Message[Assert::asrttf, 
   HeldInputForm@test, HeldInputForm@result, formatExtra@extra]; 
  call$AssertFunction[test, extra];)

Assert[test : logicalOperation_[operands__], extra___] := 
  Module[{dummy}, With[
    {(*evaluate parts in standard order: first the head*)
     op = logicalOperation, 
     ops = dummy[
       operands](*use inert dummy Head to not eliminate Nothing*)
     },
    {result = logicalOperation @@ ops(*finish evaluation*)},
    (*HoldComplete op and ops: 
    they might evaluate again now that result is computed, 
    but that would not usually happen*)
    AssertSub[result, test, HoldComplete@op, HoldComplete@ops, {extra}]
    ]];

Assert[test_, extra___] := 
 With[{result = test}, AssertSub[result, test, {extra}]]

Protect@Assert;
(* -- End of Assert extension --*)

It just appends any extra arguments in a Row, unlike the original assert which uses the "Assertion `1` in `2` failed." message, as in

Assert[a, b]

Assert::asrttf: Assertion test a evaluated to a that is neither True nor False. Assert::asrtfe: Assertion a in Assert[a,b] failed.

Demos:

Assert[True, "Everything is ok."]
Assert[1 == 1, "1 equals itself."]

Assert[1 == 0, "There is something fishy."]
Assert[1 + 1 == 
  "2", "This looked like it should be true with the old Assert."]
Assert[False, "This uses the base case above."]
f[] := False;
Assert[f[], "This uses the base case above."]

Assert[1 + 1 == True, "This generates Assert::asrttf."]

f[] := Print@"f is evaluated";
Assert[f[] =!= Null, "f is only evaluated once."]

nothingFalse[Nothing] = False;
Assert[nothingFalse[Nothing], "This shows why we need a dummy head."]

enter image description here

I then usually set $AssertFunction = Abort[]&; to support fail-early. Use

$AssertFunction = Print["$AssertFunction: ", InputForm@#] &;

to see how it's called.

Note that the behaviour of $AssertFunction changes a bit with this: All Assert messages are generated prior to calling it, not just Assert::asrttf. $AssertFunction == Automatic has no special meaning anymore. Also, Off@Assert is not implemented (how do we check whether a message is disabled currently?).

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.