7017
|
1 ## Copyright (C) 2000, 2006, 2007 Paul Kienzle |
5589
|
2 ## |
7016
|
3 ## This file is part of Octave. |
5589
|
4 ## |
7016
|
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. |
|
9 ## |
|
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. |
5589
|
14 ## |
|
15 ## You should have received a copy of the GNU General Public License |
7016
|
16 ## along with Octave; see the file COPYING. If not, see |
|
17 ## <http://www.gnu.org/licenses/>. |
5589
|
18 |
|
19 ## -*- texinfo -*- |
|
20 ## @deftypefn {Function File} {} assert (@var{cond}) |
|
21 ## @deftypefnx {Function File} {} assert (@var{observed},@var{expected}) |
|
22 ## @deftypefnx {Function File} {} assert (@var{observed},@var{expected},@var{tol}) |
|
23 ## |
|
24 ## Produces an error if the condition is not met. @code{assert} can be |
|
25 ## called in three different ways. |
|
26 ## |
|
27 ## @table @code |
|
28 ## @item assert (@var{cond}) |
|
29 ## Called with a single argument @var{cond}, @code{assert} produces an |
|
30 ## error if @var{cond} is zero. |
|
31 ## |
|
32 ## @item assert (@var{observed}, @var{expected}) |
|
33 ## Produce an error if observed is not the same as expected. Note that |
|
34 ## observed and expected can be strings, scalars, vectors, matrices, |
|
35 ## lists or structures. |
|
36 ## |
|
37 ## @item assert(@var{observed}, @var{expected}, @var{tol}) |
7027
|
38 ## Accept a tolerance when comparing numbers. |
|
39 ## If @var{tol} is possitive use it as an absolute tolerance, will produce an error if |
|
40 ## @code{abs(@var{observed} - @var{expected}) > abs(@var{tol})}. |
|
41 ## If @var{tol} is negative use it as a relative tolerance, will produce an error if |
|
42 ## @code{abs(@var{observed} - @var{expected}) > abs(@var{tol} * @var{expected})}. |
|
43 ## If @var{expected} is zero @var{tol} will always be used as an absolute tolerance. |
5589
|
44 ## @end table |
5642
|
45 ## @seealso{test} |
5589
|
46 ## @end deftypefn |
|
47 |
|
48 ## TODO: Output throttling: don't print out the entire 100x100 matrix, |
|
49 ## TODO: but instead give a summary; don't print out the whole list, just |
|
50 ## TODO: say what the first different element is, etc. To do this, make |
|
51 ## TODO: the message generation type specific. |
6494
|
52 |
|
53 function assert (cond, expected, tol) |
5589
|
54 |
|
55 if (nargin < 1 || nargin > 3) |
6046
|
56 print_usage (); |
5589
|
57 endif |
|
58 |
|
59 if (nargin < 3) |
|
60 tol = 0; |
|
61 endif |
|
62 |
6494
|
63 if (exist ("argn") == 0) |
|
64 argn = " "; |
|
65 endif |
|
66 |
|
67 in = deblank (argn(1,:)); |
|
68 for i = 2:rows (argn) |
|
69 in = strcat (in, ",", deblank (argn(i,:))); |
5589
|
70 end |
6494
|
71 in = strcat ("(", in, ")"); |
5589
|
72 |
|
73 coda = ""; |
|
74 iserror = 0; |
|
75 if (nargin == 1) |
6494
|
76 if (! isnumeric (cond) || ! all (cond(:))) |
5589
|
77 error ("assert %s failed", in); # say which elements failed? |
|
78 endif |
|
79 |
6494
|
80 elseif (is_list (cond)) |
|
81 if (! is_list (expected) || length (cond) != length (expected)) |
5589
|
82 iserror = 1; |
|
83 else |
|
84 try |
6494
|
85 for i = 1:length (cond) |
|
86 assert (nth (cond, i), nth (expected, i)); |
5589
|
87 endfor |
|
88 catch |
|
89 iserror = 1; |
|
90 end |
|
91 endif |
|
92 |
|
93 elseif (ischar (expected)) |
6494
|
94 iserror = (! ischar (cond) || ! strcmp (cond, expected)); |
5589
|
95 |
6494
|
96 elseif (iscell (expected)) |
|
97 if (! iscell (cond) || any (size (cond) != size (expected))) |
5589
|
98 iserror = 1; |
|
99 else |
|
100 try |
6494
|
101 for i = 1:length (expected(:)) |
|
102 assert (cond{i}, expected{i}, tol); |
5589
|
103 endfor |
|
104 catch |
|
105 iserror = 1; |
|
106 end |
|
107 endif |
|
108 |
|
109 elseif (isstruct (expected)) |
6494
|
110 if (! isstruct (cond) || any (size (cond) != size (expected)) |
|
111 || rows(struct_elements (cond)) != rows (struct_elements (expected))) |
5589
|
112 iserror = 1; |
|
113 else |
|
114 try |
6494
|
115 empty = numel (cond) == 0; |
|
116 normal = numel (cond) == 1; |
|
117 for [v, k] = cond |
|
118 if (! struct_contains (expected, k)) |
|
119 error (); |
|
120 endif |
|
121 if (empty) |
|
122 v = cell (1, 0); |
|
123 endif |
|
124 if (normal) |
|
125 v = {v}; |
|
126 else |
|
127 v = v(:)'; |
|
128 endif |
|
129 assert (v, {expected.(k)}, tol); |
5589
|
130 endfor |
|
131 catch |
|
132 iserror = 1; |
|
133 end |
|
134 endif |
|
135 |
6392
|
136 elseif (ndims (cond) != ndims (expected) |
|
137 || any (size (cond) != size (expected))) |
5589
|
138 iserror = 1; |
|
139 coda = "Dimensions don't match"; |
|
140 |
6494
|
141 elseif (tol == 0 && ! strcmp (typeinfo (cond), typeinfo (expected))) |
5589
|
142 iserror = 1; |
6494
|
143 coda = strcat ("Type ", typeinfo (cond), " != ", typeinfo (expected)); |
5589
|
144 |
|
145 else # numeric |
6494
|
146 A = cond(:); |
|
147 B = expected(:); |
5589
|
148 ## Check exceptional values |
6494
|
149 if (any (isna (A) != isna (B))) |
|
150 iserror = 1; |
|
151 coda = "NAs don't match"; |
|
152 elseif (any (isnan (A) != isnan (B))) |
5589
|
153 iserror = 1; |
|
154 coda = "NaNs don't match"; |
6494
|
155 ### Try to avoid problems comparing strange values like Inf+NaNi. |
|
156 elseif (any (isinf (A) != isinf (B)) |
|
157 || any (A(isinf (A) & ! isnan (A)) != B(isinf (B) & ! isnan (B)))) |
5589
|
158 iserror = 1; |
|
159 coda = "Infs don't match"; |
|
160 else |
|
161 ## Check normal values |
6494
|
162 A = A(finite (A)); |
|
163 B = B(finite (B)); |
|
164 if (tol == 0) |
|
165 err = any (A != B); |
5589
|
166 errtype = "values do not match"; |
6494
|
167 elseif (tol >= 0) |
|
168 err = max (abs (A - B)); |
5589
|
169 errtype = "maximum absolute error %g exceeds tolerance %g"; |
|
170 else |
6494
|
171 abserr = max (abs (A(B == 0))); |
|
172 A = A(B != 0); |
|
173 B = B(B != 0); |
|
174 relerr = max (abs (A - B) ./ abs (B)); |
|
175 err = max ([abserr; relerr]); |
5589
|
176 errtype = "maximum relative error %g exceeds tolerance %g"; |
|
177 endif |
6494
|
178 if (err > abs (tol)) |
5589
|
179 iserror = 1; |
6494
|
180 coda = sprintf (errtype, err, abs (tol)); |
5589
|
181 endif |
|
182 endif |
|
183 endif |
|
184 |
6494
|
185 if (! iserror) |
5589
|
186 return; |
|
187 endif |
|
188 |
|
189 ## pretty print the "expected but got" info, |
|
190 ## trimming leading and trailing "\n" |
|
191 str = disp (expected); |
6494
|
192 idx = find (str != "\n"); |
|
193 if (! isempty (idx)) |
|
194 str = str(idx(1):idx(end)); |
5589
|
195 endif |
|
196 str2 = disp (cond); |
6494
|
197 idx = find (str2 != "\n"); |
|
198 if (! isempty (idx)) |
|
199 str2 = str2 (idx(1):idx(end)); |
5589
|
200 endif |
6494
|
201 msg = strcat ("assert ", in, " expected\n", str, "\nbut got\n", str2); |
|
202 if (! isempty (coda)) |
|
203 msg = strcat (msg, "\n", coda); |
5589
|
204 endif |
6494
|
205 error ("%s", msg); |
|
206 ## disp (msg); |
|
207 ## error ("assertion failed"); |
5589
|
208 endfunction |
|
209 |
|
210 ## empty |
|
211 %!assert([]) |
|
212 %!assert(zeros(3,0),zeros(3,0)) |
|
213 %!error assert(zeros(3,0),zeros(0,2)) |
|
214 %!error assert(zeros(3,0),[]) |
6455
|
215 %!fail("assert(zeros(2,0,2),zeros(2,0))", "Dimensions don't match") |
5589
|
216 |
|
217 ## conditions |
|
218 %!assert(isempty([])) |
|
219 %!assert(1) |
|
220 %!error assert(0) |
|
221 %!assert(ones(3,1)) |
|
222 %!assert(ones(1,3)) |
|
223 %!assert(ones(3,4)) |
|
224 %!error assert([1,0,1]) |
|
225 %!error assert([1;1;0]) |
|
226 %!error assert([1,0;1,1]) |
|
227 |
|
228 ## vectors |
|
229 %!assert([1,2,3],[1,2,3]); |
|
230 %!assert([1;2;3],[1;2;3]); |
|
231 %!error assert([2;2;3],[1;2;3]); |
|
232 %!error assert([1,2,3],[1;2;3]); |
|
233 %!error assert([1,2],[1,2,3]); |
|
234 %!error assert([1;2;3],[1;2]); |
|
235 %!assert([1,2;3,4],[1,2;3,4]); |
|
236 %!error assert([1,4;3,4],[1,2;3,4]) |
|
237 %!error assert([1,3;2,4;3,5],[1,2;3,4]) |
|
238 |
|
239 ## exceptional values |
|
240 %!assert([NaN, NA, Inf, -Inf, 1+eps, eps],[NaN, NA, Inf, -Inf, 1, 0],eps) |
|
241 %!error assert(NaN, 1) |
|
242 %!error assert(NA, 1) |
|
243 %!error assert(-Inf, Inf) |
|
244 |
|
245 ## scalars |
|
246 %!error assert(3, [3,3; 3,3]) |
|
247 %!error assert([3,3; 3,3], 3) |
|
248 %!assert(3, 3); |
|
249 %!assert(3+eps, 3, eps); |
|
250 %!assert(3, 3+eps, eps); |
|
251 %!error assert(3+2*eps, 3, eps); |
|
252 %!error assert(3, 3+2*eps, eps); |
|
253 |
7027
|
254 ## must give a little space for floating point errors on relative |
5589
|
255 %!assert(100+100*eps, 100, -2*eps); |
|
256 %!assert(100, 100+100*eps, -2*eps); |
|
257 %!error assert(100+300*eps, 100, -2*eps); |
|
258 %!error assert(100, 100+300*eps, -2*eps); |
|
259 %!error assert(3, [3,3]); |
|
260 %!error assert(3,4); |
|
261 |
7027
|
262 ## test relative vs. absolute tolerances |
|
263 %!test assert (0.1+eps, 0.1, 2*eps); # accept absolute |
|
264 %!error assert (0.1+eps, 0.1, -2*eps); # fail relative |
|
265 %!test assert (100+100*eps, 100, -2*eps); # accept relative |
|
266 %!error assert (100+100*eps, 100, 2*eps); # fail absolute |
|
267 |
5589
|
268 ## structures |
|
269 %!shared x,y |
|
270 %! x.a = 1; x.b=[2, 2]; |
|
271 %! y.a = 1; y.b=[2, 2]; |
|
272 %!assert (x,y) |
|
273 %!test y.b=3; |
|
274 %!error assert (x,y) |
|
275 %!error assert (3, x); |
|
276 %!error assert (x, 3); |
|
277 |
|
278 ## check usage statements |
|
279 %!error assert |
|
280 %!error assert(1,2,3,4,5) |
|
281 |
|
282 ## strings |
|
283 %!assert("dog","dog") |
|
284 %!error assert("dog","cat") |
|
285 %!error assert("dog",3); |
|
286 %!error assert(3,"dog"); |