TransWikia.com

Improving `$AssertFunction` to always tell what the assertion test resulted in

Mathematica Asked by masterxilo on February 20, 2021

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?

One Answer

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?).

Answered by masterxilo on February 20, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP