rbx.cp 0.5.46__py3-none-any.whl → 0.5.48__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. rbx/box/checkers.py +81 -28
  2. rbx/box/cli.py +12 -10
  3. rbx/box/environment.py +1 -1
  4. rbx/box/naming.py +22 -0
  5. rbx/box/packaging/boca/extension.py +1 -0
  6. rbx/box/packaging/boca/packager.py +54 -11
  7. rbx/box/packaging/main.py +7 -0
  8. rbx/box/packaging/moj/packager.py +89 -19
  9. rbx/box/packaging/packager.py +15 -3
  10. rbx/box/packaging/polygon/packager.py +5 -4
  11. rbx/box/solutions.py +2 -2
  12. rbx/box/statements/build_statements.py +2 -1
  13. rbx/box/stresses.py +0 -1
  14. rbx/box/tasks.py +6 -4
  15. rbx/grading/judge/sandbox.py +29 -1
  16. rbx/grading/judge/sandboxes/isolate.py +10 -0
  17. rbx/grading/judge/sandboxes/stupid_sandbox.py +16 -4
  18. rbx/grading/judge/sandboxes/timeit.py +12 -3
  19. rbx/grading/processing_context.py +48 -0
  20. rbx/grading/steps.py +25 -14
  21. rbx/resources/packagers/boca/checker.sh +8 -6
  22. rbx/resources/packagers/boca/compare.sh +48 -0
  23. rbx/resources/packagers/boca/interactive/c +207 -0
  24. rbx/resources/packagers/boca/interactive/cc +207 -0
  25. rbx/resources/packagers/boca/interactive/cpp +207 -0
  26. rbx/resources/packagers/boca/interactive/java +240 -0
  27. rbx/resources/packagers/boca/interactive/kt +231 -0
  28. rbx/resources/packagers/boca/interactive/py2 +209 -0
  29. rbx/resources/packagers/boca/interactive/py3 +209 -0
  30. rbx/resources/packagers/boca/interactor_compile.sh +45 -0
  31. rbx/resources/packagers/boca/run/bkp +163 -0
  32. rbx/resources/packagers/boca/run/c +19 -19
  33. rbx/resources/packagers/boca/run/cc +19 -19
  34. rbx/resources/packagers/boca/run/cpp +19 -19
  35. rbx/resources/packagers/boca/run/java +51 -51
  36. rbx/resources/packagers/boca/run/kt +30 -30
  37. rbx/resources/packagers/boca/run/py2 +42 -42
  38. rbx/resources/packagers/boca/run/py3 +42 -42
  39. rbx/resources/packagers/moj/scripts/c/compile.sh +19 -0
  40. rbx/resources/packagers/moj/scripts/c/prep.sh +5 -0
  41. rbx/resources/packagers/moj/scripts/c/run.sh +16 -0
  42. rbx/resources/packagers/moj/scripts/compare.sh +32 -6
  43. rbx/resources/packagers/moj/scripts/cpp/compile.sh +19 -0
  44. rbx/resources/packagers/moj/scripts/cpp/prep.sh +5 -0
  45. rbx/resources/packagers/moj/scripts/cpp/run.sh +16 -0
  46. rbx/resources/packagers/moj/scripts/interactor_prep.sh +14 -0
  47. rbx/resources/packagers/moj/scripts/interactor_run.sh +38 -0
  48. rbx/resources/packagers/moj/scripts/java/compile.sh +12 -0
  49. rbx/resources/packagers/moj/scripts/java/prep.sh +8 -0
  50. rbx/resources/packagers/moj/scripts/java/run.sh +17 -0
  51. rbx/resources/packagers/moj/scripts/py2/compile.sh +7 -0
  52. rbx/resources/packagers/moj/scripts/py2/prep.sh +5 -0
  53. rbx/resources/packagers/moj/scripts/py2/run.sh +16 -0
  54. rbx/resources/packagers/moj/scripts/py3/compile.sh +7 -0
  55. rbx/resources/packagers/moj/scripts/py3/prep.sh +5 -0
  56. rbx/resources/packagers/moj/scripts/py3/run.sh +16 -0
  57. {rbx_cp-0.5.46.dist-info → rbx_cp-0.5.48.dist-info}/METADATA +1 -1
  58. {rbx_cp-0.5.46.dist-info → rbx_cp-0.5.48.dist-info}/RECORD +61 -33
  59. rbx/resources/packagers/boca/compare +0 -53
  60. {rbx_cp-0.5.46.dist-info → rbx_cp-0.5.48.dist-info}/LICENSE +0 -0
  61. {rbx_cp-0.5.46.dist-info → rbx_cp-0.5.48.dist-info}/WHEEL +0 -0
  62. {rbx_cp-0.5.46.dist-info → rbx_cp-0.5.48.dist-info}/entry_points.txt +0 -0
