# HG changeset patch
# User Robert Bradshaw <robertwb@math.washington.edu>
# Date 1300429764 25200
# Node ID c1e730e089904d01bd592cc6029e6eb0ca78a5a2
# Parent 26abf552ceaa2a69e7cf50ad4826a64406284cf2
#10952 - better handling of numerial noise in doctests
diff -r 26abf552ceaa -r c1e730e08990 sage-doctest
|
a
|
b
|
|
| 216 | 216 | sys.exit(runner.failures) |
| 217 | 217 | """ % dict |
| 218 | 218 | |
| 219 | | NONE=0; LONG_TIME=1; RANDOM=2; OPTIONAL=3; NOT_IMPLEMENTED=4; NOT_TESTED=5 |
| | 219 | NONE=0; LONG_TIME=1; RANDOM=2; OPTIONAL=3; NOT_IMPLEMENTED=4; NOT_TESTED=5; TOLERANCE=6 |
| | 220 | tolerance_pattern = re.compile(r'\b((?:abs(?:olute)?)|(?:rel(?:ative)?))? *?tol(?:erance)?\b( +[0-9.e+-]+)?') |
| 220 | 221 | |
| 221 | 222 | def comment_modifier(s): |
| 222 | 223 | sind = s.find('#') |
| … |
… |
|
| 235 | 236 | v.append(NOT_TESTED) |
| 236 | 237 | if 'random' in L: |
| 237 | 238 | v.append(RANDOM) |
| | 239 | m = tolerance_pattern.search(L) |
| | 240 | if m: |
| | 241 | v.append(TOLERANCE) |
| | 242 | rel_or_abs, epsilon = m.groups() |
| | 243 | if rel_or_abs is not None: |
| | 244 | rel_or_abs = rel_or_abs[:3] |
| | 245 | if epsilon is None: |
| | 246 | epsilon = 1e-15 |
| | 247 | else: |
| | 248 | epsilon = float(epsilon.strip()) |
| | 249 | v.append((rel_or_abs, epsilon)) |
| 238 | 250 | return v, L |
| 239 | 251 | |
| | 252 | def close_tolerance(comment_tags): |
| | 253 | rel_or_abs, epsilon = comment_tags[comment_tags.index(TOLERANCE) + 1] |
| | 254 | return "... ''', res, %r%s)" % (epsilon, '' if rel_or_abs is None else ", '%s'" % rel_or_abs) |
| | 255 | |
| 240 | 256 | def preparse_line_with_prompt(L): |
| 241 | 257 | i = L.find(':') |
| 242 | 258 | if i == -1: |
| … |
… |
|
| 255 | 271 | # t: Deal with code whose output should be ignored. |
| 256 | 272 | t = [] |
| 257 | 273 | |
| 258 | | # following two: used only for parsing only_optional; list of comments |
| 259 | | comment_modifiers = [] |
| | 274 | # used for adapting the output based on comments |
| | 275 | has_tolerance = False |
| | 276 | comment_modifiers = [] |
| 260 | 277 | last_prompt_comment = '' |
| 261 | 278 | |
| 262 | 279 | for L in s.splitlines(): |
| | 280 | if not L.strip(): |
| | 281 | if has_tolerance: |
| | 282 | t.append(close_tolerance(c)) |
| | 283 | has_tolerance = False |
| | 284 | |
| 263 | 285 | begin = L.lstrip()[:5] |
| 264 | 286 | comment = '' |
| 265 | 287 | if begin == 'sage:': |
| | 288 | if has_tolerance: |
| | 289 | t.append(close_tolerance(c)) |
| 266 | 290 | c, comment = comment_modifier(L) |
| 267 | 291 | last_prompt_comment = comment |
| 268 | 292 | line = '' |
| … |
… |
|
| 276 | 300 | L = '\n' # not tested |
| 277 | 301 | if OPTIONAL in c and not (only_optional or optional): |
| 278 | 302 | L = '\n' |
| | 303 | if TOLERANCE in c: |
| | 304 | t.append(">>> res = Exception") |
| | 305 | L = "sage: res = %s" % L.lstrip()[5:] |
| | 306 | has_tolerance = True |
| | 307 | else: |
| | 308 | has_tolerance = False |
| 279 | 309 | line = preparse_line_with_prompt(L) |
| 280 | 310 | if RANDOM in c: |
| 281 | 311 | # count spaces at the beginning of line to fix alignment later |
| … |
… |
|
| 285 | 315 | # append a line saying 'ignore' followed by ellipsis (...) |
| 286 | 316 | # and an empty line, to ignore the output given in the test |
| 287 | 317 | line += '\n' + ' '*i + 'ignore ...\n' |
| | 318 | if has_tolerance: |
| | 319 | line += "\n>>> check_with_tolerance('''" |
| 288 | 320 | t.append(line) |
| 289 | 321 | |
| 290 | 322 | elif begin.startswith('...'): |
| … |
… |
|
| 294 | 326 | |
| 295 | 327 | else: |
| 296 | 328 | comment = last_prompt_comment |
| | 329 | if has_tolerance: |
| | 330 | L = "... " + L |
| 297 | 331 | t.append(L) |
| 298 | 332 | |
| 299 | 333 | comment_modifiers.append(comment) |
| 300 | 334 | |
| | 335 | if has_tolerance: |
| | 336 | t.append(close_tolerance(c)) |
| | 337 | |
| 301 | 338 | # The very last line -- which is typically """ -- must never be marked as optional, |
| 302 | 339 | # or it might not get included, which would be a syntax error. |
| 303 | 340 | comment_modifiers[-1] = '' |
| … |
… |
|
| 419 | 456 | s = "# -*- coding: utf-8 -*-\n" |
| 420 | 457 | s += "from sage.all_cmdline import *; \n" |
| 421 | 458 | s += "import sage.plot.plot; sage.plot.plot.DOCTEST_MODE=True\n" # turn off image popup |
| 422 | | s += """ |
| | 459 | s += r""" |
| 423 | 460 | def warning_function(f): |
| 424 | 461 | import warnings |
| 425 | 462 | |
| … |
… |
|
| 433 | 470 | def change_warning_output(file): |
| 434 | 471 | import warnings |
| 435 | 472 | warnings.showwarning = warning_function(file) |
| | 473 | |
| | 474 | import re |
| | 475 | float_pattern = re.compile('[+-]?((\d*\.?\d+)|(\d+\.?))([eE][+-]?\d+)?') |
| | 476 | |
| | 477 | def check_with_tolerance(expected, actual, epsilon, rel_or_abs=None): |
| | 478 | if actual is Exception: |
| | 479 | return # error computing actual |
| | 480 | else: |
| | 481 | actual = str(actual) |
| | 482 | expected = re.sub('\n +', '\n', expected) |
| | 483 | actual = re.sub('\n +', '\n', actual) |
| | 484 | assert float_pattern.sub('#', expected.strip()) == float_pattern.sub('#', actual.strip()), \ |
| | 485 | "Expected '" + expected + "' got '" + actual + "'" |
| | 486 | for expected_value, actual_value in zip(float_pattern.finditer(expected), float_pattern.finditer(actual)): |
| | 487 | expected_value = float(expected_value.group()) |
| | 488 | actual_value = float(actual_value.group()) |
| | 489 | if rel_or_abs == 'abs' or expected_value == 0: |
| | 490 | assert abs(expected_value - actual_value) < epsilon, "Out of tolerance %s vs %s" % (expected_value, actual_value) |
| | 491 | else: |
| | 492 | assert abs((expected_value - actual_value) / expected_value) < epsilon, "Out of tolerance %s vs %s" % (expected_value, actual_value) |
| 436 | 493 | """ |
| 437 | 494 | |
| 438 | 495 | if not library_code: |