Mercurial > ~astiob > upreckon > hgweb
comparison 2.00/testcases.py @ 25:b500e117080e
Bug fixes and overhead reduction
Added the --problem/-p option. (WARNING: not the same as the -p option of test.py 1.x.) The problem names supplied are not validated.
Added zip_longest to compat.py.
Experimental: problem names are now _always_ printed for multi-problem sets.
Overhead: Escape presses are now checked only once every .15 seconds (at least kbhit() on Windows is very slow).
Overhead: sleep(0) is now called in the time-control-and-Escape-watching loop (at least on Windows, it immediately transfers control to some waiting thread).
Bug fix: compat.py now overwrites built-ins only while including testconfs (--help was broken in Python 2).
Bug fix: ReadDeleting in config.py now closes the file it opens (especially important on Windows, where open files cannot be deleted).
Bug fix: added callable to compat.py (it is absent from Python 3).
Bug fix: the default (built-in) output validator now properly handles unwanted trailing data.
Bug fix: testconfs in custom archives no more raise NameError.
Bug fix: if a validator program cannot be launched, CannotStartValidator is now raised instead of the fatal OSError.
author | Oleg Oshmyan <chortos@inbox.lv> |
---|---|
date | Thu, 23 Sep 2010 23:05:58 +0000 |
parents | c23d81f4a1a3 |
children | 5bbb68833868 |
comparison
equal
deleted
inserted
replaced
24:c23d81f4a1a3 | 25:b500e117080e |
---|---|
57 def cleanup(old=termios.tcgetattr(sys.stdin.fileno())): | 57 def cleanup(old=termios.tcgetattr(sys.stdin.fileno())): |
58 termios.tcsetattr(sys.stdin.fileno(), termios.TCSAFLUSH, old) | 58 termios.tcsetattr(sys.stdin.fileno(), termios.TCSAFLUSH, old) |
59 atexit.register(cleanup) | 59 atexit.register(cleanup) |
60 del cleanup | 60 del cleanup |
61 tty.setcbreak(sys.stdin.fileno()) | 61 tty.setcbreak(sys.stdin.fileno()) |
62 def canceled(): | 62 def canceled(select=select.select, stdin=sys.stdin, read=sys.stdin.read): |
63 while select.select((sys.stdin,), (), (), 0)[0]: | 63 while select((stdin,), (), (), 0)[0]: |
64 if sys.stdin.read(1) == '\33': | 64 if read(1) == '\33': |
65 return True | 65 return True |
66 return False | 66 return False |
67 def init_canceled(): | 67 def init_canceled(): |
68 while select.select((sys.stdin,), (), (), 0)[0]: | 68 while select.select((sys.stdin,), (), (), 0)[0]: |
69 sys.stdin.read(1) | 69 sys.stdin.read(1) |
70 def pause(): | 70 def pause(): |
71 sys.stdin.read(1) | 71 sys.stdin.read(1) |
72 else: | 72 else: |
73 def canceled(): | 73 def canceled(kbhit=msvcrt.kbhit, getch=msvcrt.getch): |
74 while msvcrt.kbhit(): | 74 while kbhit(): |
75 c = msvcrt.getch() | 75 c = getch() |
76 if c == '\33': | 76 if c == '\33': |
77 return True | 77 return True |
78 elif c == '\0': | 78 elif c == '\0': |
79 # Let's hope no-one is fiddling with this | 79 # Let's hope no-one is fiddling with this |
80 msvcrt.getch() | 80 getch() |
81 return False | 81 return False |
82 def init_canceled(): | 82 def init_canceled(): |
83 while msvcrt.kbhit(): | 83 while msvcrt.kbhit(): |
84 msvcrt.getch() | 84 msvcrt.getch() |
85 def pause(): | 85 def pause(): |
296 def validate(case, output): | 296 def validate(case, output): |
297 if not case.validator: | 297 if not case.validator: |
298 # Compare the output with the reference output | 298 # Compare the output with the reference output |
299 case.open_outfile() | 299 case.open_outfile() |
300 with case.outfile.open() as refoutput: | 300 with case.outfile.open() as refoutput: |
301 for line, refline in zip(output, refoutput): | 301 for line, refline in zip_longest(output, refoutput): |
302 if not isinstance(refline, basestring): | 302 if refline is not None and not isinstance(refline, basestring): |
303 line = bytes(line, sys.getdefaultencoding()) | 303 line = bytes(line, sys.getdefaultencoding()) |
304 if line != refline: | 304 if line != refline: |
305 raise WrongAnswer | 305 raise WrongAnswer |
306 try: | |
307 try: | |
308 next(output) | |
309 except NameError: | |
310 output.next() | |
311 except StopIteration: | |
312 pass | |
313 else: | |
314 raise WrongAnswer | |
315 try: | |
316 try: | |
317 next(refoutput) | |
318 except NameError: | |
319 refoutput.next() | |
320 except StopIteration: | |
321 pass | |
322 else: | |
323 raise WrongAnswer | |
324 return 1 | 306 return 1 |
325 elif callable(case.validator): | 307 elif callable(case.validator): |
326 return case.validator(output) | 308 return case.validator(output) |
327 else: | 309 else: |
328 # Call the validator program | 310 # Call the validator program |
329 output.close() | 311 output.close() |
330 if case.problem.config.ansname: | 312 if case.problem.config.ansname: |
331 case.open_outfile() | 313 case.open_outfile() |
332 case.outfile.copy(case.problem.config.ansname) | 314 case.outfile.copy(case.problem.config.ansname) |
333 case.process = Popen(case.validator, stdin=devnull, stdout=PIPE, stderr=STDOUT, universal_newlines=True, bufsize=-1) | 315 try: |
316 case.process = Popen(case.validator, stdin=devnull, stdout=PIPE, stderr=STDOUT, universal_newlines=True, bufsize=-1) | |
317 except OSError: | |
318 raise CannotStartValidator(sys.exc_info()[1]) | |
334 comment = case.process.communicate()[0].strip() | 319 comment = case.process.communicate()[0].strip() |
335 lower = comment.lower() | 320 match = re.match(r'(?i)(ok|correct|wrong(?:(?:\s|_)*answer)?)(?:$|\s+|[.,!:]+\s*)', comment) |
336 match = re.match(r'(ok|correct|wrong(?:(?:\s|_)*answer)?)(?:$|\s+|[.,!:]+\s*)', lower) | |
337 if match: | 321 if match: |
338 comment = comment[match.end():] | 322 comment = comment[match.end():] |
339 if not case.problem.config.maxexitcode: | 323 if not case.problem.config.maxexitcode: |
340 if case.process.returncode: | 324 if case.process.returncode: |
341 raise WrongAnswer(comment) | 325 raise WrongAnswer(comment) |
373 if options.erase and not case.validator: | 357 if options.erase and not case.validator: |
374 # TODO: re-use the same file name if possible | 358 # TODO: re-use the same file name if possible |
375 # FIXME: 2.5 lacks the delete parameter | 359 # FIXME: 2.5 lacks the delete parameter |
376 with tempfile.NamedTemporaryFile(delete=False) as f: | 360 with tempfile.NamedTemporaryFile(delete=False) as f: |
377 inputdatafname = f.name | 361 inputdatafname = f.name |
378 context = CopyDeleting(case, case.infile, inputdatafname) | 362 contextmgr = CopyDeleting(case, case.infile, inputdatafname) |
379 else: | 363 else: |
380 inputdatafname = case.problem.config.inname | 364 inputdatafname = case.problem.config.inname |
381 context = Copying(case.infile, inputdatafname) | 365 contextmgr = Copying(case.infile, inputdatafname) |
382 with context: | 366 with contextmgr: |
367 # FIXME: this U doesn't do anything good for the child process, does it? | |
383 with open(inputdatafname, 'rU') as infile: | 368 with open(inputdatafname, 'rU') as infile: |
384 with tempfile.TemporaryFile('w+') if options.erase and not case.validator else open(case.problem.config.outname, 'w+') as outfile: | 369 with tempfile.TemporaryFile('w+') if options.erase and not case.validator else open(case.problem.config.outname, 'w+') as outfile: |
385 # TODO: make sure outfile.file is passed to Popen if needed | 370 # TODO: make sure outfile.file is passed to Popen if needed |
386 try: | 371 try: |
387 try: | 372 try: |
391 # opt for silent dropping of the limit | 376 # opt for silent dropping of the limit |
392 case.process = Popen(case.problem.config.path, stdin=infile, stdout=outfile, stderr=devnull, universal_newlines=True, bufsize=-1) | 377 case.process = Popen(case.problem.config.path, stdin=infile, stdout=outfile, stderr=devnull, universal_newlines=True, bufsize=-1) |
393 except OSError: | 378 except OSError: |
394 raise CannotStartTestee(sys.exc_info()[1]) | 379 raise CannotStartTestee(sys.exc_info()[1]) |
395 case.time_started = clock() | 380 case.time_started = clock() |
381 time_next_check = case.time_started + .15 | |
396 if not case.maxtime: | 382 if not case.maxtime: |
397 while True: | 383 while True: |
398 exitcode, now = case.process.poll(), clock() | 384 exitcode, now = case.process.poll(), clock() |
399 if exitcode is not None: | 385 if exitcode is not None: |
400 case.time_stopped = now | 386 case.time_stopped = now |
401 break | 387 break |
402 elif canceled(): | 388 # For some reason (probably Microsoft's fault), |
403 raise CanceledByUser | 389 # msvcrt.kbhit() is slow as hell |
390 else: | |
391 if now >= time_next_check: | |
392 if canceled(): | |
393 raise CanceledByUser | |
394 else: | |
395 time_next_check = now + .15 | |
396 time.sleep(0) | |
404 else: | 397 else: |
405 time_end = case.time_started + case.maxtime | 398 time_end = case.time_started + case.maxtime |
406 while True: | 399 while True: |
407 exitcode, now = case.process.poll(), clock() | 400 exitcode, now = case.process.poll(), clock() |
408 if exitcode is not None: | 401 if exitcode is not None: |
409 case.time_stopped = now | 402 case.time_stopped = now |
410 break | 403 break |
411 elif now >= time_end: | 404 elif now >= time_end: |
412 raise TimeLimitExceeded | 405 raise TimeLimitExceeded |
413 elif canceled(): | 406 else: |
414 raise CanceledByUser | 407 if now >= time_next_check: |
408 if canceled(): | |
409 raise CanceledByUser | |
410 else: | |
411 time_next_check = now + .15 | |
412 time.sleep(0) | |
415 if config.globalconf.force_zero_exitcode and case.process.returncode: | 413 if config.globalconf.force_zero_exitcode and case.process.returncode: |
416 raise NonZeroExitCode(case.process.returncode) | 414 raise NonZeroExitCode(case.process.returncode) |
417 callback() | 415 callback() |
418 case.has_called_back = True | 416 case.has_called_back = True |
419 outfile.seek(0) | 417 outfile.seek(0) |
428 # opt for silent dropping of the limit | 426 # opt for silent dropping of the limit |
429 case.process = Popen(case.problem.config.path, stdin=devnull, stdout=devnull, stderr=STDOUT) | 427 case.process = Popen(case.problem.config.path, stdin=devnull, stdout=devnull, stderr=STDOUT) |
430 except OSError: | 428 except OSError: |
431 raise CannotStartTestee(sys.exc_info()[1]) | 429 raise CannotStartTestee(sys.exc_info()[1]) |
432 case.time_started = clock() | 430 case.time_started = clock() |
431 time_next_check = case.time_started + .15 | |
433 if not case.maxtime: | 432 if not case.maxtime: |
434 while True: | 433 while True: |
435 exitcode, now = case.process.poll(), clock() | 434 exitcode, now = case.process.poll(), clock() |
436 if exitcode is not None: | 435 if exitcode is not None: |
437 case.time_stopped = now | 436 case.time_stopped = now |
438 break | 437 break |
439 elif canceled(): | 438 else: |
440 raise CanceledByUser | 439 if now >= time_next_check: |
440 if canceled(): | |
441 raise CanceledByUser | |
442 else: | |
443 time_next_check = now + .15 | |
444 time.sleep(0) | |
441 else: | 445 else: |
442 time_end = case.time_started + case.maxtime | 446 time_end = case.time_started + case.maxtime |
443 while True: | 447 while True: |
444 exitcode, now = case.process.poll(), clock() | 448 exitcode, now = case.process.poll(), clock() |
445 if exitcode is not None: | 449 if exitcode is not None: |
446 case.time_stopped = now | 450 case.time_stopped = now |
447 break | 451 break |
448 elif now >= time_end: | 452 elif now >= time_end: |
449 raise TimeLimitExceeded | 453 raise TimeLimitExceeded |
450 elif canceled(): | 454 else: |
451 raise CanceledByUser | 455 if now >= time_next_check: |
456 if canceled(): | |
457 raise CanceledByUser | |
458 else: | |
459 time_next_check = now + .15 | |
460 time.sleep(0) | |
452 if config.globalconf.force_zero_exitcode and case.process.returncode: | 461 if config.globalconf.force_zero_exitcode and case.process.returncode: |
453 raise NonZeroExitCode(case.process.returncode) | 462 raise NonZeroExitCode(case.process.returncode) |
454 callback() | 463 callback() |
455 case.has_called_back = True | 464 case.has_called_back = True |
456 with open(case.problem.config.outname, 'rU') as output: | 465 with open(case.problem.config.outname, 'rU') as output: |