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: