# HG changeset patch
# User Daniel J Sebald <daniel.sebald@ieee.org>
# Date 1373446307 18000
# Node ID 711716bb62f8bffec9c45828cc891bd415934ecf
# Parent  11dee6ef204c2c72467ddc7280b270617ac09e4c
Allow variable tolerance and improve error messages for assert.m script

* assert.m
(assert): Document non-scalar tolerance option.  Remove FIXME about format of
output.  Remove 'coda' and 'iserror' spanning whole routine.  Use pprint() to
immediately exit upon discrepancy.  Use if/endif rather than if/elseif/endif
construct.  Construct vector tolerance from scalar tolerance.  Add test
illustrating variable tolerance.
(pprint): Sub function to format and print input command, expected and observed
values and the error message.

diff --git a/scripts/testfun/assert.m b/scripts/testfun/assert.m
--- a/scripts/testfun/assert.m
+++ b/scripts/testfun/assert.m
@@ -47,16 +47,13 @@
 ## If @var{tol} is negative then it is a relative tolerance which will produce
 ## an error if @code{abs (@var{observed} - @var{expected}) >
 ## abs (@var{tol} * @var{expected})}.  If @var{expected} is zero @var{tol} will
-## always be interpreted as an absolute tolerance.
+## always be interpreted as an absolute tolerance.  If @var{tol} is not scalar
+## its dimensions must agree with those of @var{observed} and @var{expected}
+## and tests are performed on an element-wise basis.
 ## @end table
 ## @seealso{test, fail, error}
 ## @end deftypefn
 
-## FIXME: Output throttling: don't print out the entire 100x100 matrix,
-## but instead give a summary; don't print out the whole list, just
-## say what the first different element is, etc.  To do this, make
-## the message generation type specific.
-
 function assert (cond, varargin)
 
   in = deblank (argn(1,:));
@@ -90,30 +87,27 @@
       argn = " ";
     endif
 
-    coda = "";
-    iserror = 0;
-
-
     if (ischar (expected))
-      iserror = (! ischar (cond) || ! strcmp (cond, expected));
-
+      if (! ischar (cond) || ! strcmp (cond, expected))
+        pprint (in, expected, cond, "Strings don't match");
+      endif
     elseif (iscell (expected))
       if (! iscell (cond) || any (size (cond) != size (expected)))
-        iserror = 1;
+        pprint (in, expected, cond, "Cell sizes don't match");
       else
         try
           for i = 1:length (expected(:))
             assert (cond{i}, expected{i}, tol);
           endfor
         catch
-          iserror = 1;
+          pprint (in, expected, cond, "Cell configuration error");
         end_try_catch
       endif
 
     elseif (isstruct (expected))
       if (! isstruct (cond) || any (size (cond) != size (expected))
           || rows (fieldnames (cond)) != rows (fieldnames (expected)))
-        iserror = 1;
+          pprint (in, expected, cond, "Structure sizes don't match");
       else
         try
           #empty = numel (cond) == 0;
@@ -133,15 +127,14 @@
             assert (v, {expected.(k)}, tol);
           endfor
         catch
-          iserror = 1;
+          pprint (in, expected, cond, "Structure configuration error");
         end_try_catch
       endif
 
     elseif (ndims (cond) != ndims (expected)
             || any (size (cond) != size (expected)))
-      iserror = 1;
       coda = "Dimensions don't match";
-
+      pprint (in, expected, cond, coda);
     else
       if (nargin < 3)
         ## Without explicit tolerance, be more strict.
@@ -151,93 +144,93 @@
         elseif (isnumeric (cond))
           if (issparse (cond) != issparse (expected))
             if (issparse (cond))
-              iserror = 1;
               coda = "sparse != non-sparse";
             else
-              iserror = 1;
               coda = "non-sparse != sparse";
             endif
+            pprint (in, expected, cond, coda);
           elseif (iscomplex (cond) != iscomplex (expected))
             if (iscomplex (cond))
-              iserror = 1;
               coda = "complex != real";
             else
-              iserror = 1;
               coda = "real != complex";
             endif
+            pprint (in, expected, cond, coda);
           endif
         endif
       endif
 
