aboutsummaryrefslogtreecommitdiff
path: root/minishell_test/suite/suite.py
blob: 842a876e0675ea980cc154f9f370a264945ecee9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# ############################################################################ #
#                                                                              #
#                                                         :::      ::::::::    #
#    suite.py                                           :+:      :+:    :+:    #
#                                                     +:+ +:+         +:+      #
#    By: charles <charles.cabergs@gmail.com>        +#+  +:+       +#+         #
#                                                 +#+#+#+#+#+   +#+            #
#    Created: 2020/07/15 18:24:29 by charles           #+#    #+#              #
#    Updated: 2021/03/06 15:57:52 by cacharle         ###   ########.fr        #
#                                                                              #
# ############################################################################ #

from typing import List, Tuple, Optional, Callable

from minishell_test.config import Config
from minishell_test.test import Test
from minishell_test import colors


class SuiteException(Exception):
    """ Base exception for suite """
    pass


class SuiteExitFirstException(SuiteException):
    pass


# class AmbiguousNameException(SuiteException):
#     def __init__(self, name: str, matches: List[str]):
#         self._name = name
#         self._matches = matches
#
#     def __str__(self) -> str:
#         return (f"Ambiguous name `{self._name}` match the following suites"
#                 f"\n\t{', '.join(self._matches)}\n"
#                  "See the --list option to list the _available test suites")


class NoMatchException(SuiteException):
    def __init__(self, name: str):
        self._name = name

    def __str__(self) -> str:
        return (f"Name `{self._name}` doesn't match any suite/group name\n\t"
                 "Try to run with -l to see the available suites")


class Suite:
    _available: List['Suite'] = []

    @classmethod
    def run(cls, asked_names: List[str]) -> None:
        """Run all _available suites"""

        """ Remove not asked suite from _available suites
            Tries to autocomplete the asked names
        """

        asked_suites = cls._asked_suites(asked_names)
        for suite in asked_suites:
            suite._register()
        for suite in asked_suites:
            try:
                suite._run()
            except SuiteExitFirstException:
                break

    @classmethod
    def _asked_suites(cls, asked_names: [str]) -> ['Suite']:
        suites = cls._available
        if not Config.bonus:
            suites = [suite for suite in cls._available if not suite._bonus]
        if len(asked_names) == 0:
            asked_names = [suite._name for suite in suites]

        names = []
        for name in asked_names:
            matches = [suite._name for suite in suites if suite._name.startswith(name) or suite._group.startswith(name)]
            if len(matches) == 0:
                raise NoMatchException(name)
            names.extend(matches)

        suites = list(set(
            [suite for suite in suites if suite._name in names] +
            [suite for suite in suites if suite._group in names]
        ))
        return sorted(suites, key=lambda suite: suite._name)

    @classmethod
    def list(cls) -> str:
        max_name_width = max(len(suite._name + suite._group) for suite in cls._available) + 5
        out = ""
        for suite in cls._available:
            prefixed_name = f"{suite._group}/{suite._name} "
            out += f"{prefixed_name:.<{max_name_width}} {suite._description}\n"
        return out

    def __init__(
        self,
        origin,
        name:        str,
        group:       str,
        bonus:       bool = False,
        description: str  = "no description",
    ):
        """Suite class
           name:   suite id
           groups: list of suite groups
           bonus:  is this suite bonus
        """
        self._name                  = name
        self._group                 = group
        self._description           = description
        self._bonus                 = bonus
        self._origin                = origin
        self._tests: List[Test]     = []
        self._results: List[Result] = []

    def _run(self) -> None:
        """Run all test in the suite"""

        title = ' ' + self._name + ' '
        print(colors.blue(f"{title:#^{Config.term_cols}}"))
        if Config.range is not None:
            self._tests = self._tests[Config.range[0] : Config.range[1] + 1]
        for i, test in enumerate(self._tests):
            result = test.run()
            self._results.append(result)
            print(result.summarize(i))
            if Config.exit_first and result.failed:
                raise SuiteExitFirstException()

    def _register(self) -> None:
        def test(*args, **kwargs):
            self._tests.append(Test(*args, **kwargs))
        self._origin(test)

    @classmethod
    def summarize(cls):
        """Print a summary of all runned suites"""
        full_pass_count = sum(suite._pass_count for suite in suites)
        full_fail_count = sum(suite._fail_count for suite in suites)
        lines = ["Summary:"]
        for suite in cls._available:
            lines.append(Suite._stat_summary(suite._name, suite._pass_count, suite._fail_count))
        lines.append(Suite._stat_summary("TOTAL", full_pass_count, full_fail_count))
        return "\n".join(lines) + "\n"

    @property
    def _pass_count(self) -> int:
        count = 0
        for result in self._results:
            if result.passed:
                count += 1
        return count

    @property
    def _fail_count(self) -> int:
        return len(self._results) - self._pass_count

    @staticmethod
    def _stat_summary(self, name: str, pass_count: int, fail_count: int) -> str:
        prefix = f"{name + ' ':.<{Config.term_cols - 24}}"
        pass_str = colors.green("{pass_count:4} [PASS]")
        fail_str = colors.red("{fail_count:4} [FAIL]")
        return f"{prefix} {pass_str} {fail_str}"

    @classmethod
    def save(cls):
        """Save the result of all suites to a file"""
        colors.disable()
        with open(Config.log_path, "w") as file:
            for suite in suites:
                for result in suite._results:
                    if result.failed:
                        file.write(result)