From 6778ee9537c33fbc1bfdc32123b3fe2e0ce09dd4 Mon Sep 17 00:00:00 2001 From: patrick thornton Date: Wed, 31 Jul 2024 10:32:27 -0400 Subject: [PATCH 1/2] bash env packing --- check50/_api.py | 10 ++++++-- check50/c.py | 3 ++- setup.py | 2 +- tests/c_tests.py | 51 +++++++++++++++++++++++----------------- tests/test_files/blank.c | 0 tests/test_files/cash.c | 21 +++++++++++++++++ tests/test_files/foo.c | 1 + tests/test_files/hello.c | 5 ++++ tests/test_files/leak.c | 9 +++++++ 9 files changed, 76 insertions(+), 26 deletions(-) create mode 100644 tests/test_files/blank.c create mode 100644 tests/test_files/cash.c create mode 100644 tests/test_files/foo.c create mode 100644 tests/test_files/hello.c create mode 100644 tests/test_files/leak.c diff --git a/check50/_api.py b/check50/_api.py index bb7f2e01..be757c76 100644 --- a/check50/_api.py +++ b/check50/_api.py @@ -161,10 +161,16 @@ def __init__(self, command, env={}): full_env = os.environ.copy() full_env.update(env) + # load in env into bash manually via export commands + bash_command = "" + for key in full_env: + bash_command += "export {}={} && ".format(shlex.quote(key), shlex.quote(full_env[key])) + bash_command += command + # Workaround for OSX pexpect bug http://pexpect.readthedocs.io/en/stable/commonissues.html#truncated-output-just-before-child-exits # Workaround from https://github.com/pexpect/pexpect/issues/373 - command = "bash -c {}".format(shlex.quote(command)) - self.process = pexpect.spawn(command, encoding="utf-8", echo=False, env=full_env) + command = "bash -c {}".format(shlex.quote(bash_command)) + self.process = pexpect.spawn(command, encoding="utf-8", echo=False) def stdin(self, line, str_line=None, prompt=True, timeout=3): """ diff --git a/check50/c.py b/check50/c.py index d38078f3..021d1779 100644 --- a/check50/c.py +++ b/check50/c.py @@ -55,7 +55,8 @@ def compile(*files, exe_name=None, cc=CC, max_log_lines=50, **cflags): out_flag = f" -o {exe_name} " if exe_name is not None else " " - process = run(f"{cc} {files}{out_flag}{flags}") + proc_str = f"{cc} {files}{out_flag}{flags}" + process = run(proc_str) # Strip out ANSI codes stdout = re.sub(r"\x1B\[[0-?]*[ -/]*[@-~]", "", process.stdout()) diff --git a/setup.py b/setup.py index 550225fb..b82a0c91 100644 --- a/setup.py +++ b/setup.py @@ -30,6 +30,6 @@ "console_scripts": ["check50=check50.__main__:main"] }, url="https://github.com/cs50/check50", - version="3.3.11", + version="3.3.12", include_package_data=True ) diff --git a/tests/c_tests.py b/tests/c_tests.py index 6a3e02cd..a98ee45f 100644 --- a/tests/c_tests.py +++ b/tests/c_tests.py @@ -15,49 +15,63 @@ CHECKS_DIRECTORY = pathlib.Path(__file__).absolute().parent / "checks" class Base(unittest.TestCase): + starting_dir = os.getcwd() + def setUp(self): if not CLANG_INSTALLED: raise unittest.SkipTest("clang not installed") - if not VALGRIND_INSTALLED: - raise unittest.SkipTest("valgrind not installed") + + # ensures each test starts from same cwd; + # 'chdir' would otherwise persist between tests + os.chdir(self.starting_dir) + + # write c test files to temp directory + test_files = {} + os.chdir("test_files") + for file in os.listdir(): + with open(file) as f: + test_files[file] = f.read() self.working_directory = tempfile.TemporaryDirectory() os.chdir(self.working_directory.name) + for file in test_files: + with open(file, "w") as f: + f.write(test_files[file]) + def tearDown(self): self.working_directory.cleanup() class TestCompile(Base): def test_compile_incorrect(self): - open("blank.c", "w").close() - with self.assertRaises(check50.Failure): check50.c.compile("blank.c") def test_compile_hello_world(self): - with open("hello.c", "w") as f: - src = '#include \n'\ - 'int main() {\n'\ - ' printf("hello, world!\\n");\n'\ - '}' - f.write(src) - check50.c.compile("hello.c") - self.assertTrue(os.path.isfile("hello")) + +class TestRun(Base): + def test_stdout_hello_world(self): + check50.c.compile("hello.c") check50.run("./hello").stdout("hello, world!", regex=False) + def test_stdin_cash(self): + check50.c.compile("cash.c", lcs50=True) + check50.run("./cash").stdin("42", prompt=True).stdout("5").exit() + class TestValgrind(Base): def setUp(self): super().setUp() + + # valgrind installation check moved to here from Base() + if not VALGRIND_INSTALLED: + raise unittest.SkipTest("valgrind not installed") if not (sys.platform == "linux" or sys.platform == "linux2"): raise unittest.SkipTest("skipping valgrind checks under anything other than Linux due to false positives") def test_no_leak(self): check50.internal.check_running = True - with open("foo.c", "w") as f: - src = 'int main() {}' - f.write(src) check50.c.compile("foo.c") with check50.internal.register: @@ -66,13 +80,6 @@ def test_no_leak(self): def test_leak(self): check50.internal.check_running = True - with open("leak.c", "w") as f: - src = '#include \n'\ - 'void leak() {malloc(sizeof(int));}\n'\ - 'int main() {\n'\ - ' leak();\n'\ - '}' - f.write(src) check50.c.compile("leak.c") with self.assertRaises(check50.Failure): diff --git a/tests/test_files/blank.c b/tests/test_files/blank.c new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_files/cash.c b/tests/test_files/cash.c new file mode 100644 index 00000000..f8ed03fd --- /dev/null +++ b/tests/test_files/cash.c @@ -0,0 +1,21 @@ +#include +#include + +int main(void) { + int cents; + do { + cents = get_int("Cents? "); + } + while (cents < 0); + + const int n_denominations = 4; + int denominations[n_denominations] = {1, 5, 10, 25}; + + int coins = 0; + for (int i = n_denominations - 1; i >= 0; i--) { + coins += cents / denominations[i]; + cents %= denominations[i]; + } + + printf("%i\n", coins); +} diff --git a/tests/test_files/foo.c b/tests/test_files/foo.c new file mode 100644 index 00000000..237c8ce1 --- /dev/null +++ b/tests/test_files/foo.c @@ -0,0 +1 @@ +int main() {} diff --git a/tests/test_files/hello.c b/tests/test_files/hello.c new file mode 100644 index 00000000..a262b3fa --- /dev/null +++ b/tests/test_files/hello.c @@ -0,0 +1,5 @@ +#include + +int main() { + printf("hello, world!\n"); +} diff --git a/tests/test_files/leak.c b/tests/test_files/leak.c new file mode 100644 index 00000000..e0e29f3a --- /dev/null +++ b/tests/test_files/leak.c @@ -0,0 +1,9 @@ +#include + +void leak() { + malloc(sizeof(int)); +} + +int main() { + leak(); +} From 82cfc2083369e03cc98645547a5a371169445b43 Mon Sep 17 00:00:00 2001 From: patrick thornton Date: Sun, 17 Nov 2024 21:37:02 -0500 Subject: [PATCH 2/2] escape literals --- check50/_simple.py | 2 +- check50/regex.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/check50/_simple.py b/check50/_simple.py index 9339b052..ca4a188d 100644 --- a/check50/_simple.py +++ b/check50/_simple.py @@ -59,7 +59,7 @@ def _compile_check(name, check): if check_name[0].isdigit(): check_name = f"_{check_name}" - if not re.match("\w+", check_name): + if not re.match(r"\w+", check_name): raise CompileError( _("{} is not a valid name for a check; check names should consist only of alphanumeric characters, underscores, and spaces").format(name)) diff --git a/check50/regex.py b/check50/regex.py index 215c65a8..2b20786c 100644 --- a/check50/regex.py +++ b/check50/regex.py @@ -7,16 +7,16 @@ def decimal(number): In case of a positive number:: - (?