Coverage for tests/test_valid_source.py: 25%

103 statements  

« prev     ^ index     » next       coverage.py v7.5.3, created at 2024-06-13 21:17 +0200

1import ast 

2import hashlib 

3from pathlib import Path 

4from unittest.mock import patch 

5 

6import pytest 

7from pysource_minimize import minimize 

8 

9from .test_invalid_ast import does_compile 

10from pysource_codegen._codegen import generate_ast 

11from pysource_codegen._codegen import is_valid_ast 

12from pysource_codegen._codegen import unparse 

13 

14sample_dir = Path(__file__).parent / "valid_source_samples" 

15sample_dir.mkdir(exist_ok=True) 

16 

17 

18@pytest.mark.parametrize( 

19 "file", [pytest.param(f, id=f.stem[:12]) for f in sample_dir.glob("*.py")] 

20) 

21def test_valid_source(file): 

22 code = file.read_text() 

23 

24 try: 

25 tree = ast.parse(code) 

26 except: 

27 return 

28 

29 if not does_compile(tree): 29 ↛ 30line 29 didn't jump to line 30, because the condition on line 29 was never true

30 return 

31 

32 print("the following code is valid but can not be generated by codegen:\n") 

33 print(code) 

34 print() 

35 

36 assert is_valid_ast(tree) 

37 

38 

39def minimize_if_valid(code): 

40 def bug_found(code): 

41 try: 

42 tree = ast.parse(code) 

43 except: 

44 return False 

45 

46 if not does_compile(tree): 

47 return False 

48 

49 return not is_valid_ast(tree) 

50 

51 if bug_found(code): 

52 code = minimize(code, bug_found) 

53 print() 

54 print("minimized:") 

55 print(code) 

56 name = sample_dir / f"{hashlib.sha256(code.encode('utf-8')).hexdigest()}.py" 

57 name.write_text(code) 

58 return True 

59 else: 

60 return False 

61 

62 

63def generate_valid_source(seed): 

64 """ 

65 try to find source code which is valid but can not be generated by codegen() 

66 

67 The use() hook controls if a specific restriction in the code generation should be applied 

68 

69 """ 

70 print("seed=", seed) 

71 

72 ignore_index = 0 

73 ignored_something = True 

74 max_index = 0 

75 

76 while ignored_something: 

77 ignored_something = False 

78 

79 current_index = 0 

80 

81 def use(): 

82 nonlocal current_index 

83 nonlocal ignored_something 

84 

85 ignore = current_index == ignore_index 

86 current_index += 1 

87 if ignore: 

88 ignored_something = True 

89 return not ignore 

90 

91 with patch("pysource_codegen._codegen.use", use): 

92 tree = generate_ast(seed, node_limit=200, depth_limit=5) 

93 

94 max_index = max(max_index, current_index) 

95 

96 try: 

97 code = unparse(tree) 

98 except Exception as e: 

99 print(repr(e)) 

100 continue 

101 

102 print(f"{seed} {ignore_index} of {max_index} ignored? {ignored_something}") 

103 

104 if minimize_if_valid(code): 

105 return True 

106 

107 ignore_index += 1 

108 

109 return False 

110 

111 

112if __name__ == "__main__": 112 ↛ 113line 112 didn't jump to line 113, because the condition on line 112 was never true

113 import json 

114 import argparse 

115 

116 parser = argparse.ArgumentParser( 

117 description="Find valid code which can not be generated by pysource-codegen" 

118 ) 

119 parser.add_argument("folder", help="Folder to process") 

120 parser.add_argument("--skip-first", action="store_true", help="Skip the first file") 

121 args = parser.parse_args() 

122 

123 db = Path("checked_valid_source_files") 

124 

125 if db.exists(): 

126 checked = set(json.loads(db.read_text())) 

127 else: 

128 checked = set() 

129 

130 globbed_files = set(map(str, Path(args.folder).rglob("*.py"))) 

131 

132 all_files = globbed_files - checked 

133 

134 if not all_files: 

135 all_files = globbed_files 

136 checked = set() 

137 

138 is_first = True 

139 

140 for file in map(Path, sorted(all_files)): 

141 print(file) 

142 try: 

143 code = file.read_text("utf-8") 

144 compile(code, str(file), "exec") 

145 except: 

146 print("skip") 

147 continue 

148 if is_first and args.skip_first: 

149 checked.add(str(file)) 

150 is_first = False 

151 continue 

152 if minimize_if_valid(code): 

153 break 

154 

155 checked.add(str(file)) 

156 

157 db.write_text(json.dumps(sorted(checked)))