From ecfec8db37dace13b2e04355330bc2ff24fadb91 Mon Sep 17 00:00:00 2001 From: weilycoder Date: Mon, 16 Dec 2024 22:21:51 +0800 Subject: [PATCH 1/5] Rewrite utils.py; Add function comments. --- cyaron/utils.py | 84 ++++++++++++++++++++++++++----------------------- 1 file changed, 44 insertions(+), 40 deletions(-) diff --git a/cyaron/utils.py b/cyaron/utils.py index 5b7cfd5..e61ecfb 100644 --- a/cyaron/utils.py +++ b/cyaron/utils.py @@ -1,67 +1,71 @@ -def ati(array): - """ati(array) -> list - Convert all the elements in the array and return them in a list. - """ +from typing import cast, Any, Dict, Iterable, Tuple, Union + +__all__ = [ + "ati", + "list_like", + "int_like", + "strtolines", + "make_unicode", + "unpack_kwargs", +] + + +def ati(array: Iterable[Any]): + """Convert all the elements in the array and return them in a list.""" return [int(i) for i in array] -def list_like(data): - """list_like(data) -> bool - Judge whether the object data is like a list or a tuple. - object data -> the data to judge - """ +def list_like(data: Any): + """Judge whether the object data is like a list or a tuple.""" return isinstance(data, (tuple, list)) -def int_like(data): - isint = False - try: - isint = isint or isinstance(data, long) - except NameError: - pass - isint = isint or isinstance(data, int) - return isint +def int_like(data: Any): + """Judge whether the object data is like a int.""" + return isinstance(data, int) -def strtolines(str): - lines = str.split('\n') +def strtolines(string: str): + """ + Split the string by the newline character, remove trailing spaces from each line, + and remove any blank lines at the end of the the string. + """ + lines = string.split("\n") for i in range(len(lines)): lines[i] = lines[i].rstrip() - while len(lines) > 0 and len(lines[len(lines) - 1]) == 0: - del lines[len(lines) - 1] + while len(lines) > 0 and len(lines[-1]) == 0: + lines.pop() return lines -def make_unicode(data): +def make_unicode(data: Any): + """Convert the data to a string.""" return str(data) -def unpack_kwargs(funcname, kwargs, arg_pattern): +def unpack_kwargs( + funcname: str, + kwargs: Dict[str, Any], + arg_pattern: Iterable[Union[str, Tuple[str, Any]]], +): + """Parse the keyword arguments.""" rv = {} kwargs = kwargs.copy() for tp in arg_pattern: if list_like(tp): - k, v = tp - rv[k] = kwargs.get(k, v) - try: - del kwargs[k] - except KeyError: - pass + k, v = cast(Tuple[str, Any], tp) + rv[k] = kwargs.pop(k, v) else: - error = False + tp = cast(str, tp) try: - rv[tp] = kwargs[tp] - del kwargs[tp] - except KeyError as e: - error = True - if error: + rv[tp] = kwargs.pop(tp) + except KeyError: raise TypeError( - '{}() missing 1 required keyword-only argument: \'{}\''. - format(funcname, tp)) + f"{funcname}() missing 1 required keyword-only argument: '{tp}'" + ) if kwargs: raise TypeError( - '{}() got an unexpected keyword argument \'{}\''.format( - funcname, - next(iter(kwargs.items()))[0])) + f"{funcname}() got an unexpected keyword argument '{next(iter(kwargs.items()))[0]}'" + ) return rv From 37309781de3dd1cd201e7b566ab159a3ff74d89c Mon Sep 17 00:00:00 2001 From: weilycoder Date: Mon, 16 Dec 2024 22:44:15 +0800 Subject: [PATCH 2/5] Support generating a random seed from the command-line arguments --- cyaron/utils.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/cyaron/utils.py b/cyaron/utils.py index e61ecfb..78d195b 100644 --- a/cyaron/utils.py +++ b/cyaron/utils.py @@ -1,4 +1,6 @@ -from typing import cast, Any, Dict, Iterable, Tuple, Union +import sys +import random +from typing import List, Optional, cast, Any, Dict, Iterable, Tuple, Union __all__ = [ "ati", @@ -69,3 +71,24 @@ def unpack_kwargs( f"{funcname}() got an unexpected keyword argument '{next(iter(kwargs.items()))[0]}'" ) return rv + + +def get_seed_from_argv(argv: Optional[List[str]] = None): + """ + Calculate a random seed from the command-line arguments, + referencing the implementation of `testlib.h`, but with differing behavior. + + https://github.com/MikeMirzayanov/testlib/blob/9ecb11126c16caeda2ba375e0084b3ddd03d4ace/testlib.h#L800 + """ + seed = 3905348978240129619 + for s in sys.argv[1:] if argv is None else argv: + for c in s: + seed = seed * 0x5DEECE66D + ord(c) + 0xB + seed &= 0xFFFFFFFFFFFF + seed += 0x88A12C38 + return seed & 0xFFFFFFFFFFFF + + +def set_seed_from_argv(argv: Optional[List[str]] = None, version: int = 2): + """Set the random seed from the command-line arguments.""" + random.seed(get_seed_from_argv(argv), version) From 613c88fc05cb05a7a9be4db4dfb57c534986c698 Mon Sep 17 00:00:00 2001 From: weilycoder Date: Mon, 16 Dec 2024 22:57:11 +0800 Subject: [PATCH 3/5] Add the functions __all__ --- cyaron/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cyaron/utils.py b/cyaron/utils.py index 78d195b..1d5361b 100644 --- a/cyaron/utils.py +++ b/cyaron/utils.py @@ -9,6 +9,8 @@ "strtolines", "make_unicode", "unpack_kwargs", + "get_seed_from_argv", + "set_seed_from_argv" ] From ee0bdc327c2bc36408e801738c67c136f9eaa589 Mon Sep 17 00:00:00 2001 From: "Mr. Python" <2789762371@qq.com> Date: Tue, 17 Dec 2024 23:22:23 +0800 Subject: [PATCH 4/5] Fix some warnings --- cyaron/utils.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/cyaron/utils.py b/cyaron/utils.py index 1d5361b..9984da9 100644 --- a/cyaron/utils.py +++ b/cyaron/utils.py @@ -3,14 +3,8 @@ from typing import List, Optional, cast, Any, Dict, Iterable, Tuple, Union __all__ = [ - "ati", - "list_like", - "int_like", - "strtolines", - "make_unicode", - "unpack_kwargs", - "get_seed_from_argv", - "set_seed_from_argv" + "ati", "list_like", "int_like", "strtolines", "make_unicode", + "unpack_kwargs", "get_seed_from_argv", "set_seed_from_argv" ] @@ -35,7 +29,7 @@ def strtolines(string: str): and remove any blank lines at the end of the the string. """ lines = string.split("\n") - for i in range(len(lines)): + for i, _ in enumerate(lines): lines[i] = lines[i].rstrip() while len(lines) > 0 and len(lines[-1]) == 0: @@ -67,7 +61,7 @@ def unpack_kwargs( except KeyError: raise TypeError( f"{funcname}() missing 1 required keyword-only argument: '{tp}'" - ) + ) from None if kwargs: raise TypeError( f"{funcname}() got an unexpected keyword argument '{next(iter(kwargs.items()))[0]}'" From a3fa96fa725f652f02ed5f330d5d83a7f5e9f491 Mon Sep 17 00:00:00 2001 From: "Mr. Python" <2789762371@qq.com> Date: Wed, 18 Dec 2024 16:36:09 +0800 Subject: [PATCH 5/5] Use another way to process args; write tests --- cyaron/tests/__init__.py | 1 + cyaron/tests/compare_test.py | 39 ++++++++++++++++++++++++++------ cyaron/tests/general_test.py | 44 ++++++++++++++++++++++++++++++++++++ cyaron/tests/io_test.py | 2 ++ cyaron/utils.py | 29 ++++++++---------------- 5 files changed, 89 insertions(+), 26 deletions(-) diff --git a/cyaron/tests/__init__.py b/cyaron/tests/__init__.py index 328a930..6fcff5c 100644 --- a/cyaron/tests/__init__.py +++ b/cyaron/tests/__init__.py @@ -5,3 +5,4 @@ from .compare_test import TestCompare from .graph_test import TestGraph from .vector_test import TestVector +from .general_test import TestGeneral diff --git a/cyaron/tests/compare_test.py b/cyaron/tests/compare_test.py index f820a9d..883845b 100644 --- a/cyaron/tests/compare_test.py +++ b/cyaron/tests/compare_test.py @@ -14,10 +14,12 @@ class TestCompare(unittest.TestCase): def setUp(self): + self.original_directory = os.getcwd() self.temp_directory = tempfile.mkdtemp() os.chdir(self.temp_directory) def tearDown(self): + os.chdir(self.original_directory) try: shutil.rmtree(self.temp_directory) except: @@ -60,7 +62,10 @@ def test_noipstyle_incorrect(self): self.assertTrue(False) result = out.getvalue().strip() - self.assertEqual(result, "test_another_incorrect.out: !!!INCORRECT!!! On line 2 column 7, read 4, expected 3.") + self.assertEqual( + result, + "test_another_incorrect.out: !!!INCORRECT!!! On line 2 column 7, read 4, expected 3." + ) def test_fulltext_program(self): with open("correct.py", "w") as f: @@ -77,14 +82,24 @@ def test_fulltext_program(self): try: with captured_output() as (out, err): - Compare.program("python correct.py", "python incorrect.py", std=io, input=io, grader="FullText") + Compare.program("python correct.py", + "python incorrect.py", + std=io, + input=io, + grader="FullText") except CompareMismatch as e: self.assertEqual(e.name, 'python incorrect.py') e = e.mismatch self.assertEqual(e.content, '2\n') self.assertEqual(e.std, '1\n') - self.assertEqual(e.content_hash, '53c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c3') - self.assertEqual(e.std_hash, '4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865') + self.assertEqual( + e.content_hash, + '53c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c3' + ) + self.assertEqual( + e.std_hash, + '4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865' + ) else: self.assertTrue(False) @@ -106,7 +121,10 @@ def test_file_input(self): io.input_writeln("233") with captured_output() as (out, err): - Compare.program("python correct.py", std_program="python std.py", input=io, grader="NOIPStyle") + Compare.program("python correct.py", + std_program="python std.py", + input=io, + grader="NOIPStyle") result = out.getvalue().strip() correct_out = 'python correct.py: Correct' @@ -120,7 +138,11 @@ def test_concurrent(self): with open('std.py', 'w') as f: f.write('print({})'.format(16)) with IO() as test: - Compare.program(*[(sys.executable, program) for program in programs], std_program=(sys.executable, 'std.py'), max_workers=None, input=test) + Compare.program(*[(sys.executable, program) + for program in programs], + std_program=(sys.executable, 'std.py'), + max_workers=None, + input=test) ios = [IO() for i in range(16)] try: @@ -137,7 +159,10 @@ def test_timeout(self): if sys.version_info >= (3, 3): with IO() as test: try: - Compare.program(((sys.executable, '-c', '__import__(\'time\').sleep(10)'), 1), std=test, input=test) + Compare.program(((sys.executable, '-c', + '__import__(\'time\').sleep(10)'), 1), + std=test, + input=test) except subprocess.TimeoutExpired: pass else: diff --git a/cyaron/tests/general_test.py b/cyaron/tests/general_test.py index e69de29..69f7a7f 100644 --- a/cyaron/tests/general_test.py +++ b/cyaron/tests/general_test.py @@ -0,0 +1,44 @@ +import subprocess +import unittest +import os +import tempfile +import shutil +import sys + + +class TestGeneral(unittest.TestCase): + + def setUp(self): + self.original_directory = os.getcwd() + self.temp_directory = tempfile.mkdtemp() + os.chdir(self.temp_directory) + + def tearDown(self): + os.chdir(self.original_directory) + try: + shutil.rmtree(self.temp_directory) + except: + pass + + def test_randseed_arg(self): + with open("test_randseed.py", 'w', encoding='utf-8') as f: + f.write("import cyaron as c\n" + "c.process_args()\n" + "for i in range(10):\n" + " print(c.randint(1,1000000000),end=' ')\n") + + env = os.environ.copy() + env['PYTHONPATH'] = self.original_directory + os.pathsep + env.get( + 'PYTHONPATH', '') + result = subprocess.run([ + sys.executable, 'test_randseed.py', + '--randseed=pinkrabbit147154220' + ], + env=env, + stdout=subprocess.PIPE, + universal_newlines=True, + check=True) + self.assertEqual( + result.stdout, + "243842479 490459912 810766286 646030451 191412261 929378523 273000814 982402032 436668773 957169453 " + ) diff --git a/cyaron/tests/io_test.py b/cyaron/tests/io_test.py index 42cb996..f722e87 100644 --- a/cyaron/tests/io_test.py +++ b/cyaron/tests/io_test.py @@ -10,10 +10,12 @@ class TestIO(unittest.TestCase): def setUp(self): + self.original_directory = os.getcwd() self.temp_directory = tempfile.mkdtemp() os.chdir(self.temp_directory) def tearDown(self): + os.chdir(self.original_directory) try: shutil.rmtree(self.temp_directory) except: diff --git a/cyaron/utils.py b/cyaron/utils.py index 9984da9..4ef5ad8 100644 --- a/cyaron/utils.py +++ b/cyaron/utils.py @@ -1,10 +1,11 @@ +"""Some utility functions.""" import sys import random -from typing import List, Optional, cast, Any, Dict, Iterable, Tuple, Union +from typing import cast, Any, Dict, Iterable, Tuple, Union __all__ = [ "ati", "list_like", "int_like", "strtolines", "make_unicode", - "unpack_kwargs", "get_seed_from_argv", "set_seed_from_argv" + "unpack_kwargs", "process_args" ] @@ -69,22 +70,12 @@ def unpack_kwargs( return rv -def get_seed_from_argv(argv: Optional[List[str]] = None): +def process_args(): """ - Calculate a random seed from the command-line arguments, - referencing the implementation of `testlib.h`, but with differing behavior. - - https://github.com/MikeMirzayanov/testlib/blob/9ecb11126c16caeda2ba375e0084b3ddd03d4ace/testlib.h#L800 + Process the command line arguments. + Now we support: + - randseed: set the random seed """ - seed = 3905348978240129619 - for s in sys.argv[1:] if argv is None else argv: - for c in s: - seed = seed * 0x5DEECE66D + ord(c) + 0xB - seed &= 0xFFFFFFFFFFFF - seed += 0x88A12C38 - return seed & 0xFFFFFFFFFFFF - - -def set_seed_from_argv(argv: Optional[List[str]] = None, version: int = 2): - """Set the random seed from the command-line arguments.""" - random.seed(get_seed_from_argv(argv), version) + for s in sys.argv: + if s.startswith("--randseed="): + random.seed(s.split("=")[1])