diff options
| -rw-r--r-- | README.md | 6 | ||||
| -rwxr-xr-x | main.py | 215 | ||||
| -rw-r--r-- | suites.py | 78 | ||||
| -rw-r--r-- | utils.py | 151 |
4 files changed, 251 insertions, 199 deletions
@@ -4,8 +4,10 @@ Test for the minishell project of school 42. # Usage -`> ./main.py --help` -`> ./main.py` +The default path to your project is `..` but you can change it the the [configuration](config.py). + +* `> ./main.py --help` +* `> ./main.py` ## Test compatibility @@ -1,200 +1,20 @@ #!/usr/bin/python3 -import os import sys -import subprocess -import shutil import argparse +import utils import config - -COLOR_RED = "\033[32m" -COLOR_GREEN = "\033[31m" -COLOR_CLOSE = "\033[0m" - -def green(s: str) -> str: - return COLOR_RED + s + COLOR_CLOSE - -def red(s: str) -> str: - return COLOR_GREEN + s + COLOR_CLOSE - -def expected_line(color: bool) -> str: - s = "----------------------------------------EXPECTED--------------------------------" - return COLOR_GREEN + s + COLOR_CLOSE if color else s - -def actual_line(color: bool) -> str: - s = "----------------------------------------ACTUAL----------------------------------" - return COLOR_RED + s + COLOR_CLOSE if color else s - - -def diff_file(file_name: str, expected: str, actual: str, color: bool = False) -> str: - return """\ -FILE {} -{} -{}\ -{} -{}\ -""".format(file_name, expected_line(color), expected, actual_line(color), - "FROM TEST: File not created\n" if actual is None else actual) - -def diff_output(cmd: str, expected: str, actual: str, color: bool = False) -> str: - return """\ -WITH: {} -STATUS: TODO -{} -{}\ -{} -{}\ -""".format(cmd, expected_line(color), expected, actual_line(color), actual) - -def diff(cmd: str, expected: str, actual: str, - files: [str], expected_files: [str], actual_files: [str], - color: bool = False) -> str: - s = "" - if expected != actual: - s += diff_output(cmd, expected, actual, color) - - for file_name, e, a in zip(files, expected_files, actual_files): - if a != e: - s += "-" * 80 + "\n" + diff_file(file_name, e, a, color) - return s - - -def put_result(passed: bool, cmd: str): - if len(cmd) > 70: - cmd = cmd[:67] + "..." - - if passed: - print(green("{:74} [PASS]".format(cmd))) - else: - print(red("{:74} [FAIL]".format(cmd))) - - - -def run_sandboxed(program: str, cmd: str, setup: str = None, files: [str] = []) -> str: - """ run the command in a sandbox environment, return the output (stdout and stderr) of it """ - - try: - os.mkdir(config.SANDBOX_PATH) - except OSError: - pass - if setup is not None: - try: - setup_status = subprocess.run(setup, shell=True, cwd=config.SANDBOX_PATH, check=True, text=True, capture_output=True) - except subprocess.CalledProcessError as e: - print("Error: `{}` setup command failed for `{}`\n\twith '{}'".format(setup, cmd, e.stderr.strip())) - sys.exit(1) - - # TODO: add timeout - # https://docs.python.org/3/library/subprocess.html#using-the-subprocess-module - process_status = subprocess.run([program, "-c", cmd], - text=True, - stderr=subprocess.STDOUT, - stdout=subprocess.PIPE, - cwd=config.SANDBOX_PATH) - output = process_status.stdout - - output_files = [] - for file_name in files: - try: - with open(os.path.join(config.SANDBOX_PATH, file_name), "r") as f: - output_files.append(f.read()) - except FileNotFoundError as e: - output_files.append(None) - - shutil.rmtree(config.SANDBOX_PATH) - return (output, output_files) - -status = 0 -ignored_suites = [] -suites = {} -current_suite = "default" -verbose = False - -def check(expected: str, actual: str, expected_files: [str], actual_files: [str]) -> bool: - return actual == expected and all([a == e for a, e in zip(actual_files, expected_files)]) - -def test(cmd: str, setup: str = None, files: [str] = []): - """ get expected and actual strings, compare them and push them to the suites result """ - - (expected, expected_files) = run_sandboxed(config.REFERENCE_SHELL_PATH, cmd, setup, files) - (actual, actual_files) = run_sandboxed(config.MINISHELL_PATH, cmd, setup, files) - - passed = check(expected, actual, expected_files, actual_files) - global status - if not passed: - status = 1 - if not verbose: - put_result(actual == expected, cmd) - elif not passed: - print(diff(cmd, expected, actual, files, expected_files, actual_files, color=True)) - - if suites.get(current_suite) is None: - suites[current_suite] = [] - suites[current_suite].append((cmd, expected, actual, files, expected_files, actual_files)) - -available_suites = [] - -def suite(origin): - """ decorator for a suite function (fmt: suite_[name]) - update the current_suite global and print it before the suite execution - """ - - name = origin.__name__[len("suite_"):] - available_suites.append(name) - def f(): - if name in ignored_suites: - return - global current_suite - current_suite = name.upper() - print("{} {:#<41}".format("#" * 39, current_suite + " ")) - origin() - print() - return f - -@suite -def suite_quote(): - test("'echo' 'bonjour'") - test("'echo' 'je' 'suis' 'charles'") - - test('"echo" "bonjour"') - test('"echo" "je" "suis" "charles"') - - test('echo je\'suis\'"charles"') - test('echo "je"suis\'charles\'') - test('echo \'je\'"suis"charles') - - test('echo "\\""') - test('echo "\\$"') - test('echo "\\\\"') - -@suite -def suite_echo(): - test("echo bonjour") - test("echo lalalala lalalalal alalalalal alalalala") - test("echo lalalala lalalalal alalalalal alalalala") - test("echo " + config.LOREM) - - test("echo -n bonjour") - test("echo -n lalalala lalalalal alalalalal alalalala") - test("echo -n lalalala lalalalal alalalalal alalalala") - test("echo -n " + config.LOREM) - -@suite -def suite_redirection(): - test("echo bonjour > test", setup="", files=["test"]) - test("echo > test bonjour", setup="", files=["test"]) - test("> test echo bonjour", setup="", files=["test"]) - +import suites def main(): - suite_quote() - suite_echo() - suite_redirection() + suites.suite_quote() + suites.suite_echo() + suites.suite_redirection() if __name__ == "__main__": - available_suites_str = ", ".join(available_suites) + available_suites_str = ", ".join(utils.available_suites) parser = argparse.ArgumentParser(description="Minishell test", epilog="Make sure read README.md") parser.add_argument("-v", "--verbose", action="store_true", @@ -202,32 +22,33 @@ if __name__ == "__main__": parser.add_argument("suites", nargs='*', metavar="suite", help="test suites to run (available suites: {})".format(available_suites_str)) args = parser.parse_args() - verbose = args.verbose + utils.verbose = args.verbose # check if selected suite is valid for s in args.suites: - if s not in available_suites: - print("{}: error: `{}` isn't a valid suite, the available suites are {}" + if s not in utils.available_suites: + print("{}: error: `{}` isn't a valid suite, the available runned_suites are {}" .format(sys.argv[0], s, available_suites_str)) sys.exit(1) - # update ignored suites according to the selected ones (if no suite is selected, all are run) + # update ignored runned_suites according to the selected ones (if no suite is selected, all are run) if len(args.suites) != 0: - for available in available_suites: + for available in utils.available_suites: if available not in args.suites: - ignored_suites.append(available) + utils.ignored_suites.append(available) main() log_file = open(config.LOG_PATH, "w") print("Summary:") - for suite_name, results in suites.items(): + for suite_name, results in utils.runned_suites.items(): print("{:15} ".format(suite_name), end="") pass_total = 0 for (cmd, expected, actual, files, expected_files, actual_files) in results: - if check(expected, actual, expected_files, actual_files): + if utils.check(expected, actual, expected_files, actual_files): pass_total += 1 else: - log_file.write(diff(cmd, expected, actual, files, expected_files, actual_files)) + log_file.write(utils.diff(cmd, expected, actual, files, expected_files, actual_files)) log_file.write("=" * 80 + "\n\n") - print(green("{:2} [PASS]".format(pass_total)), end=" ") - print(red("{:2} [FAIL]".format(len(results) - pass_total))) + print(utils.green("{:2} [PASS]".format(pass_total)), end=" ") + print(utils.red("{:2} [FAIL]".format(len(results) - pass_total))) + sys.exit(utils.status) diff --git a/suites.py b/suites.py new file mode 100644 index 0000000..941ab6c --- /dev/null +++ b/suites.py @@ -0,0 +1,78 @@ +import config +from utils import suite, test + +@suite +def suite_quote(): + test("'echo' 'bonjour'") + test("'echo' 'je' 'suis' 'charles'") + + test('"echo" "bonjour"') + test('"echo" "je" "suis" "charles"') + + test('echo je\'suis\'"charles"') + test('echo "je"suis\'charles\'') + test('echo \'je\'"suis"charles') + + test('echo "\\""') + test('echo "\\$"') + test('echo "\\\\"') + +@suite +def suite_echo(): + test("echo bonjour") + test("echo lalalala lalalalal alalalalal alalalala") + test("echo lalalala lalalalal alalalalal alalalala") + test("echo " + config.LOREM) + + test("echo -n bonjour") + test("echo -n lalalala lalalalal alalalalal alalalala") + test("echo -n lalalala lalalalal alalalalal alalalala") + test("echo -n " + config.LOREM) + +@suite +def suite_redirection(): + test("echo bonjour > test", setup="", files=["test"]) + test("echo > test bonjour", setup="", files=["test"]) + test("> test echo bonjour", setup="", files=["test"]) + test("echo bonjour >> test", setup="", files=["test"]) + test("echo >> test bonjour", setup="", files=["test"]) + test(">> test echo bonjour", setup="", files=["test"]) + test("cat < test", setup="echo bonjour > test") + test("echo bonjour > test", setup="", files=["test"]) + + test("echo > test'sticked' bonjour", setup="", files=["teststicked"]) + test("> test'sticked' echo bonjour", setup="", files=["teststicked"]) + test("echo bonjour >> test'sticked'", setup="", files=["teststicked"]) + test("echo >> test'sticked' bonjour", setup="", files=["teststicked"]) + test(">> test'sticked' echo bonjour", setup="", files=["teststicked"]) + test("cat < test'sticked'", setup="echo bonjour > test'sticked'") + test("< test'sticked' cat", setup="echo bonjour > test'sticked'") + + test("echo > test\"sticked\" bonjour", setup="", files=["teststicked"]) + test("> test\"sticked\" echo bonjour", setup="", files=["teststicked"]) + test("echo bonjour >> test\"sticked\"", setup="", files=["teststicked"]) + test("echo >> test\"sticked\" bonjour", setup="", files=["teststicked"]) + test(">> test\"sticked\" echo bonjour", setup="", files=["teststicked"]) + test("cat < test\"sticked\"", setup="echo bonjour > test\"sticked\"") + test("< test\"sticked\" cat", setup="echo bonjour > test\"sticked\"") + + test("echo > test'yo'\"sticked\" bonjour", setup="", files=["testyosticked"]) + test("> test'yo'\"sticked\" echo bonjour", setup="", files=["testyosticked"]) + test("echo bonjour >> test'yo'\"sticked\"", setup="", files=["testyosticked"]) + test("echo >> test'yo'\"sticked\" bonjour", setup="", files=["testyosticked"]) + test(">> test'yo'\"sticked\" echo bonjour", setup="", files=["testyosticked"]) + test("cat < test'yo'\"sticked\"", setup="echo bonjour > test'yo'\"sticked\"") + test("< test'yo'\"sticked\" cat", setup="echo bonjour > test'yo'\"sticked\"") + + test("echo bonjour > test > je > suis", setup="", files=["test", "je", "suis"]) + test("echo > test > je bonjour > suis", setup="", files=["test", "je", "suis"]) + test("> test echo bonjour > je > suis", setup="", files=["test", "je", "suis"]) + test("echo bonjour >> test > je >> suis", setup="", files=["test", "je", "suis"]) + test("echo >> test bonjour > je > suis", setup="", files=["test", "je", "suis"]) + test(">> test echo > je bonjour > suis", setup="", files=["test", "je", "suis"]) + test("cat < test < je", setup="echo bonjour > test; echo salut > je") + + test("echo bonjour>test>je>suis", setup="", files=["test", "je", "suis"]) + test(">test echo bonjour>je>suis", setup="", files=["test", "je", "suis"]) + test("echo bonjour>>test>je>>suis", setup="", files=["test", "je", "suis"]) + test("cat<test<je", setup="echo bonjour > test; echo salut > je") diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..fac97db --- /dev/null +++ b/utils.py @@ -0,0 +1,151 @@ +import os +import sys +import subprocess +import shutil + +import config + +COLOR_RED = "\033[32m" +COLOR_GREEN = "\033[31m" +COLOR_CLOSE = "\033[0m" + +def green(s: str) -> str: + return COLOR_RED + s + COLOR_CLOSE + +def red(s: str) -> str: + return COLOR_GREEN + s + COLOR_CLOSE + +def expected_line(color: bool) -> str: + s = "----------------------------------------EXPECTED--------------------------------" + return COLOR_GREEN + s + COLOR_CLOSE if color else s + +def actual_line(color: bool) -> str: + s = "----------------------------------------ACTUAL----------------------------------" + return COLOR_RED + s + COLOR_CLOSE if color else s + + +def diff_file(file_name: str, expected: str, actual: str, color: bool = False) -> str: + return """\ +FILE {} +{} +{}\ +{} +{}\ +""".format(file_name, expected_line(color), expected, actual_line(color), + "FROM TEST: File not created\n" if actual is None else actual) + +def diff_output(cmd: str, expected: str, actual: str, color: bool = False) -> str: + return """\ +WITH: {} +STATUS: TODO +{} +{}\ +{} +{}\ +""".format(cmd, expected_line(color), expected, actual_line(color), actual) + +def diff(cmd: str, expected: str, actual: str, + files: [str], expected_files: [str], actual_files: [str], + color: bool = False) -> str: + s = "" + if expected != actual: + s += diff_output(cmd, expected, actual, color) + + for file_name, e, a in zip(files, expected_files, actual_files): + if a != e: + s += "-" * 80 + "\n" + diff_file(file_name, e, a, color) + return s + + +def put_result(passed: bool, cmd: str): + if len(cmd) > 70: + cmd = cmd[:67] + "..." + + if passed: + print(green("{:74} [PASS]".format(cmd))) + else: + print(red("{:74} [FAIL]".format(cmd))) + + + +def run_sandboxed(program: str, cmd: str, setup: str = None, files: [str] = []) -> str: + """ run the command in a sandbox environment, return the output (stdout and stderr) of it """ + + try: + os.mkdir(config.SANDBOX_PATH) + except OSError: + pass + if setup is not None: + try: + setup_status = subprocess.run(setup, shell=True, cwd=config.SANDBOX_PATH, check=True, text=True, capture_output=True) + except subprocess.CalledProcessError as e: + print("Error: `{}` setup command failed for `{}`\n\twith '{}'".format(setup, cmd, e.stderr.strip())) + sys.exit(1) + + # TODO: add timeout + # https://docs.python.org/3/library/subprocess.html#using-the-subprocess-module + process_status = subprocess.run([program, "-c", cmd], + text=True, + stderr=subprocess.STDOUT, + stdout=subprocess.PIPE, + cwd=config.SANDBOX_PATH) + output = process_status.stdout + + output_files = [] + for file_name in files: + try: + with open(os.path.join(config.SANDBOX_PATH, file_name), "r") as f: + output_files.append(f.read()) + except FileNotFoundError as e: + output_files.append(None) + + shutil.rmtree(config.SANDBOX_PATH) + return (output, output_files) + +status = 0 +ignored_suites = [] +runned_suites = {} +current_suite = "default" +verbose = False + +def check(expected: str, actual: str, expected_files: [str], actual_files: [str]) -> bool: + return actual == expected and all([a == e for a, e in zip(actual_files, expected_files)]) + +def test(cmd: str, setup: str = None, files: [str] = []): + """ get expected and actual strings, compare them and push them to the suites result """ + + (expected, expected_files) = run_sandboxed(config.REFERENCE_SHELL_PATH, cmd, setup, files) + (actual, actual_files) = run_sandboxed(config.MINISHELL_PATH, cmd, setup, files) + + passed = check(expected, actual, expected_files, actual_files) + global status + if not passed: + status = 1 + if not verbose: + put_result(actual == expected, cmd) + elif not passed: + print(diff(cmd, expected, actual, files, expected_files, actual_files, color=True)) + + if runned_suites.get(current_suite) is None: + runned_suites[current_suite] = [] + runned_suites[current_suite].append((cmd, expected, actual, files, expected_files, actual_files)) + +available_suites = [] + +def suite(origin): + """ decorator for a suite function (fmt: suite_[name]) + update the current_suite global and print it before the suite execution + """ + + name = origin.__name__[len("suite_"):] + available_suites.append(name) + def f(): + if name in ignored_suites: + return + global current_suite + current_suite = name.upper() + print("{} {:#<41}".format("#" * 39, current_suite + " ")) + origin() + print() + return f + |
