teddecor.UnitTest.TEDTest

  1from __future__ import annotations
  2import ast
  3import argparse
  4import os
  5
  6from teddecor.UnitTest import RunResults, TestSuite, SaveType
  7from teddecor.Util import slash
  8
  9
 10def get_test_functions(module: ast.Module) -> list:
 11    """Retrieve the test case names from the given module.
 12
 13    Args:
 14        module (ast.Module): The module to parse test cases from.
 15
 16    Returns:
 17        list: Test case names
 18    """
 19    functions = [
 20        obj.name
 21        for obj in module.body
 22        if isinstance(obj, ast.FunctionDef)
 23        and "test" in [decor.id for decor in obj.decorator_list]
 24    ]
 25    return functions if functions is not None else []
 26
 27
 28def get_test_classes(module: ast.Module) -> list:
 29    """Retrieve the test class names from the given module.
 30
 31    Args:
 32        module (ast.Module): The module the parse classes from.
 33
 34    Returns:
 35        list: Test class names.
 36    """
 37    klasses = [
 38        obj.name
 39        for obj in module.body
 40        if isinstance(obj, ast.ClassDef) and "Test" in [base.id for base in obj.bases]
 41    ]
 42    return klasses if klasses is not None else []
 43
 44
 45def get_files() -> list[str]:
 46    """Gets the python files/modules from the specified directory
 47
 48    Args:
 49        dir (str): The directory to recursively search
 50
 51    Returns:
 52        list[str]: The python files found in the specified directory
 53    """
 54    from glob import glob
 55
 56    return [y for x in os.walk(f".{slash()}") for y in glob(os.path.join(x[0], "*.py"))]
 57
 58
 59def generate_run(files: list[str], arguments: str) -> TestSuite:
 60    """Generates a TestSuite with the tests pulled from the found modules.
 61
 62    Args:
 63        files (list[str]): The files/modules that have tests.
 64        name (str): The name of the test suite.
 65
 66    Returns:
 67        TestSuite: The TestSuite with all tests added to it.
 68    """
 69    from sys import path
 70
 71    test_run = RunResults(name=arguments["name"])
 72    curdir = os.getcwd()
 73
 74    for file in files:
 75        mdir = file.rsplit(slash(), 1)[0]
 76        mname = file.split(slash())[-1].split(".")[0]
 77
 78        test_suite = TestSuite(name=mname)
 79        test_suite.tests = []
 80
 81        with open(file, "r", encoding="utf-8") as fd:
 82            file_content = fd.read()
 83
 84        module = ast.parse(file_content)
 85        runners = []
 86        runners.extend(get_test_classes(module))
 87        runners.extend(get_test_functions(module))
 88
 89        os.chdir(mdir)
 90        try:
 91            # Bring module into scope and grab it
 92
 93            path.insert(0, str(os.getcwd()))
 94            mod = __import__(mname)
 95
 96            # For each of the valid test objects add them to the test suite
 97            for runner in runners:
 98                if hasattr(mod, runner):
 99                    test_suite.append(getattr(mod, runner))
