From a5c3b35d26dcd171254091196330492d667ecf0e Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 12 Dec 2024 21:14:53 +0000 Subject: [PATCH] Fixed the issue with test case decoder function not getting invoked when dumping to string --- coderunners/checkers.py | 16 +++++++++------- coderunners/executors.py | 4 ++-- models.py | 10 +++++++--- sync/app.py | 5 +++++ sync/services.py | 5 ++++- sync/summary.py | 11 +++++++---- 6 files changed, 34 insertions(+), 17 deletions(-) diff --git a/coderunners/checkers.py b/coderunners/checkers.py index f531605..eabc18c 100644 --- a/coderunners/checkers.py +++ b/coderunners/checkers.py @@ -41,9 +41,11 @@ def check( ... @staticmethod - def from_mode(mode: str, - float_precision: float | None = None, delimiter: str | None = None, - executor: Executor | None = None) -> 'Checker': + def from_mode( + mode: str, + float_precision: float | None = None, delimiter: str | None = None, + executor: Executor | None = None, + ) -> 'Checker': if mode == 'ok': return OkChecker() if mode == 'whole': @@ -59,10 +61,10 @@ def from_mode(mode: str, class OkChecker(Checker): def check( - self, inputs: str, output: str, target: str, code: dict[str, str], - input_files: dict[str, str] | None = None, output_files: dict[str, str] | None = None, - target_files: dict[str, str] | None = None, input_assets: dict[str, bytes] | None = None, - output_assets: dict[str, bytes] | None = None, target_assets: dict[str, bytes] | None = None, + self, inputs: str, output: str, target: str, code: dict[str, str], + input_files: dict[str, str] | None = None, output_files: dict[str, str] | None = None, + target_files: dict[str, str] | None = None, input_assets: dict[str, bytes] | None = None, + output_assets: dict[str, bytes] | None = None, target_assets: dict[str, bytes] | None = None, ) -> tuple[Status, float, str | None]: return Status.OK, 100, None diff --git a/coderunners/executors.py b/coderunners/executors.py index 6e06b29..b447711 100644 --- a/coderunners/executors.py +++ b/coderunners/executors.py @@ -32,12 +32,12 @@ def run(self, test: TestCase, time_limit: float, memory_limit_mb: int, output_li # Crete input files and input assets for filename, content in (test.input_files or {}).items(): file = self.ROOT / filename - print('Creating file at:', file) + print(f'Creating file at: {file} with content len: {len(content)} of type {type(content)}') file.parent.mkdir(parents=True, exist_ok=True) file.write_text(content) for filename, content in (test.input_assets or {}).items(): file = self.ROOT / filename - print('Creating asset at:', file) + print(f'Creating asset at: {file} with content len: {len(content)} of type {type(content)}') file.parent.mkdir(parents=True, exist_ok=True) file.write_bytes(content) diff --git a/models.py b/models.py index b121756..2aaa296 100644 --- a/models.py +++ b/models.py @@ -9,13 +9,17 @@ class DataClassJsonCamelMixIn(DataClassJsonMixin): dataclass_json_config = config(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE)['dataclasses_json'] -def base64_to_bytes(data: dict[str, str | bytes] | None) -> dict[str, bytes] | None: +def base64_to_bytes(data: dict[str, str] | None) -> dict[str, bytes] | None: + if data is not None: + print('base64_to_bytes:', {filename: type(content) for filename, content in (data or {}).items()}) if data is not None and all(isinstance(content, str) for content in data.values()): return {filename: base64.b64decode(content.encode('utf-8')) for filename, content in data.items()} return data def bytes_to_base64(data: dict[str, bytes] | None) -> dict[str, str] | None: + if data is not None: + print('bytes_to_base64:', {filename: type(content) for filename, content in (data or {}).items()}) if data is not None and all(isinstance(content, bytes) for content in data.values()): return {filename: base64.b64encode(content).decode('utf-8') for filename, content in data.items()} return data @@ -115,8 +119,8 @@ class RunResult(DataClassJsonCamelMixIn): message: str | None = None outputs: str | None = None errors: str | None = None - output_files: dict[str, str] | None = None - output_assets: dict[str, bytes] | None = field( + output_files: dict[str, str] | None = None # mapping filename -> textual content + output_assets: dict[str, bytes] | None = field( # mapping filename -> binary content metadata=config(encoder=bytes_to_base64, decoder=base64_to_bytes), default=None, ) diff --git a/sync/app.py b/sync/app.py index e414bf3..31c2325 100644 --- a/sync/app.py +++ b/sync/app.py @@ -35,6 +35,11 @@ def trigger_sync_s3_handler(event, context): res = json.loads(res) print('invocation result:', res) + if 'tests_truncated' not in res: + error = res.get('errorMessage', 'Could not process tests...') + print('There was an error and we could not get the tests', error) + return SummaryTable(dynamodb).log_error(problem, error) + tests = TestCase.schema().load(res['tests_truncated'], many=True) print('tests:', tests) SummaryTable(dynamodb).write(problem, tests) diff --git a/sync/services.py b/sync/services.py index 556d3a8..5bc8c79 100644 --- a/sync/services.py +++ b/sync/services.py @@ -1,4 +1,5 @@ import gzip +import json import sys from glob import glob from pathlib import Path @@ -73,7 +74,9 @@ def encrypt_tests(tests: list[TestCase], encryption_key: str) -> bytes: # Compress: (1) json.dumps (2) .encode('utf-8') (3) gzip.compress() (4) encrypt # Decompress: (1) decrypt (2) gzip.decompress() (3) .decode('utf-8') (4) json.loads() - tests = TestCase.schema().dumps(tests, many=True) # (1) + # TestCase.schema().dumps(tests, many=True) does not invoke the decoder function properly + # https://github.com/lidatong/dataclasses-json/issues/551 => We'll use json.dumps([t.to_dict()...]) instead + tests = json.dumps([test.to_dict() for test in tests]) # (1) print('initial sys.getsizeof of tests:', sys.getsizeof(tests)) big = sys.getsizeof(tests) > 50 * 1024 * 1024 tests = tests.encode('utf-8') # (2) diff --git a/sync/summary.py b/sync/summary.py index 6f33998..ecdaa5a 100644 --- a/sync/summary.py +++ b/sync/summary.py @@ -14,6 +14,12 @@ class SummaryTable: def __init__(self, dynamodb): self.table = dynamodb.Table(self.TABLE_NAME) + def log_error(self, problem_id: str, message: str) -> None: + self.table.put_item(Item={ + 'id': problem_id, + 'message': message, + }) + def write(self, problem_id: str, tests: list[TestCase]) -> None: response = self.table.put_item(Item={ 'id': problem_id, @@ -21,10 +27,7 @@ def write(self, problem_id: str, tests: list[TestCase]) -> None: 'tests': [t.to_dict() for t in tests], }) if response['ResponseMetadata']['HTTPStatusCode'] not in range(200, 300): - self.table.put_item(Item={ - 'id': problem_id, - 'message': 'Could not summarize the tests', - }) + self.log_error(problem_id, 'Could not summarize the tests') raise SummaryWriteError('Could not summarize the tests', response)