rbx/box/solutions.py CHANGED
@@ -616,7 +616,7 @@ def _get_solution_repr(sol: Solution) -> List[Tuple[str, str]]:
616
616
  ]
617
617
 
618
618
 
619
- def pick_solutions(tracked_solutions: Optional[Set[str]]) -> List[str]:
619
+ async def pick_solutions(tracked_solutions: Optional[Set[str]]) -> List[str]:
620
620
  pkg = package.find_problem_package_or_die()
621
621
  if tracked_solutions is None:
622
622
  tracked_solutions = set(str(sol.path) for sol in pkg.solutions)
@@ -630,7 +630,7 @@ def pick_solutions(tracked_solutions: Optional[Set[str]]) -> List[str]:
630
630
  if str(sol.path) in tracked_solutions
631
631
  ]
632
632
 
633
- picked = questionary.checkbox('Select solutions', choices=choices).ask()
633
+ picked = await questionary.checkbox('Select solutions', choices=choices).ask_async()
634
634
  if picked is None:
635
635
  raise typer.Abort()
636
636
  return picked
@@ -7,7 +7,7 @@ import syncer
7
7
  import typer
8
8
 
9
9
  from rbx import annotations, console
10
- from rbx.box import environment, package
10
+ from rbx.box import environment, naming, package
11
11
  from rbx.box.schema import Package
