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