-      if (! iserror)
-        ## Numeric.
-        A = cond(:);
-        B = expected(:);
-        ## Check exceptional values.
-        if (any (isna (A) != isna (B)))
+      ## Numeric.
+      A = cond(:);
+      B = expected(:);
+
+      ## Check exceptional values.
+
+      if (any (isna (A) != isna (B)))
+        k = (isna (A) != isna (B));
+        coda = "NAs don't match";
+        pprint (in, A (k), B (k), coda);
+      endif
+
+      if (any (isnan (A) != isnan (B)))
+        k = (isnan (A) != isnan (B));
+        coda = "NaNs don't match";
+        pprint (in, A (k), B (k), coda);
+      endif
+
+      ## Try to avoid problems comparing strange values like Inf+NaNi.
+      if (any (isinf (A) != isinf (B))
+              || any (A(isinf (A) & ! isnan (A)) != B(isinf (B) & ! isnan (B))))
+        k = ((isinf (A) != isinf (B)) | (A(isinf (A) & ! isnan (A)) != B(isinf (B) & ! isnan (B))));
+        coda = "Infs don't match";
+        pprint (in, A (k), B (k), coda);
+      endif
+
+      ## Check normal values.
+      A = A(isfinite (A));
+      B = B(isfinite (B));
+      if (isscalar (tol))
+        vtol = ones (size (A)) * tol;
+      else
+        vtol = tol (:);
+      endif
+
+      k = (vtol == 0);
+      if (any (A (k) != B (k)))
+        l = (A (k) != B (k));
+        errtype = "values do not match";
+        pprint (in, (A (k)) (l), (B (k)) (l), errtype);
+      endif
+
+      k = (vtol > 0);
+      if (any (abs (A (k) - B (k)) > vtol (k)))
+        l = (abs (A (k) - B (k)) > vtol (k));
+        errtype = "maximum absolute error %g exceeds tolerance %g\n";
+        coda = sprintf (errtype, [(abs (A (k) - B (k)))(l), (vtol (k))(l)]');
+        pprint (in, (A (k)) (l), (B (k)) (l), coda);
+      endif
+
+      k = (vtol < 0);
+      if (any (k))
+        AA = A (k);
+        BB = B (k);
+        abserr = max (abs (AA(BB == 0)));
+        AA = AA(BB != 0);
+        BB = BB(BB != 0);
+        relerr = max (abs (AA - BB) ./ abs (BB));
+        err = max ([abserr; relerr]);
+            errtype = "maximum relative error %g exceeds tolerance %g";
+        if (err > abs (tol))
           iserror = 1;
-          coda = "NAs don't match";
-        elseif (any (isnan (A) != isnan (B)))
-          iserror = 1;
-          coda = "NaNs don't match";
-          ## Try to avoid problems comparing strange values like Inf+NaNi.
-        elseif (any (isinf (A) != isinf (B))
-                || any (A(isinf (A) & ! isnan (A)) != B(isinf (B) & ! isnan (B))))
-          iserror = 1;
-          coda = "Infs don't match";
-        else
-          ## Check normal values.
-          A = A(isfinite (A));
-          B = B(isfinite (B));
-          if (tol == 0)
-            err = any (A != B);
-            errtype = "values do not match";
-          elseif (tol >= 0)
-            err = max (abs (A - B));
-            errtype = "maximum absolute error %g exceeds tolerance %g";
-          else
-            abserr = max (abs (A(B == 0)));
-            A = A(B != 0);
-            B = B(B != 0);
-            relerr = max (abs (A - B) ./ abs (B));
-            err = max ([abserr; relerr]);
-            errtype = "maximum relative error %g exceeds tolerance %g";
-          endif
-          if (err > abs (tol))
-            iserror = 1;
-            coda = sprintf (errtype, err, abs (tol));
-          endif
+          pprint (AA, BB);
+          coda = sprintf (errtype, err, abs (tol));
         endif
       endif
-
     endif
-
-    if (! iserror)
-      return;
-    endif
-
-    ## Pretty print the "expected but got" info, trimming leading and
-    ## trailing "\n".
-    str = disp (expected);
-    idx = find (str != "\n");
-    if (! isempty (idx))
-      str = str(idx(1):idx(end));
-    endif
-    str2 = disp (cond);
-    idx = find (str2 != "\n");
-    if (! isempty (idx))
-      str2 = str2 (idx(1):idx(end));
-    endif
-    msg = cstrcat ("assert ", in, " expected\n", str, "\nbut got\n", str2);
-    if (! isempty (coda))
-      msg = cstrcat (msg, "\n", coda);
-    endif
-    error ("%s", msg);
   endif
 
 endfunction
 
-
 ## empty input
 %!assert ([])
 %!assert (zeros (3,0), zeros (3,0))
@@ -331,7 +324,36 @@
 %! y = x; y(2,2) = "cat";
 %! fail ("assert (x, y)");
 
-%% Test input validation
+## variable tolerance
+%!test
+%! x = [-40:0];
+%! y1 = (10.^x).*(10.^x);
+%! y2 = 10.^(2*x);
+%! assert (y1, y2, eps (y1));
+
+## test input validation
 %!error assert
 %!error assert (1,2,3,4)
 
+
+## Pretty print the "expected but got" info, trimming leading and
+## trailing "\n".
+function pprint (in, expected, cond, coda)
+
+    str = disp (expected);
+    idx = find (str != "\n");
+    if (! isempty (idx))
+      str = str(idx(1):idx(end));
+    endif
+    str2 = disp (cond);
+    idx = find (str2 != "\n");
+    if (! isempty (idx))
+      str2 = str2 (idx(1):idx(end));
+    endif
+    msg = cstrcat ("assert ", in, " expected\n", str, "\nbut got\n", str2);
+    if (! isempty (coda))
+      msg = cstrcat (msg, "\n", coda);
+    endif
+    error ("%s", msg);
+
+endfunction
exporting patch:
<fdopen>