100
101        except Exception as error:
102            # TODO: Properly look at errors and display them
103            # Don't stop as it could be module level errors that cause the code to go to this block
104            print(error)
105
106        os.chdir(curdir)
107        test_run.append(test_suite.run(display=False, regex=arguments["regex"]))
108
109    return test_run
110
111
112def get_args() -> dict:
113    """Parse the passed in arguments with `ArgParse`
114
115    Raises:
116        Exception: If the user specifies a start directory and it does not exist.
117
118    Returns:
119        dict: The key value pairs of the arguments passed in.
120    """
121    from os import getcwd
122
123    parser = argparse.ArgumentParser(description="Process some integers.")
124    parser.add_argument(
125        "-n",
126        "--name",
127        help="The name of the group of tests that will be run.\nShows up as the test suite name.",
128    )
129    parser.add_argument(
130        "-e",
131        "--entry",
132        help="The entry point where the scan for tests will start.",
133    )
134    parser.add_argument(
135        "-s",
136        "--save",
137        help="The file type that the results will be saved too.",
138    )
139    parser.add_argument(
140        "-r",
141        "--regex",
142        help="Regex to apply so only matching tests are run.",
143    )
144    parser.add_argument(
145        "-o",
146        "--save_path",
147        help="Relative path on where to save the results.",
148    )
149
150    args = parser.parse_args()
151    variables = {
152        "path": None,
153        "save": None,
154        "regex": None,
155        "save_path": "." + slash(),
156        "name": None,
157    }
158
159    if args.entry is None:
160        variables["path"] = str(getcwd())
161    else:
162        from re import split
163
164        if args.entry.startswith("~"):
165            from pathlib import Path
166
167            args.entry = str(Path.home()) + args.entry[1:]
168
169        if os.path.isdir(args.entry):
170            os.chdir(slash().join(split(r"[\\/]", args.entry)))
171            variables["path"] = str(os.getcwd())
172        else:
173            raise Exception(f"{dir} is not a directory")
174
175    if args.save is not None and args.save.lower() in ["json", "csv", "txt", "all"]:
176        if args.save == "all":
177            variables["save"] = SaveType.ALL()
178        else:
179            variables["save"] = f".{args.save.lower()}"
180
181    if args.regex is not None:
182        variables["regex"] = args.regex
183
184    if args.name is not None:
185        variables["name"] = args.name
186    else:
187        variables["name"] = variables["path"].split(slash())[-1]
188
189    if args.save_path is not None:
190        if not os.path.isdir(args.save_path):
191            os.mkdir(args.save_path)
192
193        variables["save_path"] = args.save_path
194
195    return variables
196
197
198def main():
199    # TODO: display type
200
201    arguments = get_args()
202
203    files = get_files()
204    run = generate_run(files, arguments)
205    run.write()
206    if arguments["save"] is not None:
207        run.save(location=arguments["save_path"], ext=arguments["save"])
208
209
210if __name__ == "__main__":
211    main()
def get_test_functions(module: ast.Module) -> list:
11def get_test_functions(module: ast.Module) -> list:
12    """Retrieve the test case names from the given module.
13
14    Args:
15        module (ast.Module): The module to parse test cases from.
16
17    Returns:
18        list: Test case names
19    """
20    functions = [
21        obj.name
22        for obj in module.body
23        if isinstance(obj, ast.FunctionDef)
24        and "test" in [decor.id for decor in obj.decorator_list]
25    ]
26    return functions if functions is not None else []

Retrieve the test case names from the given module.

Args: module (ast.Module): The module to parse test cases from.

Returns: list: Test case names

def get_test_classes(module: ast.Module) -> list:
29def get_test_classes(module: ast.Module) -> list:
30    """Retrieve the test class names from the given module.
31
32    Args:
33        module (ast.Module): The module the parse classes from.
34
35    Returns:
36        list: Test class names.
37    """
38    klasses = [
39        obj.name
40        for obj in module.body
41        if isinstance(obj, ast.ClassDef) and "Test" in [base.id for base in obj.bases]
42    ]
43    return klasses if klasses is not None else []

Retrieve the test class names from the given module.

Args: module (ast.Module): The module the parse classes from.

Returns: list: Test class names.

def get_files() -> list[str]:
46def get_files() -> list[str]:
47    """Gets the python files/modules from the specified directory
48
49    Args:
50        dir (str): The directory to recursively search
51
52    Returns:
53        list[str]: The python files found in the specified directory
54    """
55    from glob import glob
56
57    return [y for x in os.walk(f".{slash()}") for y in glob(os.path.join(x[0], "*.py"))]

Gets the python files/modules from the specified directory

Args: dir (str): The directory to recursively search

Returns: list[str]: The python files found in the specified directory

