1## Copyright (C) 2000, 2006, 2007, 2008, 2009 Paul Kienzle
3## This file is part of Octave.
5## Octave is free software; you can redistribute it and/or modify it
6## under the terms of the GNU General Public License as published by
7## the Free Software Foundation; either version 3 of the License, or (at
8## your option) any later version.
10## Octave is distributed in the hope that it will be useful, but
11## WITHOUT ANY WARRANTY; without even the implied warranty of
12## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13## General Public License for more details.
15## You should have received a copy of the GNU General Public License
16## along with Octave; see the file COPYING. If not, see
17## <http://www.gnu.org/licenses/>.
20## @deftypefn {Function File} {} assert (@var{cond})
21## @deftypefnx {Function File} {} assert (@var{cond}, @var{errmsg}, @dots{})
22## @deftypefnx {Function File} {} assert (@var{cond}, @var{msg_id}, @var{errmsg}, @dots{})
23## @deftypefnx {Function File} {} assert (@var{observed},@var{expected})
24## @deftypefnx {Function File} {} assert (@var{observed},@var{expected},@var{tol})
26## Produces an error if the condition is not met. @code{assert} can be
27## called in three different ways.
30## @item assert (@var{cond})
31## @itemx assert (@var{cond}, @var{errmsg}, @dots{})
32## @itemx assert (@var{cond}, @var{msg_id}, @var{errmsg}, @dots{})
33## Called with a single argument @var{cond}, @code{assert} produces an
34## error if @var{cond} is zero. If called with a single argument a
35## generic error message. With more than one argument, the additional
36## arguments are passed to the @code{error} function.
38## @item assert (@var{observed}, @var{expected})
39## Produce an error if observed is not the same as expected. Note that
40## observed and expected can be strings, scalars, vectors, matrices,
41## lists or structures.
43## @item assert(@var{observed}, @var{expected}, @var{tol})
44## Accept a tolerance when comparing numbers.
45## If @var{tol} is positive use it as an absolute tolerance, will produce an error if
46## @code{abs(@var{observed} - @var{expected}) > abs(@var{tol})}.
47## If @var{tol} is negative use it as a relative tolerance, will produce an error if
48## @code{abs(@var{observed} - @var{expected}) > abs(@var{tol} * @var{expected})}.
49## If @var{expected} is zero @var{tol} will always be used as an absolute tolerance.
54## FIXME: Output throttling: don't print out the entire 100x100 matrix,
55## but instead give a summary; don't print out the whole list, just
56## say what the first different element is, etc. To do this, make
57## the message generation type specific.
59function assert (cond, varargin)
61 in = deblank (argn(1,:));
63 in = cstrcat (in, ",", deblank (argn(i,:)));
65 in = cstrcat ("(", in, ")");
67 if (nargin == 1 || (nargin > 1 && islogical (cond) && ischar (varargin{1})))
68 if ((! isnumeric (cond) && ! islogical (cond)) || ! all (cond(:)))
70 ## Say which elements failed?
71 error ("assert %s failed", in);
77 if (nargin < 2 || nargin > 3)
81 expected = varargin {1};
88 if (exist ("argn") == 0)
96 if (ischar (expected))
97 iserror = (! ischar (cond) || ! strcmp (cond, expected));
99 elseif (iscell (expected))
100 if (! iscell (cond) || any (size (cond) != size (expected)))
104 for i = 1:length (expected(:))
105 assert (cond{i}, expected{i}, tol);
112 elseif (isstruct (expected))
113 if (! isstruct (cond) || any (size (cond) != size (expected))
114 || rows (fieldnames (cond)) != rows (fieldnames (expected)))
118 empty = numel (cond) == 0;
119 normal = numel (cond) == 1;
121 if (! isfield (expected, k))
132 assert (v, {expected.(k)}, tol);
139 elseif (ndims (cond) != ndims (expected)
140 || any (size (cond) != size (expected)))
142 coda = "Dimensions don't match";
146 ## Without explicit tolerance, be more strict.
147 if (! strcmp(class (cond), class (expected)))
149 coda = cstrcat ("Class ", class (cond), " != ", class (expected));
150 elseif (isnumeric (cond))
151 if (issparse (cond) != issparse (expected))
154 coda = "sparse != non-sparse";
157 coda = "non-sparse != sparse";
159 elseif (iscomplex (cond) != iscomplex (expected))
160 if (iscomplex (cond))
162 coda = "complex != real";
165 coda = "real != complex";
175 ## Check exceptional values.
176 if (any (isna (A) != isna (B)))
178 coda = "NAs don't match";
179 elseif (any (isnan (A) != isnan (B)))
181 coda = "NaNs don't match";
182 ## Try to avoid problems comparing strange values like Inf+NaNi.
183 elseif (any (isinf (A) != isinf (B))
184 || any (A(isinf (A) & ! isnan (A)) != B(isinf (B) & ! isnan (B))))
186 coda = "Infs don't match";
188 ## Check normal values.
193 errtype = "values do not match";
195 err = max (abs (A - B));
196 errtype = "maximum absolute error %g exceeds tolerance %g";
198 abserr = max (abs (A(B == 0)));
201 relerr = max (abs (A - B) ./ abs (B));
202 err = max ([abserr; relerr]);
203 errtype = "maximum relative error %g exceeds tolerance %g";
207 coda = sprintf (errtype, err, abs (tol));
218 ## Pretty print the "expected but got" info, trimming leading and
220 str = disp (expected);
221 idx = find (str != "\n");
223 str = str(idx(1):idx(end));
226 idx = find (str2 != "\n");
228 str2 = str2 (idx(1):idx(end));
230 msg = cstrcat ("assert ", in, " expected\n", str, "\nbut got\n", str2);
231 if (! isempty (coda))
232 msg = cstrcat (msg, "\n", coda);
236 ## error ("assertion failed");
242%!assert(zeros(3,0),zeros(3,0))
243%!error assert(zeros(3,0),zeros(0,2))
244%!error assert(zeros(3,0),[])
245%!fail("assert(zeros(2,0,2),zeros(2,0))", "Dimensions don't match")
254%!error assert([1,0,1])
255%!error assert([1;1;0])
256%!error assert([1,0;1,1])
259%!assert([1,2,3],[1,2,3]);
260%!assert([1;2;3],[1;2;3]);
261%!error assert([2;2;3],[1;2;3]);
262%!error assert([1,2,3],[1;2;3]);
263%!error assert([1,2],[1,2,3]);
264%!error assert([1;2;3],[1;2]);
265%!assert([1,2;3,4],[1,2;3,4]);
266%!error assert([1,4;3,4],[1,2;3,4])
267%!error assert([1,3;2,4;3,5],[1,2;3,4])
270%!assert([NaN, NA, Inf, -Inf, 1+eps, eps],[NaN, NA, Inf, -Inf, 1, 0],eps)
271%!error assert(NaN, 1)
273%!error assert(-Inf, Inf)
276%!error assert(3, [3,3; 3,3])
277%!error assert([3,3; 3,3], 3)
279%!assert(3+eps, 3, eps);
280%!assert(3, 3+eps, eps);
281%!error assert(3+2*eps, 3, eps);
282%!error assert(3, 3+2*eps, eps);
284## must give a little space for floating point errors on relative
285%!assert(100+100*eps, 100, -2*eps);
286%!assert(100, 100+100*eps, -2*eps);
287%!error assert(100+300*eps, 100, -2*eps);
288%!error assert(100, 100+300*eps, -2*eps);
289%!error assert(3, [3,3]);
292## test relative vs. absolute tolerances
293%!test assert (0.1+eps, 0.1, 2*eps); # accept absolute
294%!error assert (0.1+eps, 0.1, -2*eps); # fail relative
295%!test assert (100+100*eps, 100, -2*eps); # accept relative
296%!error assert (100+100*eps, 100, 2*eps); # fail absolute
300%! x.a = 1; x.b=[2, 2];
301%! y.a = 1; y.b=[2, 2];
305%!error assert (3, x);
306%!error assert (x, 3);
308## check usage statements
310%!error assert(1,2,3,4,5)
314%!error assert("dog","cat")
315%!error assert("dog",3);
316%!error assert(3,"dog");