12
12
  from rbx.box.statements.builders import (
13
13
  BUILDER_LIST,
@@ -291,6 +291,7 @@ def build_statement(
291
291
  output_type=output_type,
292
292
  use_samples=use_samples,
293
293
  is_editorial=is_editorial,
294
+ short_name=naming.get_problem_shortname(),
294
295
  )
295
296
  statement_path = (
296
297
  package.get_build_path()
rbx/box/stresses.py CHANGED
@@ -50,7 +50,6 @@ def _compile_finder(finder: CodeItem) -> str:
50
50
  return digest
51
51
 
52
52
 
53
- @syncer.sync
54
53
  async def run_stress(
55
54
  name: str,
56
55
  timeoutInSeconds: int,
rbx/box/tasks.py CHANGED
@@ -194,7 +194,8 @@ async def _run_communication_solution_on_testcase(
194
194
  output_path = testcase.outputPath
195
195
  else:
196
196
  output_path = output_dir / testcase.inputPath.with_suffix('.out').name
197
- error_path = output_path.with_suffix('.err')
197
+ solution_error_path = output_path.with_suffix('.sol.err')
198
+ interactor_error_path = output_path.with_suffix('.int.err')
198
199
  log_path = output_path.with_suffix('.log')
199
200
  output_path.parent.mkdir(parents=True, exist_ok=True)
200
201
 
@@ -204,7 +205,7 @@ async def _run_communication_solution_on_testcase(
204
205
  interactor_item = CommunicationItem(
205
206
  code=package.get_interactor(),
206
207
  executable=DigestOrSource.create(interactor_digest),
207
- stderr=DigestOrDest.create(error_path),
208
+ stderr=DigestOrDest.create(interactor_error_path),
208
209
  extra_config=interactor_extra_config,
209
210
  extra_args='interactor.in interactor.out',
210
211
  inputs=[
@@ -230,6 +231,7 @@ async def _run_communication_solution_on_testcase(
230
231
  solution_item = CommunicationItem(
231
232
  code=solution,
232
233
  executable=DigestOrSource.create(compiled_digest),
234
+ stderr=DigestOrDest.create(solution_error_path),
233
235
  extra_config=extra_config,
234
236
  capture=DigestOrDest.create(solution_capture_path)
235
237
  if solution_capture_path
@@ -248,7 +250,7 @@ async def _run_communication_solution_on_testcase(
248
250
  checker_digest,
249
251
  run_log,
250
252
  interactor_run_log,
251
- error_path,
253
+ interactor_error_path,
252
254
  testcase,
253
255
  output_path,
254
256
  )
@@ -263,7 +265,7 @@ async def _run_communication_solution_on_testcase(
263
265
  log=TestcaseLog(
264
266
  **(run_log.model_dump() if run_log is not None else {}),
265
267
  stdout_absolute_path=output_path.absolute(),
266
- stderr_absolute_path=error_path.absolute(),
268
+ stderr_absolute_path=solution_error_path.absolute(),
267
269
  log_absolute_path=log_path.absolute(),
268
270
  ),
269
271
  )
@@ -6,6 +6,7 @@ import logging
6
6
  import os
7
7
  import pathlib
8
8
  import select
9
+ import signal
9
10
  import stat
10
11
  import subprocess
11
12
  import sys
@@ -14,6 +15,7 @@ from typing import IO, Any, Dict, List, Optional
14
15
 
15
16
  import pydantic
16
17
 
18
+ from rbx.grading import processing_context
17
19
  from rbx.grading.judge import cacher, storage
18
20
 
19
21
  logger = logging.getLogger(__name__)
@@ -187,6 +189,7 @@ class SandboxBase(abc.ABC):
187
189
 
188
190
  EXIT_SANDBOX_ERROR = 'sandbox error'
189
191
  EXIT_OK = 'ok'
192
+ EXIT_TERMINATED = 'terminated'
190
193
  EXIT_SIGNAL = 'signal'
191
194
  EXIT_TIMEOUT = 'timeout'
192
195
  EXIT_TIMEOUT_WALL = 'wall timeout'
@@ -225,6 +228,7 @@ class SandboxBase(abc.ABC):
225
228
  self.cmd_file = pathlib.PosixPath('commands.log')
226
229
 
227
230
  self.params = params or SandboxParams()
231
+ self.pid = None
228
232
 
229
233
  # Set common environment variables.
230
234
  # Specifically needed by Python, that searches the home for
@@ -324,6 +328,28 @@ class SandboxBase(abc.ABC):
324
328
  """
325
329
  pass
326
330
 
331
+ def set_pid(self, pid: int):
332
+ processing_context.add_to_processing_context(pid)
333
+ self.pid = pid
334
+
335
+ def get_pid(self) -> Optional[int]:
336
+ """Return the PID of the sandboxed process.
337
+
338
+ return (int|None): the PID of the sandboxed process, or None if
339
+ the sandboxed process is not running.
340
+
341
+ """
342
+ return self.pid
343
+
344
+ @abc.abstractmethod
345
+ def get_detailed_logs(self) -> str:
346
+ """Return the detailed logs of the sandbox.
347
+
348
+ return (string): the detailed logs of the sandbox.
349
+
350
+ """
351
+ pass
352
+
327
353
  @abc.abstractmethod
328
354
  def get_human_exit_description(self) -> str:
329
355
  """Get the status of the sandbox and return a human-readable
@@ -667,7 +693,9 @@ class SandboxBase(abc.ABC):
667
693
  did).
668
694
 
669
695
  """
670
- return exitcode == 0
696
+ # SIGTERM can be safely ignored, just in case it leaks away from
697
+ # the sandbox.
698
+ return exitcode == 0 or exitcode == -signal.SIGTERM
671
699
 
672
700
  @abc.abstractmethod
673
701
  def initialize(self):
@@ -420,6 +420,7 @@ class IsolateSandbox(SandboxBase):
420
420
  return (string): the main reason why the sandbox terminated.
421
421
 
422
422
  """
423
+ # TODO: figure out EXIT_TERMINATED
423
424
  assert self.log is not None
424
425
  status_list = self.get_status_list()
425
426
  if 'XX' in status_list:
@@ -462,6 +463,14 @@ class IsolateSandbox(SandboxBase):
462
463
  return 'Execution failed because the return code was nonzero'
463
464
  return ''
464
465
 
466
+ def get_detailed_logs(self) -> str:
467
+ """Return the detailed logs of the sandbox.
468
+
469
+ return (string): the detailed logs of the sandbox.
470
+
471
+ """
472
+ return str(self.log)
473
+
465
474
  def inner_absolute_path(self, path: pathlib.Path) -> pathlib.Path:
466
475
  """Translate from a relative path inside the sandbox to an
467
476
  absolute path inside the sandbox.
@@ -602,6 +611,7 @@ class IsolateSandbox(SandboxBase):
602
611
  # std*** to interfere with command. Otherwise we let the
603
612
  # caller handle these issues.
604
613
  with popen as p:
614
+ self.set_pid(p.pid)
605
615
  exitcode = self.translate_box_exitcode(
606
616
  wait_without_std([p], actually_pipe_to_stdout=self.debug)[0]
607
617
  )
@@ -57,7 +57,6 @@ class StupidSandbox(SandboxBase):
57
57
  self.initialize()
58
58
 
59
59
  self.exec_num = -1
60
- self.popen = None
61
60
  self.log = None
62
61
  self.returncode = None
63
62
 
@@ -192,9 +191,13 @@ class StupidSandbox(SandboxBase):
192
191
  return (string): the main reason why the sandbox terminated.
193
192
 
194
193
  """
195
- if self.returncode != 0:
194
+ if self.returncode is not None and not self.translate_box_exitcode(
195
+ self.returncode
196
+ ):
196
197
  return self.EXIT_SANDBOX_ERROR
197
198
  status_list = self.get_status_list()
199
+ if 'TE' in status_list:
200
+ return self.EXIT_TERMINATED
198
201
  if 'WT' in status_list:
199
202
  return self.EXIT_TIMEOUT_WALL
200
203
  if 'TO' in status_list:
@@ -218,6 +221,9 @@ class StupidSandbox(SandboxBase):
218
221
  assert self.log is not None
219
222
  return int(self.log['exit-code'])
220
223
 
224
+ def get_detailed_logs(self) -> str:
225
+ return str(self.log)
226
+
221
227
  def get_human_exit_description(self) -> str:
222
228
  """Get the status of the sandbox and return a human-readable
223
229
  string describing it.
@@ -299,13 +305,19 @@ class StupidSandbox(SandboxBase):
299
305
  + self.get_timeit_args()
300
306
  + command
301
307
  )
302
- self.returncode = subprocess.call(
308
+ with subprocess.Popen(
303
309
  real_command,
304
310
  stdin=subprocess.PIPE,
305
311
  stdout=subprocess.PIPE,
306
312
  stderr=subprocess.STDOUT,
307
313
  env={**os.environ, **self.params.set_env},
308
- )
314
+ ) as p:
315
+ self.set_pid(p.pid)
316
+ try:
317
+ self.returncode = p.wait()
318
+ except Exception:
319
+ p.kill()
320
+ raise
309
321
  self.hydrate_logs()
310
322
  return self.translate_box_exitcode(self.returncode)
311
323
 
@@ -223,6 +223,7 @@ def wait_and_finish(
223
223
  pid: int,
224
224
  options: Options,
225
225
  start_time: float,
226
+ status_holder: Set[str],
226
227
  alarm_msg: Optional[List[Optional[str]]] = None,
227
228
  ):
228
229
  _, exitstatus, ru = os.wait4(pid, 0)
@@ -237,7 +238,7 @@ def wait_and_finish(
237
238
  if exitcode < 0:
238
239
  entries.append(f'exit-sig: {-exitcode}')
239
240
 
240
- status = set()
241
+ status = status_holder
241
242
  if exitcode > 0:
242
243
  status.add('RE')
243
244
  if exitcode < 0:
@@ -284,9 +285,11 @@ def main():
284
285
  os.chdir(options.chdir)
285
286
  set_rlimits(options)
286
287
  redirect_fds(options)
288
+ signal.signal(signal.SIGPIPE, signal.SIG_DFL)
287
289
  os.execvp(options.argv[0], options.argv)
288
290
 
289
291
  alarm_msg: List[Optional[str]] = [None]
292
+ status_holder: Set[str] = set()
290
293
 
291
294
  def handle_alarm(*args, **kwargs):
292
295
  nonlocal alarm_msg
@@ -311,15 +314,21 @@ def main():
311
314
 
312
315
  signal.setitimer(signal.ITIMER_REAL, 0.3)
313
316
 
317
+ def handle_sub_term(*args, **kwargs):
318
+ nonlocal status_holder
319
+ status_holder.add('TE')
320
+ os.kill(sub_pid, 9)
321
+
314
322
  signal.setitimer(signal.ITIMER_REAL, 0.3)
315
323
  signal.signal(signal.SIGALRM, handle_alarm)
316
- wait_and_finish(sub_pid, options, start_time, alarm_msg=alarm_msg)
324
+ signal.signal(signal.SIGTERM, handle_sub_term)
317
325
 
326
+ wait_and_finish(sub_pid, options, start_time, status_holder, alarm_msg=alarm_msg)
318
327
  # Cancel alarm before exiting to avoid surprises.
319
328
  signal.setitimer(signal.ITIMER_REAL, 0)
320
329
 
321
330
  # Exit gracefully.
322
- sys.exit()
331
+ sys.exit(0)
323
332
 
324
333
 
325
334
  if __name__ == '__main__':
@@ -0,0 +1,48 @@
1
+ import contextlib
2
+ import os
3
+ import signal
4
+ import threading
5
+ from typing import Optional, Set
6
+
7
+ _processing_context_pids: Optional[Set[int]] = None
8
+ _lock = threading.Lock()
9
+
10
+ # Creating a processing context is not thread-safe, but adding to it is.
11
+
12
+
13
+ @contextlib.contextmanager
14
+ def new_processing_context():
15
+ global _processing_context_pids
16
+ with _lock:
17
+ old_processing_context_pids = _processing_context_pids
18
+ _processing_context_pids = set()
19
+ try:
20
+ yield
21
+ finally:
22
+ with _lock:
23
+ _processing_context_pids = old_processing_context_pids
24
+
25
+
26
+ def get_processing_context() -> Set[int]:
27
+ with _lock:
28
+ return _processing_context_pids or set()
29
+
30
+
31
+ def add_to_processing_context(pid: int):
32
+ global _processing_context_pids
33
+ with _lock:
34
+ if _processing_context_pids is None:
35
+ return
36
+ _processing_context_pids.add(pid)
37
+
38
+
39
+ def terminate_all_processes_in_context():
40
+ with _lock:
41
+ if _processing_context_pids is None:
42
+ return
43
+ for pid in _processing_context_pids:
44
+ try:
45
+ os.kill(pid, signal.SIGTERM)
46
+ except OSError:
47
+ pass
48
+ _processing_context_pids.clear()
rbx/grading/steps.py CHANGED
@@ -21,6 +21,7 @@ from rich.text import Text
21
21
  from rbx import utils
22
22
  from rbx.config import get_bits_stdcpp, get_jngen, get_testlib
23
23
  from rbx.console import console
24
+ from rbx.grading import processing_context
24
25
  from rbx.grading.judge.sandbox import SandboxBase, SandboxParams
25
26
  from rbx.grading.judge.storage import Storage, copyfileobj
26
27
 
@@ -197,6 +198,7 @@ class RunLog(BaseModel):
197
198
  exitstatus: str = SandboxBase.EXIT_SANDBOX_ERROR
198
199
  time: Optional[float] = 0.0
199
200
  memory: Optional[int] = 0
201
+ sandbox: str = ''
200
202
  warnings: bool = False
201
203
  metadata: Optional[RunLogMetadata] = None
202
204
 
@@ -210,7 +212,7 @@ class RunLog(BaseModel):
210
212
  return 'OK'
211
213
  time = self.time or 0.0
212
214
  memory = self.memory or 0
213
- return f'FAILED with exit code {self.exitcode} and sandbox status {self.exitstatus} (time: {time}s, memory: {memory//(1024*1024)}MB)'
215
+ return f'FAILED with exit code {self.exitcode} and sandbox status {self.exitstatus} (time: {time}s, memory: {memory // (1024 * 1024)}MB)'
214
216
 
215
217
 
216
218
  class PreprocessLog(RunLog):
@@ -330,7 +332,7 @@ def _expand_part(part: str, sandbox: SandboxBase) -> List[str]:
330
332
 
331
333
  def _split_and_expand(command: str, sandbox: SandboxBase) -> List[str]:
332
334
  res = []
333
- parts = shlex.split(command.format(memory=sandbox.params.address_space))
335
+ parts = shlex.split(command.format(memory=sandbox.params.address_space or 2048))
334
336
  for part in parts:
335
337
  res.extend(_expand_part(part, sandbox))
336
338
  return res
@@ -567,6 +569,7 @@ def compile(
567
569
  memory=sandbox.get_memory_used(),
568
570
  warnings=_check_for_compilation_warnings(sandbox, stderr_file),
569
571
  log='\n'.join(std_outputs),
572
+ sandbox=sandbox.get_detailed_logs(),
570
573
  )
571
574
  logs.append(log)
572
575
 
@@ -594,6 +597,7 @@ async def run(
594
597
  sandbox: SandboxBase,
595
598
  artifacts: GradingArtifacts,
596
599
  metadata: Optional[RunLogMetadata] = None,
600
+ kill_on_processing_context_exit: bool = False,
597
601
  ) -> Optional[RunLog]:
598
602
  _process_input_artifacts(artifacts, sandbox)
599
603
  _process_fifos(artifacts, sandbox)
@@ -609,6 +613,9 @@ async def run(
609
613
  )
610
614
  return None
611
615
 
616
+ if sandbox.get_exit_code() != 0 and kill_on_processing_context_exit:
617
+ processing_context.terminate_all_processes_in_context()
618
+
612
619
  if not _process_output_artifacts(artifacts, sandbox):
613
620
  return None
614
621
 
@@ -625,6 +632,7 @@ async def run(
625
632
  time=sandbox.get_execution_time(),
626
633
  memory=sandbox.get_memory_used(),
627
634
  metadata=metadata,
635
+ sandbox=sandbox.get_detailed_logs(),
628
636
  )
629
637
  if metadata is not None and metadata.is_sanitized:
630
638
  run_log.warnings = _check_for_sanitizer_warnings(
@@ -649,19 +657,22 @@ async def run_coordinated(
649
657
  interactor: CoordinatedRunParams,
650
658
  solution: CoordinatedRunParams,
651
659
  ) -> Tuple[Optional[RunLog], Optional[RunLog]]:
652
- runs = tuple(
653
- run(
654
- params.command,
655
- params.params,
656
- params.sandbox,
657
- params.artifacts,
658
- params.metadata,
660
+ with processing_context.new_processing_context():
661
+ runs = tuple(
662
+ run(
663
+ params.command,
664
+ params.params,
665
+ params.sandbox,
666
+ params.artifacts,
667
+ params.metadata,
668
+ kill_on_processing_context_exit=True,
669
+ )
670
+ for params in [interactor, solution]
671
+ )
672
+ return typing.cast(
673
+ Tuple[Optional[RunLog], Optional[RunLog]],
674
+ tuple(await asyncio.gather(*runs)),
659
675
  )
660
- for params in [interactor, solution]
661
- )
662
- return typing.cast(
663
- Tuple[Optional[RunLog], Optional[RunLog]], tuple(await asyncio.gather(*runs))
664
- )
665
676
 
666
677
 
667
678
  def _normalize_checked_words(s: str) -> Tuple[str, ...]:
@@ -1,9 +1,10 @@
1
1
  ### START OF CHECKER COMPILATION
2
+ CDIR=$(pwd)
2
3
  CHECKER_PATH="checker.cpp"
3
- CHECKER_OUT="../checker.exe"
4
+ CHECKER_OUT="checker.exe"
4
5
 
5
6
  # find compiler
6
- cc=`which g++`
7
+ cc=$(which g++)
7
8
  [ -x "$cc" ] || cc=/usr/bin/g++
8
9
  if [ ! -x "$cc" ]; then
9
10
  echo "$cc not found or it's not executable"
@@ -12,25 +13,26 @@ fi
12
13
  read -r -d '' TestlibContent <<"EOF"
13
14
  {{testlib_content}}
14
15
  EOF
15
-
16
+
16
17
  read -r -d '' CheckerContent <<"EOF"
17
18
  {{checker_content}}
18
19
  EOF
19
20
 
20
- printf "%s" "${TestlibContent}" > testlib.h
21
- printf "%s" "${CheckerContent}" > $CHECKER_PATH
21
+ printf "%s" "${TestlibContent}" >testlib.h
22
+ printf "%s" "${CheckerContent}" >$CHECKER_PATH
22
23
 
23
24
  checker_hash=($(md5sum $CHECKER_PATH))
24
25
  checker_cache="/tmp/boca-chk-${checker_hash}"
25
26
 
26
27
  echo "Polygon checker hash: $checker_hash"
28
+ echo "Copying polygon checker to $CDIR/$CHECKER_OUT"
27
29
  if [ -f "$checker_cache" ]; then
28
30
  echo "Recovering polygon checker from cache: $checker_cache"
29
31
  cp "$checker_cache" $CHECKER_OUT -f
30
32
  else
31
33
  echo "Compiling polygon checker: $CHECKER_PATH"
32
34
  $cc {{rbxFlags}} $CHECKER_PATH -o $CHECKER_OUT
33
-
35
+
34
36
  if [ $? -ne 0 ]; then
35
37
  echo "Checker could not be compiled"
36
38
  exit 47
@@ -0,0 +1,48 @@
1
+ #!/bin/bash
2
+ if [ ! -r "$1" -o ! -r "$2" ]; then
3
+ echo "Parameter problem"
4
+ exit 43
5
+ fi
6
+
7
+ INTERACTOREXITCODE=$(grep '^testlib exitcode' "$1" | awk '{print $NF}')
8
+ if [[ -n $INTERACTOREXITCODE ]]; then
9
+ echo "interactor exitcode = $INTERACTOREXITCODE"
10
+ if [[ $INTERACTOREXITCODE -eq 1 ]]; then
11
+ echo "interactor return wrong answer"
12
+ exit 6
13
+ elif [[ $INTERACTOREXITCODE -eq 2 ]]; then
14
+ echo "interactor invalid input"
15
+ exit 6
16
+ elif [[ $INTERACTOREXITCODE -eq 3 ]]; then
17
+ echo "interactor failed with exit code 3"
18
+ exit 43
19
+ else
20
+ echo "interactor failed with exit code $INTERACTOREXITCODE"
21
+ exit 47
22
+ fi
23
+ fi
24
+
25
+ # Next lines of this script just compares team_output and sol_output,
26
+ # although it is possible to change them to more complex evaluations.
27
+ output=$(./checker.exe $3 $1 $2 2>&1 >/dev/null)
28
+ EC=$?
29
+
30
+ echo "checker exitcode = $EC"
31
+ echo "$output"
32
+
33
+ if [ $EC -eq 0 ]; then
34
+ echo "checker found no differences"
35
+ exit 4
36
+ elif [ $EC -eq 1 ]; then
37
+ echo "checker found differences"
38
+ exit 6
39
+ elif [ $EC -eq 2 ]; then
40
+ echo "checker found invalid output"
41
+ exit 6
42
+ elif [ $EC -eq 3 ]; then
43
+ echo "judge failed with $EC"
44
+ exit 43
45
+ else
46
+ echo "judge failed with $EC"
47
+ exit 47
48
+ fi