def generate_run( files: list[str], arguments: str) -> teddecor.UnitTest.TestSuite.TestSuite:
 60def generate_run(files: list[str], arguments: str) -> TestSuite:
 61    """Generates a TestSuite with the tests pulled from the found modules.
 62
 63    Args:
 64        files (list[str]): The files/modules that have tests.
 65        name (str): The name of the test suite.
 66
 67    Returns:
 68        TestSuite: The TestSuite with all tests added to it.
 69    """
 70    from sys import path
 71
 72    test_run = RunResults(name=arguments["name"])
 73    curdir = os.getcwd()
 74
 75    for file in files:
 76        mdir = file.rsplit(slash(), 1)[0]
 77        mname = file.split(slash())[-1].split(".")[0]
 78
 79        test_suite = TestSuite(name=mname)
 80        test_suite.tests = []
 81
 82        with open(file, "r", encoding="utf-8") as fd:
 83            file_content = fd.read()
 84
 85        module = ast.parse(file_content)
 86        runners = []
 87        runners.extend(get_test_classes(module))
 88        runners.extend(get_test_functions(module))
 89
 90        os.chdir(mdir)
 91        try:
 92            # Bring module into scope and grab it
 93
 94            path.insert(0, str(os.getcwd()))
 95            mod = __import__(mname)
 96
 97            # For each of the valid test objects add them to the test suite
 98            for runner in runners:
 99                if hasattr(mod, runner):
100                    test_suite.append(getattr(mod, runner))
101
102        except Exception as error:
103            # TODO: Properly look at errors and display them
104            # Don't stop as it could be module level errors that cause the code to go to this block
105            print(error)
106
107        os.chdir(curdir)
108        test_run.append(test_suite.run(display=False, regex=arguments["regex"]))
109
110    return test_run

Generates a TestSuite with the tests pulled from the found modules.

Args: files (list[str]): The files/modules that have tests. name (str): The name of the test suite.

Returns: TestSuite: The TestSuite with all tests added to it.

def get_args() -> dict:
113def get_args() -> dict:
114    """Parse the passed in arguments with `ArgParse`
115
116    Raises:
117        Exception: If the user specifies a start directory and it does not exist.
118
119    Returns:
120        dict: The key value pairs of the arguments passed in.
121    """
122    from os import getcwd
123
124    parser = argparse.ArgumentParser(description="Process some integers.")
125    parser.add_argument(
126        "-n",
127        "--name",
128        help="The name of the group of tests that will be run.\nShows up as the test suite name.",
129    )
130    parser.add_argument(
131        "-e",
132        "--entry",
133        help="The entry point where the scan for tests will start.",
134    )
135    parser.add_argument(
136        "-s",
137        "--save",
138        help="The file type that the results will be saved too.",
139    )
140    parser.add_argument(
141        "-r",
142        "--regex",
143        help="Regex to apply so only matching tests are run.",
144    )
145    parser.add_argument(
146        "-o",
147        "--save_path",
148        help="Relative path on where to save the results.",
149    )
150
151    args = parser.parse_args()
152    variables = {
153        "path": None,
154        "save": None,
155        "regex": None,
156        "save_path": "." + slash(),
157        "name": None,
158    }
159
160    if args.entry is None:
161        variables["path"] = str(getcwd())
162    else:
163        from re import split
164
165        if args.entry.startswith("~"):
166            from pathlib import Path
167
168            args.entry = str(Path.home()) + args.entry[1:]
169
170        if os.path.isdir(args.entry):
171            os.chdir(slash().join(split(r"[\\/]", args.entry)))
172            variables["path"] = str(os.getcwd())
173        else:
174            raise Exception(f"{dir} is not a directory")
175
176    if args.save is not None and args.save.lower() in ["json", "csv", "txt", "all"]:
177        if args.save == "all":
178            variables["save"] = SaveType.ALL()
179        else:
180            variables["save"] = f".{args.save.lower()}"
181
182    if args.regex is not None:
183        variables["regex"] = args.regex
184
185    if args.name is not None:
186        variables["name"] = args.name
187    else:
188        variables["name"] = variables["path"].split(slash())[-1]
189
190    if args.save_path is not None:
191        if not os.path.isdir(args.save_path):
192            os.mkdir(args.save_path)
193
194        variables["save_path"] = args.save_path
195
196    return variables

Parse the passed in arguments with ArgParse

Raises: Exception: If the user specifies a start directory and it does not exist.

Returns: dict: The key value pairs of the arguments passed in.

def main()
199def main():
200    # TODO: display type
201
202    arguments = get_args()
203
204    files = get_files()
205    run = generate_run(files, arguments)
206    run.write()
207    if arguments["save"] is not None:
208        run.save(location=arguments["save_path"], ext=arguments["save"])