#!/usr/bin/env python3 """This program is used to correct the exam_1""" from abc import ABC, abstractmethod import collections import contextlib import importlib import io import itertools import re import subprocess import sys import unicodedata import combinaisons_corrigee # The score of each TODO NOTEMAX_IFMAIN = 1 NOTEMAX_TESTE = 4 NOTEMAX_RECUPERE_PARAMS = 4 NOTEMAX_RECUPERE_COMBINAISONS_2 = 3 NOTEMAX_RENVERSE = 3 NOTEMAX_RECUPERE_COMBINAISONS = 4 NOTEMAX_RECUPERE_COMBINAISONS_COMPLEX = 1 # A test result is made of # - a boolean result # - an integer score # - a str message TestResult = collections.namedtuple("TestResult", ["result", "note", "messages"]) def strip_accents(string): """Remove all accents in s""" return ''.join(c for c in unicodedata.normalize('NFD', string) if unicodedata.category(c) != 'Mn') class AbstractTest(ABC): """Abstract base class for all tests""" def __init__(self, name, note_max): self.name = name self.note_max = note_max self.result = None def run(self): """run the test and print the result Fail the test if any exception is thrown """ # pylint: disable=broad-except #try: self.result = self.run_test() #except Exception as exc: # self.result = TestResult(False, 0, ["Exception de type " # + type(exc).__name__ # + " imprévue lancée"]) print(self.name, ":") if self.result.messages: for message in self.result.messages: print(" ", message) else: print(" rien à dire") print(f" {self.result.note}/{self.note_max}") @abstractmethod def run_test(self): """run the test and return a TestResult""" pass class TestIfMain(AbstractTest): """Test that the `teste` fonction is called properly""" def __init__(self): super().__init__(name="Appel à `teste()` si programme principal", note_max=NOTEMAX_IFMAIN) def run_test(self): """Search for the `if __name__` thing""" in_if_main = False ifmain_regexp = re.compile(r"if\s*__name__\s*==\s*[\"']__main__[\"']\s*:\s*$") with open("combinaisons.py", "r") as combinations_file: for line in combinations_file: if in_if_main: if line.strip() == "teste()": return TestResult(True, NOTEMAX_IFMAIN, ()) if ifmain_regexp.match(line): in_if_main = True return TestResult(False, 0, ["appel à `teste() dans un " "`if __name__ == '__main__' pas trouvé"]) class TestTeste(AbstractTest): """Test that the `teste` fonction is correct""" def __init__(self): super().__init__(name="Implémentation de la fonction `teste()`", note_max=NOTEMAX_TESTE) def run_test(self): """Check all the prints of teste""" # Import combinaisons.py as a module # and call its teste function while # redirecting stdout to a variable combinaisons = importlib.import_module("combinaisons") old_recupere_combinaisons_2 = combinaisons.recupere_combinaisons_2 old_recupere_parametres = combinaisons.recupere_parametres old_recupere_combinaisons = combinaisons.recupere_combinaisons out = io.StringIO() with contextlib.redirect_stdout(out): sys.argv.append("input.txt") combinaisons.recupere_combinaisons_2 = combinaisons_corrigee.recupere_combinaisons_2 combinaisons.recupere_parametres = combinaisons_corrigee.recupere_parametres combinaisons.recupere_combinaisons = combinaisons_corrigee.recupere_combinaisons combinaisons.teste() out = out.getvalue() combinaisons.recupere_combinaisons_2 = old_recupere_combinaisons_2 combinaisons.recupere_parametres = old_recupere_parametres combinaisons.recupere_combinaisons = old_recupere_combinaisons lines = list(" ".join(strip_accents(l).lower().split()) for l in out.split("\n")) # We start by looking for easy lines, the hardcoded ones # 1 POINTS, we are VERY SYMPAS. note = 0 messages = [] easy_lines = ( 'resultats calcules dans ma tete pour la sequence "abcd" et k=2', 'resultats calcules par recupere_combinaisons_2("abcd")', 'resultats calcules dans ma tete pour la sequence [0, 1, 2, 3, 4] et k=3', 'resultats calcules par recupere_combinaisons([0, 1, 2, 3, 4], 3)', 'resultats calcules par recupere_combinaisons sur les parametres dans le ficher', ) easy_lines_points = 1 easy_line_points = easy_lines_points / len(easy_lines) for easy_line in easy_lines: if easy_line in lines: note += easy_line_points else: messages.append(f'"{easy_line}" non affichée correctement') # Then we look for the human answer of 2-combinaisons on 'ABCD' # 0.5 POINTS if all("".join(combin) in lines for combin in itertools.combinations("abcd", r=2)): note += 1/2 else: messages.append("Les résultats calculés dans ta tête " "pour la sequence 'ABCD' et k=2 sont incorrects") # Then we look for the human answer of 3-combinaisons on [0, 1, 2, 3, 4] # 0.5 POINTS if all("".join(str(elem) for elem in combin) in lines for combin in itertools.combinations([0, 1, 2, 3, 4], r=3)): note += 1/2 else: messages.append("Les résultats calculés dans ta tête " "pour la sequence [0, 1, 2, 3, 4] et k=3 sont incorrects") # Then we check that recupere_combinaisons_2 is properly called and its # result properly displayed # 0.666666 POINTS if all(str(combin) in lines for combin in itertools.combinations("abcd", r=2)): note += 2/3 else: messages.append("Appel et affichage du résultat de " "recupere_combinaisons_2('ABCD') incorrect") # Then we check that recupere_combinaisons is properly called and its # result properly displayed # 0.666666 POINTS if all(str(combin) in lines for combin in itertools.combinations([0, 1, 2, 3, 4], r=3)): note += 2/3 else: messages.append("Appel et affichage du résultat de " "recupere_combinaisons([0, 1, 2, 3, 4], 3) incorrect") # Then we check that recupere_parametres and recupere_combinaisons are # properly called and the result properly used and displayed # 0.666666 POINTS if all(str(combin) in lines for combin in itertools.combinations("abcdef", r=3)): note += 2/3 else: messages.append("Appel et affichage de recupere_parametres et du résultat de " "recupere_combinaisons sur les parametres incorrect") return TestResult(True, note, messages) class TestRecupereParams(AbstractTest): """Test that the `recupere_paramaetres` function is correct""" def __init__(self): super().__init__(name="Implémentation de la fonction `recupere_parametres()`", note_max=NOTEMAX_RECUPERE_PARAMS) def run_test(self): """Call the `recupere_paramaetres` function and check its result""" # Import combinaisons.py as a module # and call its recupere_parametres function combinaisons = importlib.import_module("combinaisons") try: seq, k = combinaisons.recupere_parametres("input.txt") except TypeError: seq, k = None, None seq_prof, k_prof = combinaisons_corrigee.recupere_parametres("input.txt") # Is the sequence ok # 2 POINTS messages = [] note = 0 if seq and list(seq) == list(seq_prof): note += 2 else: messages.append(f"Séquence renvoyée incorrecte : {seq}, " f"au lieu de {list(seq_prof)}") # Is the k ok # 2 POINTS if k == k_prof: note += 2 else: messages.append(f"k renvoyé incorrect : {k}, au lieu de {k_prof}") return TestResult(True, note, messages) class TestRecupereCombinaison2(AbstractTest): """Test that the `recupere_combinaisons_2` fonction is correct""" def __init__(self): super().__init__(name="Implémentation de la fonction `recupere_combinaisons_2`", note_max=NOTEMAX_RECUPERE_COMBINAISONS_2) def run_test(self): """Call the `recupere_combinaisons_2` function and check its result""" # Import combinaisons.py as a module # and call its recupere_combinaisons_2 function combinaisons = importlib.import_module("combinaisons") # Is the call on "ABCD" correct # NOTEMAX_RECUPERE_COMBINAISONS_2/2 POINTS combins = combinaisons.recupere_combinaisons_2("ABCD") combins_corrige = combinaisons_corrigee.recupere_combinaisons_2("ABCD") messages = [] note = 0 if combins and set(combins) == set(combins_corrige): note += NOTEMAX_RECUPERE_COMBINAISONS_2/2 else: messages.append(f"list renvoyée incorrecte : {combins}, au lieu de {combins_corrige}") # Is the call on [0, 1, 2, 3, 4] correct # NOTEMAX_RECUPERE_COMBINAISONS_2/2 POINTS combins = combinaisons.recupere_combinaisons_2([0, 1, 2, 3, 4]) combins_corrige = combinaisons_corrigee.recupere_combinaisons_2([0, 1, 2, 3, 4]) if combins and set(combins) == set(combins_corrige): note += NOTEMAX_RECUPERE_COMBINAISONS_2/2 else: messages.append(f"list renvoyée incorrecte : {combins}, au lieu de {combins_corrige}") return TestResult(True, note, messages) class TestRenverse(AbstractTest): """Test that the `renverse` fonction is correct""" def __init__(self): super().__init__(name="Implémentation de la fonction `renverse`", note_max=NOTEMAX_RENVERSE) def run_test(self): """Call the `recupere_combinaisons_2` function and check its result""" # Import combinaisons.py as a module combinaisons = importlib.import_module("combinaisons") # Is the call on [] correct # NOTEMAX_RENVERSE/3 POINTS renv = combinaisons.renverse([]) renv_corrige = combinaisons_corrigee.renverse([]) messages = [] note = 0 if renv == renv_corrige: note += NOTEMAX_RENVERSE/3 else: messages.append(f"list renvoyée incorrecte : {renv}, au lieu de {renv_corrige}") # Is the call on [4, 3, 2, 1, 0] correct # NOTEMAX_RENVERSE/3 POINTS renv = combinaisons.renverse([4, 3, 2, 1, 0]) renv_corrige = combinaisons_corrigee.renverse([4, 3, 2, 1, 0]) if renv == renv_corrige: note += NOTEMAX_RENVERSE/3 else: messages.append(f"list renvoyée incorrecte : {renv}, au lieu de {renv_corrige}") # Is the call on [0, 1, 2, 3, 4] correct # NOTEMAX_RENVERSE/3 POINTS renv = combinaisons.renverse([0, 1, 2, 3, 4]) renv_corrige = combinaisons_corrigee.renverse([0, 1, 2, 3, 4]) if renv == renv_corrige: note += NOTEMAX_RENVERSE/3 else: messages.append(f"list renvoyée incorrecte : {renv}, au lieu de {renv_corrige}") return TestResult(True, note, messages) class TestRecupereCombinaison(AbstractTest): """Test that the `recupere_combinaisons_2` fonction is correct""" def __init__(self): super().__init__(name="Implémentation de la fonction `recupere_combinaisons`", note_max=NOTEMAX_RECUPERE_COMBINAISONS) def run_test(self): """Call the `recupere_combinaisons_2` function and check its result""" # Import combinaisons.py as a module combinaisons = importlib.import_module("combinaisons") # Is the call on "ABCD" and k=2 correct # NOTEMAX_RECUPERE_COMBINAISONS/2 POINTS combins = combinaisons.recupere_combinaisons("ABCD", 2) combins_corrige = combinaisons_corrigee.recupere_combinaisons("ABCD", 2) messages = [] note = 0 if combins and set(combins) == set(combins_corrige): note += NOTEMAX_RECUPERE_COMBINAISONS/2 else: messages.append(f"list renvoyée incorrecte : {combins}, au lieu de {combins_corrige}") # Is the call on [0, 1, 2, 3, 4] correct # NOTEMAX_RECUPERE_COMBINAISONS_2/2 POINTS combins = combinaisons.recupere_combinaisons([0, 1, 2, 3, 4], 3) combins_corrige = combinaisons_corrigee.recupere_combinaisons([0, 1, 2, 3, 4], 3) if combins and set(combins) == set(combins_corrige): note += NOTEMAX_RECUPERE_COMBINAISONS/2 else: messages.append(f"list renvoyée incorrecte : {combins}, au lieu de {combins_corrige}") return TestResult(True, note, messages) class TestComplexiteRecupereCombinaison(AbstractTest): """Test nothing.""" def __init__(self): super().__init__(name="Complexité de la fonction `recupere_combinaisons`", note_max=NOTEMAX_RECUPERE_COMBINAISONS_COMPLEX) def run_test(self): """Do nothing""" return TestResult(True, NOTEMAX_RECUPERE_COMBINAISONS_COMPLEX, ("c'est pas évident à tester, donc tu as juste " "quelque soit ta réponse, sympa non ?",)) class TestPylint(AbstractTest): """Test that pylint does not complains too much on the code.""" def __init__(self): super().__init__(name="Que dit pylint sur ton fichier `combinaisons.py` ?", note_max=-2) def run_test(self): """Run pylint on combinaisons.py""" cmd = ["pylint", "combinaisons.py"] subprocess_result = subprocess.run(cmd, stdout=subprocess.PIPE, check=False) score_regexp = re.compile(r".*Your code has been rated at (.*)/10 ") score = None for line in subprocess_result.stdout.decode().split("\n"): score_match = score_regexp.search(line.strip()) if score_match: score = float(score_match.group(1)) if score: if score < 5: note = -2 message = ("score de pylint compris entre 0 et 5 ---> -2",) elif 5 <= score < 8: note = -1 message = ("score de pylint compris entre 5 et 8 ---> -1",) else: note = 0 message = ("score de pylint compris entre 8 et 10 ---> pas de malus",) else: note = 0 message = ("t'as de la chance, on a pas réussi à avoir ton score au près de pylint !",) return TestResult(True, note, message) def correct(): """run all the tests""" tests = ( TestIfMain(), TestTeste(), TestRecupereParams(), TestRecupereCombinaison2(), TestRenverse(), TestRecupereCombinaison(), TestComplexiteRecupereCombinaison(), TestPylint() ) nb_ok = 0 note = 0 for test in tests: test.run() nb_ok += test.result.result note += test.result.note print() print(f"*** Note globale = {note if not float(note).is_integer else int(note)}/20 ***") if __name__ == "__main__": correct()