hassle.generate_tests
1import argparse 2import tokenize 3 4import isort 5from pathier import Pathier 6 7from hassle import hassle_utilities 8 9root = Pathier(__file__).parent 10 11 12def get_args() -> argparse.Namespace: 13 parser = argparse.ArgumentParser() 14 15 parser.add_argument( 16 "paths", 17 type=str, 18 default=".", 19 nargs="*", 20 help=""" The name of the package or project to generate tests for, 21 assuming it's a subfolder of your current working directory. 22 Can also be a full path to the package. If nothing is given, 23 the current working directory will be used. 24 Can also be individual files.""", 25 ) 26 27 parser.add_argument( 28 "-t", 29 "--tests_dir", 30 type=str, 31 default=None, 32 help=""" A specific tests directory path to write tests to. 33 When supplying individual files to paths arg, the default 34 behavior is to create a 'tests' directory in the parent 35 directory of the specified file, resulting in multiple 36 'tests' directories being created if the files exist in 37 subdirectories. Supply a path to this arg to override 38 this behavior.""", 39 ) 40 41 args = parser.parse_args() 42 return args 43 44 45def get_function_names(filepath: Pathier) -> list[str]: 46 """Returns a list of function names from a .py file.""" 47 with filepath.open("r") as file: 48 tokens = list(tokenize.generate_tokens(file.readline)) 49 functions = [] 50 for i, token in enumerate(tokens): 51 # If token.type is "name" and the preceeding token is "def" 52 if ( 53 token.type == 1 54 and tokens[i - 1].type == 1 55 and tokens[i - 1].string == "def" 56 ): 57 functions.append(token.string) 58 return functions 59 60 61def write_placeholders( 62 package_path: Pathier, 63 pyfile: Pathier | str, 64 functions: list[str], 65 tests_dir: Pathier = None, 66): 67 """Write placeholder functions to the 68 tests/test_{pyfile} file if they don't already exist. 69 The placeholder functions use the naming convention 70 test_{function_name} 71 72 :param package_path: Path to the package. 73 74 :param pyfile: Path to the pyfile to write placeholders for. 75 76 :param functions: List of functions to generate 77 placehodlers for.""" 78 package_name = package_path.stem 79 if not tests_dir: 80 tests_dir = package_path / "tests" 81 tests_dir.mkdir() 82 pyfile = Pathier(pyfile) 83 test_file = tests_dir / f"test_{pyfile.name}" 84 # Makes sure not to overwrite previously written tests 85 # or additional imports. 86 if test_file.exists(): 87 content = test_file.read_text() + "\n\n" 88 else: 89 content = f"import pytest\nfrom {package_name} import {pyfile.stem}\n\n\n" 90 for function in functions: 91 test_function = f"def test_{function}" 92 if test_function not in content and function != "__init__": 93 content += f"{test_function}():\n ...\n\n\n" 94 test_file.write_text(content) 95 hassle_utilities.format_files(tests_dir) 96 [isort.file(path) for path in tests_dir.rglob("*.py")] 97 98 99def generate_test_files(package_path: Pathier, tests_dir: Pathier = None): 100 """Generate test files for all .py files in 'src' 101 directory of 'package_path'.""" 102 pyfiles = [ 103 file 104 for file in (package_path / "src").rglob("*.py") 105 if file.name != "__init__.py" 106 ] 107 for pyfile in pyfiles: 108 write_placeholders(package_path, pyfile, get_function_names(pyfile), tests_dir) 109 110 111def main(args: argparse.Namespace = None): 112 if not args: 113 args = get_args() 114 args.paths = [Pathier(path).resolve() for path in args.paths] 115 if args.tests_dir: 116 args.tests_dir = Pathier(args.tests_dir).resolve() 117 for path in args.paths: 118 if path.is_dir(): 119 generate_test_files(path, args.tests_dir) 120 elif path.is_file(): 121 write_placeholders( 122 path.parent, path, get_function_names(path), args.tests_dir 123 ) 124 125 126if __name__ == "__main__": 127 main(get_args())
def
get_args() -> argparse.Namespace:
13def get_args() -> argparse.Namespace: 14 parser = argparse.ArgumentParser() 15 16 parser.add_argument( 17 "paths", 18 type=str, 19 default=".", 20 nargs="*", 21 help=""" The name of the package or project to generate tests for, 22 assuming it's a subfolder of your current working directory. 23 Can also be a full path to the package. If nothing is given, 24 the current working directory will be used. 25 Can also be individual files.""", 26 ) 27 28 parser.add_argument( 29 "-t", 30 "--tests_dir", 31 type=str, 32 default=None, 33 help=""" A specific tests directory path to write tests to. 34 When supplying individual files to paths arg, the default 35 behavior is to create a 'tests' directory in the parent 36 directory of the specified file, resulting in multiple 37 'tests' directories being created if the files exist in 38 subdirectories. Supply a path to this arg to override 39 this behavior.""", 40 ) 41 42 args = parser.parse_args() 43 return args
def
get_function_names(filepath: pathier.pathier.Pathier) -> list[str]:
46def get_function_names(filepath: Pathier) -> list[str]: 47 """Returns a list of function names from a .py file.""" 48 with filepath.open("r") as file: 49 tokens = list(tokenize.generate_tokens(file.readline)) 50 functions = [] 51 for i, token in enumerate(tokens): 52 # If token.type is "name" and the preceeding token is "def" 53 if ( 54 token.type == 1 55 and tokens[i - 1].type == 1 56 and tokens[i - 1].string == "def" 57 ): 58 functions.append(token.string) 59 return functions
Returns a list of function names from a .py file.
def
write_placeholders( package_path: pathier.pathier.Pathier, pyfile: pathier.pathier.Pathier | str, functions: list[str], tests_dir: pathier.pathier.Pathier = None):
62def write_placeholders( 63 package_path: Pathier, 64 pyfile: Pathier | str, 65 functions: list[str], 66 tests_dir: Pathier = None, 67): 68 """Write placeholder functions to the 69 tests/test_{pyfile} file if they don't already exist. 70 The placeholder functions use the naming convention 71 test_{function_name} 72 73 :param package_path: Path to the package. 74 75 :param pyfile: Path to the pyfile to write placeholders for. 76 77 :param functions: List of functions to generate 78 placehodlers for.""" 79 package_name = package_path.stem 80 if not tests_dir: 81 tests_dir = package_path / "tests" 82 tests_dir.mkdir() 83 pyfile = Pathier(pyfile) 84 test_file = tests_dir / f"test_{pyfile.name}" 85 # Makes sure not to overwrite previously written tests 86 # or additional imports. 87 if test_file.exists(): 88 content = test_file.read_text() + "\n\n" 89 else: 90 content = f"import pytest\nfrom {package_name} import {pyfile.stem}\n\n\n" 91 for function in functions: 92 test_function = f"def test_{function}" 93 if test_function not in content and function != "__init__": 94 content += f"{test_function}():\n ...\n\n\n" 95 test_file.write_text(content) 96 hassle_utilities.format_files(tests_dir) 97 [isort.file(path) for path in tests_dir.rglob("*.py")]
Write placeholder functions to the tests/test_{pyfile} file if they don't already exist. The placeholder functions use the naming convention test_{function_name}
Parameters
package_path: Path to the package.
pyfile: Path to the pyfile to write placeholders for.
functions: List of functions to generate placehodlers for.
def
generate_test_files( package_path: pathier.pathier.Pathier, tests_dir: pathier.pathier.Pathier = None):
100def generate_test_files(package_path: Pathier, tests_dir: Pathier = None): 101 """Generate test files for all .py files in 'src' 102 directory of 'package_path'.""" 103 pyfiles = [ 104 file 105 for file in (package_path / "src").rglob("*.py") 106 if file.name != "__init__.py" 107 ] 108 for pyfile in pyfiles: 109 write_placeholders(package_path, pyfile, get_function_names(pyfile), tests_dir)
Generate test files for all .py files in 'src' directory of 'package_path'.
def
main(args: argparse.Namespace = None):
112def main(args: argparse.Namespace = None): 113 if not args: 114 args = get_args() 115 args.paths = [Pathier(path).resolve() for path in args.paths] 116 if args.tests_dir: 117 args.tests_dir = Pathier(args.tests_dir).resolve() 118 for path in args.paths: 119 if path.is_dir(): 120 generate_test_files(path, args.tests_dir) 121 elif path.is_file(): 122 write_placeholders( 123 path.parent, path, get_function_names(path), args.tests_dir 124 )