interactive_runner.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. # This code can be run as python2 or python3 in most systems.
  2. #
  3. # This is a small program that runs two processes, connecting the stdin of each
  4. # one to the stdout of the other.
  5. # It doesn't perform a lot of checking, so many errors may
  6. # be caught internally by Python (e.g., if your command line has incorrect
  7. # syntax) or not caught at all (e.g., if the judge or solution hangs).
  8. #
  9. # Run this as:
  10. # python interactive_runner.py <cmd_line_judge> -- <cmd_line_solution>
  11. #
  12. # For example, if you have a testing_tool.py in python3 (that takes a single
  13. # integer as a command line parameter) to use as judge -- like one
  14. # downloaded from a problem statement -- and you would run your solution
  15. # in a standalone using one of the following:
  16. # 1. python3 my_solution.py
  17. # 2. ./my_solution
  18. # 3. java Solution
  19. # 4. my_solution.exe
  20. # Then you could run the judge and solution together, using this, as:
  21. # 1. python interactive_runner.py python3 testing_tool.py 0 -- python3 my_solution.py
  22. # 2. python interactive_runner.py python3 testing_tool.py 0 -- ./my_solution
  23. # 3. python interactive_runner.py python3 testing_tool.py 0 -- java solution
  24. # 4. python interactive_runner.py python3 testing_tool.py 0 -- my_solution.exe
  25. # Notice that the solution in cases 2, 3 and 4 would usually have a
  26. # compilation step before running, which you should run in your usual way
  27. # before using this tool.
  28. #
  29. # This is only intended as a convenient tool to help contestants test solutions
  30. # locally. In particular, it is not identical to the implementation on our
  31. # server, which is more complex.
  32. #
  33. # The standard streams are handled the following way:
  34. # - judge's stdin is connected to the solution's stdout;
  35. # - judge's stdout is connected to the solution's stdin;
  36. # - stderrs of both judge and solution are piped to standard error stream, with
  37. # lines prepended by "judge: " or "sol: " respectively (note, no
  38. # synchronization is done so it's possible for the messages from both programs
  39. # to overlap with each other).
  40. from __future__ import print_function
  41. import sys, subprocess, threading
  42. class SubprocessThread(threading.Thread):
  43. def __init__(self,
  44. args,
  45. stdin_pipe=subprocess.PIPE,
  46. stdout_pipe=subprocess.PIPE,
  47. stderr_prefix=None):
  48. threading.Thread.__init__(self)
  49. self.stderr_prefix = stderr_prefix
  50. self.p = subprocess.Popen(
  51. args, stdin=stdin_pipe, stdout=stdout_pipe, stderr=subprocess.PIPE)
  52. def run(self):
  53. try:
  54. self.pipeToStdErr(self.p.stderr)
  55. self.return_code = self.p.wait()
  56. self.error_message = None
  57. except (SystemError, OSError):
  58. self.return_code = -1
  59. self.error_message = "The process crashed or produced too much output."
  60. # Reads bytes from the stream and writes them to sys.stderr prepending lines
  61. # with self.stderr_prefix.
  62. # We are not reading by lines to guard against the case when EOL is never
  63. # found in the stream.
  64. def pipeToStdErr(self, stream):
  65. new_line = True
  66. while True:
  67. chunk = stream.readline(1)
  68. if not chunk:
  69. return
  70. chunk = chunk.decode("UTF-8")
  71. if new_line and self.stderr_prefix:
  72. chunk = self.stderr_prefix + chunk
  73. new_line = False
  74. sys.stderr.write(chunk)
  75. if chunk.endswith("\n"):
  76. new_line = True
  77. sys.stderr.flush()
  78. assert sys.argv.count("--") == 1, (
  79. "There should be exactly one instance of '--' in the command line.")
  80. sep_index = sys.argv.index("--")
  81. judge_args = sys.argv[1:sep_index]
  82. sol_args = sys.argv[sep_index + 1:]
  83. t_sol = SubprocessThread(sol_args, stderr_prefix=" sol: ")
  84. t_judge = SubprocessThread(
  85. judge_args,
  86. stdin_pipe=t_sol.p.stdout,
  87. stdout_pipe=t_sol.p.stdin,
  88. stderr_prefix="judge: ")
  89. t_sol.start()
  90. t_judge.start()
  91. t_sol.join()
  92. t_judge.join()
  93. # Print an empty line to handle the case when stderr doesn't print EOL.
  94. print()
  95. print("Judge return code:", t_judge.return_code)
  96. if t_judge.error_message:
  97. print("Judge error message:", t_judge.error_message)
  98. print("Solution return code:", t_sol.return_code)
  99. if t_sol.error_message:
  100. print("Solution error message:", t_sol.error_message)
  101. if t_sol.return_code:
  102. print("A solution finishing with exit code other than 0 (without exceeding "
  103. "time or memory limits) would be interpreted as a Runtime Error "
  104. "in the system.")
  105. elif t_judge.return_code:
  106. print("A solution finishing with exit code 0 (without exceeding time or "
  107. "memory limits) and a judge finishing with exit code other than 0 "
  108. "would be interpreted as a Wrong Answer in the system.")
  109. else:
  110. print("A solution and judge both finishing with exit code 0 (without "
  111. "exceeding time or memory limits) would be interpreted as Correct "
  112. "in the system.")