diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml new file mode 100644 index 0000000..ed75c0e --- /dev/null +++ b/.github/workflows/python-tests.yml @@ -0,0 +1,29 @@ +name: Python tests + +on: + push: + branches: [ 'main' ] + pull_request: + branches: [ 'main' ] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: 'Checkout' + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.12' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + + - name: Test with pytest + run: | + pytest diff --git a/.gitignore b/.gitignore index d2e529e..8c67949 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ *.pyc __pycache__/ -.idea/ \ No newline at end of file +.idea/ + +.pytest_cache + +Projet Compilation.pdf diff --git a/README.md b/README.md index 0da5dbf..d8e45df 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ A simple tool to **analyze**, **compile**, and **run** NNP programs. +[![Python tests](https://github.com/lasercata/Compilation_project/actions/workflows/python-tests.yml/badge.svg)](https://github.com/lasercata/Compilation_project/actions/workflows/python-tests.yml) + --- ## 📁 Project Structure @@ -29,7 +31,7 @@ A simple tool to **analyze**, **compile**, and **run** NNP programs. │   ├── parser_ui.py │   └── utils.py │ -└── tests/ +└── nn_programs/ ├── nna/ │   ├── correct1.nno │   ├── ... @@ -107,9 +109,22 @@ options: ```bash ./main.py c -h -./main.py c tests/nna/correct1.nno -o correct1.obj +./main.py c nn_programs/nna/correct1.nno -o correct1.obj ./main.py r correct1.obj -./main.py r tests/nna/correct1.nno -c +./main.py r nn_programs/nna/correct1.nno -c +``` + +--- + +### Run tests +To run all the tests: +``` +python -m pytest +``` + +To have the details: +``` +python -m pytest -vv ``` --- diff --git a/tests/convert_encoding.sh b/nn_programs/convert_encoding.sh similarity index 100% rename from tests/convert_encoding.sh rename to nn_programs/convert_encoding.sh diff --git a/tests/nna/correct1.nno b/nn_programs/nna/correct1.nno similarity index 100% rename from tests/nna/correct1.nno rename to nn_programs/nna/correct1.nno diff --git a/tests/nna/correct2.nno b/nn_programs/nna/correct2.nno similarity index 100% rename from tests/nna/correct2.nno rename to nn_programs/nna/correct2.nno diff --git a/tests/nna/correct3.nno b/nn_programs/nna/correct3.nno similarity index 100% rename from tests/nna/correct3.nno rename to nn_programs/nna/correct3.nno diff --git a/tests/nna/correct4.nno b/nn_programs/nna/correct4.nno similarity index 100% rename from tests/nna/correct4.nno rename to nn_programs/nna/correct4.nno diff --git a/tests/nna/error1.nno b/nn_programs/nna/error1.nno similarity index 100% rename from tests/nna/error1.nno rename to nn_programs/nna/error1.nno diff --git a/tests/nna/error2.nno b/nn_programs/nna/error2.nno similarity index 100% rename from tests/nna/error2.nno rename to nn_programs/nna/error2.nno diff --git a/tests/nna/error3.nno b/nn_programs/nna/error3.nno similarity index 100% rename from tests/nna/error3.nno rename to nn_programs/nna/error3.nno diff --git a/tests/nna/error4.nno b/nn_programs/nna/error4.nno similarity index 100% rename from tests/nna/error4.nno rename to nn_programs/nna/error4.nno diff --git a/tests/nna/error5.nno b/nn_programs/nna/error5.nno similarity index 100% rename from tests/nna/error5.nno rename to nn_programs/nna/error5.nno diff --git a/tests/nna/error6.nno b/nn_programs/nna/error6.nno similarity index 100% rename from tests/nna/error6.nno rename to nn_programs/nna/error6.nno diff --git a/tests/nna/error7.nno b/nn_programs/nna/error7.nno similarity index 100% rename from tests/nna/error7.nno rename to nn_programs/nna/error7.nno diff --git a/tests/nna/expected/correct1.nno.expected b/nn_programs/nna/expected/correct1.nno.expected similarity index 100% rename from tests/nna/expected/correct1.nno.expected rename to nn_programs/nna/expected/correct1.nno.expected diff --git a/tests/nna/expected/correct2.nno.expected b/nn_programs/nna/expected/correct2.nno.expected similarity index 100% rename from tests/nna/expected/correct2.nno.expected rename to nn_programs/nna/expected/correct2.nno.expected diff --git a/tests/nna/expected/correct3.nno.expected b/nn_programs/nna/expected/correct3.nno.expected similarity index 100% rename from tests/nna/expected/correct3.nno.expected rename to nn_programs/nna/expected/correct3.nno.expected diff --git a/tests/nna/expected/correct3_alt.nno.expected b/nn_programs/nna/expected/correct3_alt.nno.expected similarity index 100% rename from tests/nna/expected/correct3_alt.nno.expected rename to nn_programs/nna/expected/correct3_alt.nno.expected diff --git a/tests/nna/expected/correct4.nno.expected b/nn_programs/nna/expected/correct4.nno.expected similarity index 100% rename from tests/nna/expected/correct4.nno.expected rename to nn_programs/nna/expected/correct4.nno.expected diff --git a/tests/nnp/correct1.nno b/nn_programs/nnp/correct1.nno similarity index 100% rename from tests/nnp/correct1.nno rename to nn_programs/nnp/correct1.nno diff --git a/tests/nnp/correct2.nno b/nn_programs/nnp/correct2.nno similarity index 100% rename from tests/nnp/correct2.nno rename to nn_programs/nnp/correct2.nno diff --git a/tests/nnp/correct3.nno b/nn_programs/nnp/correct3.nno similarity index 100% rename from tests/nnp/correct3.nno rename to nn_programs/nnp/correct3.nno diff --git a/tests/nnp/correct4.nno b/nn_programs/nnp/correct4.nno similarity index 100% rename from tests/nnp/correct4.nno rename to nn_programs/nnp/correct4.nno diff --git a/tests/nnp/correct5.nno b/nn_programs/nnp/correct5.nno similarity index 100% rename from tests/nnp/correct5.nno rename to nn_programs/nnp/correct5.nno diff --git a/tests/nnp/expected/correct1.nno.expected b/nn_programs/nnp/expected/correct1.nno.expected similarity index 95% rename from tests/nnp/expected/correct1.nno.expected rename to nn_programs/nnp/expected/correct1.nno.expected index 0846176..c154cc1 100644 --- a/tests/nnp/expected/correct1.nno.expected +++ b/nn_programs/nnp/expected/correct1.nno.expected @@ -37,7 +37,7 @@ tra(7) retourProc() reserver(3) reserverBloc() -traStat(3,0) +traStat(3, 0) empiler(0) get() empiler(1) @@ -49,4 +49,4 @@ affectation() empiler(1) valeurPile() put() -finProg() \ No newline at end of file +finProg() diff --git a/tests/nnp/expected/correct2.nno.expected b/nn_programs/nnp/expected/correct2.nno.expected similarity index 91% rename from tests/nnp/expected/correct2.nno.expected rename to nn_programs/nnp/expected/correct2.nno.expected index da7eafb..43a9968 100644 --- a/tests/nnp/expected/correct2.nno.expected +++ b/nn_programs/nnp/expected/correct2.nno.expected @@ -12,7 +12,7 @@ reserver(3) reserverBloc() empiler(3) empiler(4) -traStat(3,2) +traStat(3, 2) empiler(0) get() empiler(1) @@ -24,4 +24,4 @@ affectation() empiler(1) valeurPile() put() -finProg() \ No newline at end of file +finProg() diff --git a/tests/nnp/expected/correct3.nno.expected b/nn_programs/nnp/expected/correct3.nno.expected similarity index 81% rename from tests/nnp/expected/correct3.nno.expected rename to nn_programs/nnp/expected/correct3.nno.expected index 0090385..f2a4bf0 100644 --- a/tests/nnp/expected/correct3.nno.expected +++ b/nn_programs/nnp/expected/correct3.nno.expected @@ -7,6 +7,6 @@ mult() retourFonct() reserverBloc() empiler(7) -traStat(3,1) +traStat(3, 1) put() -finProg() \ No newline at end of file +finProg() diff --git a/tests/nnp/expected/correct4.nno.expected b/nn_programs/nnp/expected/correct4.nno.expected similarity index 86% rename from tests/nnp/expected/correct4.nno.expected rename to nn_programs/nnp/expected/correct4.nno.expected index 8dc15b5..2290ee2 100644 --- a/tests/nnp/expected/correct4.nno.expected +++ b/nn_programs/nnp/expected/correct4.nno.expected @@ -15,11 +15,11 @@ empilerAd(0) valeurPile() empiler(1) sous() -traStat(3,1) +traStat(3, 1) mult() retourFonct() reserverBloc() empiler(3) -traStat(3,1) +traStat(3, 1) put() -finProg() \ No newline at end of file +finProg() diff --git a/tests/nnp/expected/correct5.nno.expected b/nn_programs/nnp/expected/correct5.nno.expected similarity index 90% rename from tests/nnp/expected/correct5.nno.expected rename to nn_programs/nnp/expected/correct5.nno.expected index 48b3f16..53fe58a 100644 --- a/tests/nnp/expected/correct5.nno.expected +++ b/nn_programs/nnp/expected/correct5.nno.expected @@ -16,5 +16,5 @@ reserverBloc() empiler(2) empiler(0) valeurPile() -traStat(3,2) -finProg() \ No newline at end of file +traStat(3, 2) +finProg() diff --git a/src/anasyn.py b/src/anasyn.py index 6acd000..dad8587 100644 --- a/src/anasyn.py +++ b/src/anasyn.py @@ -970,6 +970,9 @@ def compile(self, show_ident_table: bool = False) -> str: Args: :show_ident_table: if True, prints the identifier table. + + Output: + The compiled instructions ''' try: @@ -988,54 +991,53 @@ def compile(self, show_ident_table: bool = False) -> str: ######################################################################## -def main_anasyn(file_content: str, fn_out: str, show_ident_table: bool, debug_lvl): +def main_anasyn(file_content: str, fn_out: str = '', show_ident_table: bool = False, debug_lvl=logging.INFO) -> str: ''' - TODO: Docstring for main_anasyn. + Main function for the syntax analysis, and generation of the compiled instructions. + + In: + - file_content: the content of the file (the NNP program) ; + - fn_out: the name of the potential output file. If "", prints to stdout instead ; + - show_ident_table: if true, displays the ident table ; + - debug_lvl: indicates the logging level. - - file_content : the content of the file (the NNP program) ; - - fn_out : the name of the potential output file. If "", prints to stdout instead ; - - show_ident_table : if true, displays the ident table ; - - debug_lvl : indicates the logging level. + Out: + The generated compiled instructions. + + Throws: + SyntaxError if there is a syntax error + FileNotFoundError if there is a problem to open the output file + RuntimeError if no instructions are generated ''' #---Run the analysis G = Grammar(file_content, debug_lvl) - try: - instructions_str = G.compile(show_ident_table) + instructions_str = G.compile(show_ident_table) - except SyntaxError as err: - print(f'Syntax error: {err}') - return + if instructions_str == '': + raise RuntimeError('No instruction generated!') #---Write to file / stdout #-Select file or stdout if fn_out != '': - try: - output_file = open(fn_out, 'w') - - except: - print("Error: can't open output file!") - return + output_file = open(fn_out, 'w') else: output_file = sys.stdout #-Write - if instructions_str != '': - output_file.write(instructions_str) - - if debug_lvl == logging.DEBUG: - print(f'Output to file: "{fn_out}"') + output_file.write(instructions_str) - else: - if debug_lvl == logging.DEBUG: - print('No instruction generated!') + if debug_lvl == logging.DEBUG: + print(f'Output to file: "{fn_out}"') #-Close file if fn_out != '': output_file.close() + return instructions_str + ######################################################################## diff --git a/src/parser_ui.py b/src/parser_ui.py index aa3317f..fade440 100644 --- a/src/parser_ui.py +++ b/src/parser_ui.py @@ -49,9 +49,9 @@ def __init__(self): #---Init examples = 'Examples :' examples += '\n\t./main.py c -h' - examples += '\n\t./main.py c tests/nna/correct1.nno -o correct1.obj' + examples += '\n\t./main.py c nn_programs/nna/correct1.nno -o correct1.obj' examples += '\n\t./main.py r correct1.obj' - examples += '\n\t./main.py r tests/nna/correct1.nno -c' + examples += '\n\t./main.py r nn_programs/nna/correct1.nno -c' self.parser = argparse.ArgumentParser( # prog='anasyn', @@ -190,7 +190,17 @@ def parse_compile(self, args): src_code = get_file_content(args.inputfile[0], self.parser_c) - main_anasyn(src_code, args.outputfile, args.show_ident_table, args.debug) + try: + main_anasyn(src_code, args.outputfile, args.show_ident_table, args.debug) + + except SyntaxError as err: + print(f'Syntax error: {err}') + + except FileNotFoundError: + print("Error: can't open output file!") + + except RuntimeError as err: + print(f'Error: {err}') def parse_run(self, args): '''Parse the arguments for the `compile` mode''' @@ -202,6 +212,7 @@ def parse_run(self, args): try: instructions = G.compile() print(instructions) + except SyntaxError as err: # self.parser_r.error('syntax error: ' + str(err)) print('Syntax error: ' + str(err)) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_anasyn.py b/tests/test_anasyn.py new file mode 100644 index 0000000..f50cfff --- /dev/null +++ b/tests/test_anasyn.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +##-Imports +#---General +from os import listdir + +import pytest + +#---Project +from src.anasyn import main_anasyn + + +##-Init +nna_path = 'nn_programs/nna/' +nnp_path = 'nn_programs/nnp/' + +files_nna = sorted([f for f in listdir(nna_path) if 'correct' in f]) +files_nnp = sorted([f for f in listdir(nnp_path) if 'correct' in f]) + +nna_tuples = [(nna_path + src, nna_path + 'expected/' + src + '.expected') for src in files_nna] +nnp_tuples = [(nnp_path + src, nnp_path + 'expected/' + src + '.expected') for src in files_nnp] + +error_programs = sorted([nna_path + f for f in listdir(nna_path) if 'error' in f]) + + +##-Tests +@pytest.mark.parametrize('src_fn, expected_compiled_fn', nna_tuples + nnp_tuples) +def test_correct(src_fn: str, expected_compiled_fn: str): + '''Tests to compile the correct programs''' + + with open(src_fn) as src_file: + with open(expected_compiled_fn) as compiled_file: + src = src_file.read() + compiled = compiled_file.read().strip() + + assert main_anasyn(src).strip() == compiled, f'Failed for file "{src_fn}"' + +@pytest.mark.parametrize('src_fn', error_programs) +def test_error(src_fn: str): + '''Tests to compile the programs with errors''' + + with open(src_fn) as src_file: + src = src_file.read() + + with pytest.raises(SyntaxError): + main_anasyn(src).strip() diff --git a/tests/test_vm.py b/tests/test_vm.py new file mode 100644 index 0000000..a3e7ce4 --- /dev/null +++ b/tests/test_vm.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +##-Imports +#---General +from subprocess import run + +import pytest + +#---Project +# from src.anasyn import main_anasyn + + +##-Init +test_expected_values = [ + #nn_type, nb, input, expected_output + ('nna', 1, (), '1\n2\n3\n4\n'*4), + + ('nna', 2, (), 1), + + ('nna', 3, (0,), -34), + ('nna', 3, (2,), -34), + ('nna', 3, (20,), 20*3), + ('nna', 3, (18,), ''), + + ('nna', 4, (0,), 0), + ('nna', 4, (1, 0,), 0), + ('nna', 4, (1, 1, 1, 1, 0,), 0), + ('nna', 4, (2, 0,), 1), + ('nna', 4, (2, 2, 0,), 2), + ('nna', 4, (2, 2, 2, 0,), 3), + ('nna', 4, (1, 2, 0,), 1), + ('nna', 4, (2, 1, 0,), 1), + ('nna', 4, (2, 1, 8, 0,), 2), + ('nna', 4, (2, 4, 6, 8, 10, 12, 14, 16, 0,), 8), + + ('nnp', 1, (0,), '1\n2\n3\n4\n'*4 + '>1'), # there is '>' because it is the user prompt + ('nnp', 1, (1,), '1\n2\n3\n4\n'*4 + '>2'), + ('nnp', 1, (-1,), '1\n2\n3\n4\n'*4 + '>0'), + + ('nnp', 2, (0,), '3\n4\n' + '>1'), # there is '>' because it is the user prompt + ('nnp', 2, (1,), '3\n4\n' + '>2'), + ('nnp', 2, (-1,), '3\n4\n' + '>0'), + + ('nnp', 3, (), 14), + + ('nnp', 4, (), 6), + + ('nnp', 5, (), ''), +] + + +##-Utils +def make_run_command(nn_type: str, nb: int) -> list[str]: + ''' + Crafts the command to run the given compiled file. + + In: + - nn_type: either 'nna' or 'nnp' + - nb: the number of the program (e.g 1 for correct1) + Out: + the command to run the given compiled file + ''' + + return f'python main.py r nn_programs/{nn_type}/expected/correct{nb}.nno.expected'.split(' ') + +def make_input_command(*inputs) -> list[str]: + ''' + Crafts the command to send the inputs to the main program + + In: + - *inputs: the inputs + Out: + The echo command to send the inputs + ''' + + inputs_str = '\n'.join(str(i) for i in inputs) + + return ['echo', '-e', f'"{inputs_str}"'] + + +##-Tests +@pytest.mark.parametrize('nn_type, nb, inputs, expected_output', test_expected_values) +def test_run(nn_type: str, nb: int, inputs: tuple, expected_output: str | int): + ''' + Run the test for file `nn_type`/correct`nb`.nno.expected. + + In: + - nn_type: either 'nna' or 'nnp' + - nb: the number of the program (e.g 1 for correct1) + - inputs: the input tuple for this run. If no input, set it to `()`. + - expected_output: the expected output for this run + Out: + None, or AssertionError + ''' + + if inputs == (): + command = make_run_command(nn_type, nb) + else: + command = make_input_command(*inputs) + ['|'] + make_run_command(nn_type, nb) + command = ['bash', '-c', ' '.join(command)] + + result = run(command, capture_output=True, text=True) + output = result.stdout.strip().strip('>') + + assert output == str(expected_output).strip(), 'Failed for file "{nn_type}/correct{nb}.nno.expected"'