|
3 | 3 | import pathlib |
4 | 4 | import random |
5 | 5 | from dataclasses import dataclass |
6 | | -from typing import Optional |
| 6 | +from typing import Optional, Tuple |
7 | 7 |
|
8 | 8 | from pytest import CaptureFixture, MonkeyPatch |
9 | 9 |
|
@@ -131,6 +131,100 @@ class InputsType: |
131 | 131 | pathlib.Path("tests") / "data" / "line" / "hello9.pdl", |
132 | 132 | ] |
133 | 133 |
|
| 134 | +# ACTUAL_NO_ERROR indicates there was no error when running pdl.exec_file |
| 135 | +ACTUAL_NO_ERROR = 0 |
| 136 | +# ACTUAL_NO_ERROR indicates there was PdlParserError when running pdl.exec_file |
| 137 | +ACTUAL_PARSE_ERROR_CODE = 1 |
| 138 | +# ACTUAL_RUNTIME_ERROR_CODE indicates there was runtime error when running pdl.exec_file |
| 139 | +ACTUAL_RUNTIME_ERROR_CODE = 2 |
| 140 | + |
| 141 | +def run_single_file(pdl_file_name: str, monkeypatch: MonkeyPatch) -> Tuple[bool, str, int]: |
| 142 | + """ |
| 143 | + Tests a single file |
| 144 | + Returns: |
| 145 | + - bool: True if runs successfully and False otherwise |
| 146 | + - str: "" if runs succesfully and the actual results otherwise |
| 147 | + - int: a code to indicate what kind of error occured. 0 for no error, 1 for parse error, and 2 for runtime error |
| 148 | + """ |
| 149 | + if pdl_file_name in TO_SKIP: |
| 150 | + print(f"File {pdl_file_name} is part of TO_SKIP, skipping test...") |
| 151 | + return True, "", ACTUAL_NO_ERROR |
| 152 | + |
| 153 | + path_obj = pathlib.Path(pdl_file_name) |
| 154 | + scope: ScopeType = PdlDict({}) |
| 155 | + |
| 156 | + if pdl_file_name in TESTS_WITH_INPUT: |
| 157 | + inputs = TESTS_WITH_INPUT[pdl_file_name] |
| 158 | + if inputs.stdin is not None: |
| 159 | + monkeypatch.setattr( |
| 160 | + "sys.stdin", |
| 161 | + io.StringIO(inputs.stdin), |
| 162 | + ) |
| 163 | + if inputs.scope is not None: |
| 164 | + scope = inputs.scope |
| 165 | + |
| 166 | + try: |
| 167 | + random.seed(11) |
| 168 | + output = pdl.exec_file( |
| 169 | + path_obj, |
| 170 | + scope=scope, |
| 171 | + output="all", |
| 172 | + config=pdl.InterpreterConfig(batch=0), |
| 173 | + ) |
| 174 | + |
| 175 | + actual_result = output["result"] |
| 176 | + block_to_dict(output["trace"], json_compatible=True) |
| 177 | + result_dir_name = ( |
| 178 | + pathlib.Path(".") / "tests" / "results" / path_obj.parent |
| 179 | + ) |
| 180 | + |
| 181 | + print(actual_result) |
| 182 | + |
| 183 | + # Find and compare results |
| 184 | + if not __find_and_compare_results(path_obj, str(actual_result)): |
| 185 | + if OLLAMA_GHACTIONS_RESULTS: |
| 186 | + print( |
| 187 | + f"Program {pdl_file_name} requries updating its result on GitHub Actions" |
| 188 | + ) |
| 189 | + print(f"Actual results: {str(actual_result)}") |
| 190 | + result_file_name = f"{path_obj.stem}.ollama_ghactions.result" |
| 191 | + __write_to_results_file(result_dir_name, result_file_name, str(actual_result)) |
| 192 | + |
| 193 | + # Evaluate the results again. If fails again, then consider this program as failing |
| 194 | + if not __find_and_compare_results( |
| 195 | + path_obj, str(actual_result) |
| 196 | + ): |
| 197 | + print( |
| 198 | + f"Program {str(pdl_file_name)} failed second time even after generating results from Github Actions. Consider this failing!" |
| 199 | + ) |
| 200 | + |
| 201 | + return False, str(actual_result), ACTUAL_NO_ERROR |
| 202 | + else: |
| 203 | + return True, "", ACTUAL_NO_ERROR |
| 204 | + |
| 205 | + if UPDATE_RESULTS: |
| 206 | + result_file_name = ( |
| 207 | + f"{path_obj.stem}.{str(RESULTS_VERSION)}.result" |
| 208 | + ) |
| 209 | + __write_to_results_file( |
| 210 | + result_dir_name, result_file_name, str(actual_result) |
| 211 | + ) |
| 212 | + |
| 213 | + return False, str(actual_result), ACTUAL_NO_ERROR |
| 214 | + |
| 215 | + except PDLParseError: |
| 216 | + expected_parse_errors = set(str(p) for p in EXPECTED_PARSE_ERROR) |
| 217 | + if pdl_file_name in expected_parse_errors: |
| 218 | + return True, "", ACTUAL_PARSE_ERROR_CODE |
| 219 | + return False, "", ACTUAL_PARSE_ERROR_CODE |
| 220 | + |
| 221 | + except Exception: |
| 222 | + expected_runtime_error = set(str(p) for p in EXPECTED_RUNTIME_ERROR) |
| 223 | + if pdl_file_name in expected_runtime_error: |
| 224 | + return True, "", ACTUAL_RUNTIME_ERROR_CODE |
| 225 | + return False, "", ACTUAL_RUNTIME_ERROR_CODE |
| 226 | + |
| 227 | + return True, "", ACTUAL_NO_ERROR |
134 | 228 |
|
135 | 229 | def __write_to_results_file( |
136 | 230 | dir_name: pathlib.Path, filename: str, content: str |
@@ -162,112 +256,37 @@ def __find_and_compare_results( |
162 | 256 | return True |
163 | 257 | return False |
164 | 258 |
|
| 259 | +def test_all_pdl_programs(capsys: CaptureFixture[str], monkeypatch: MonkeyPatch) -> None: |
165 | 260 |
|
166 | | -def test_valid_programs(capsys: CaptureFixture[str], monkeypatch: MonkeyPatch) -> None: |
167 | | - actual_parse_error: set[str] = set() |
168 | | - actual_runtime_error: set[str] = set() |
| 261 | + unexpected_parse_error: set[str] = set() |
| 262 | + unexpected_runtime_error: set[str] = set() |
169 | 263 | wrong_results = {} |
170 | 264 |
|
171 | 265 | files = pathlib.Path(".").glob("**/*.pdl") |
| 266 | + files = [str(f) for f in files] |
172 | 267 |
|
173 | | - for pdl_file_name in files: |
174 | | - |
175 | | - scope: ScopeType = PdlDict({}) |
176 | | - if str(pdl_file_name) in TO_SKIP: |
177 | | - continue |
178 | | - if str(pdl_file_name) in TESTS_WITH_INPUT: |
179 | | - inputs = TESTS_WITH_INPUT[str(pdl_file_name)] |
180 | | - if inputs.stdin is not None: |
181 | | - monkeypatch.setattr( |
182 | | - "sys.stdin", |
183 | | - io.StringIO(inputs.stdin), |
184 | | - ) |
185 | | - if inputs.scope is not None: |
186 | | - scope = inputs.scope |
187 | | - try: |
188 | | - random.seed(11) |
189 | | - output = pdl.exec_file( |
190 | | - pdl_file_name, |
191 | | - scope=scope, |
192 | | - output="all", |
193 | | - config=pdl.InterpreterConfig(batch=0), |
194 | | - ) |
195 | | - actual_result = output["result"] |
| 268 | + # Check if we only want to test a subset of PDL programs |
| 269 | + # MODIFIED_PDL_FILES_ENV_VAR is a string of PDL files, comma separated |
| 270 | + MODIFIED_PDL_FILES_ENV_VAR = os.getenv("MODIFIED_PDL_FILES", "") |
| 271 | + MODIFIED_PDL_FILES = [item.strip() for item in MODIFIED_PDL_FILES_ENV_VAR.split(",")] |
196 | 272 |
|
197 | | - block_to_dict(output["trace"], json_compatible=True) |
198 | | - result_dir_name = ( |
199 | | - pathlib.Path(".") / "tests" / "results" / pdl_file_name.parent |
200 | | - ) |
| 273 | + if len(MODIFIED_PDL_FILES) > 0: |
| 274 | + print("Only testing a subset of PDL programs, particularly newly added examples or PDL files that were modified.") |
| 275 | + files = MODIFIED_PDL_FILES |
201 | 276 |
|
202 | | - if not __find_and_compare_results(pdl_file_name, str(actual_result)): |
| 277 | + for pdl_file_name in files: |
203 | 278 |
|
204 | | - if OLLAMA_GHACTIONS_RESULTS: |
205 | | - print( |
206 | | - f"Program {str(pdl_file_name)} requries updating its result on GitHub Actions" |
207 | | - ) |
208 | | - print(f"Actual results: {str(actual_result)}") |
209 | | - result_file_name = f"{pdl_file_name.stem}.ollama_ghactions.result" |
210 | | - __write_to_results_file( |
211 | | - result_dir_name, result_file_name, str(actual_result) |
212 | | - ) |
| 279 | + pdl_file_name_str = str(pdl_file_name) |
| 280 | + successful, actual_results, error_code = run_single_file(pdl_file_name_str, monkeypatch) |
213 | 281 |
|
214 | | - # Evaluate the results again. If fails again, then consider this program as failing |
215 | | - if not __find_and_compare_results( |
216 | | - pdl_file_name, str(actual_result) |
217 | | - ): |
218 | | - print( |
219 | | - f"Program {str(pdl_file_name)} failed second time even after generating results from Github Actions. Consider this failing!" |
220 | | - ) |
221 | | - wrong_results[str(pdl_file_name)] = { |
222 | | - "actual": str(actual_result), |
223 | | - } |
224 | | - # If evaluating results produces correct result, then this is considered passing |
225 | | - else: |
226 | | - continue |
227 | | - |
228 | | - if UPDATE_RESULTS: |
229 | | - result_file_name = ( |
230 | | - f"{pdl_file_name.stem}.{str(RESULTS_VERSION)}.result" |
231 | | - ) |
232 | | - __write_to_results_file( |
233 | | - result_dir_name, result_file_name, str(actual_result) |
234 | | - ) |
| 282 | + if not successful: |
| 283 | + if error_code == ACTUAL_PARSE_ERROR_CODE: |
| 284 | + unexpected_parse_error |= {pdl_file_name_str} |
| 285 | + elif error_code == ACTUAL_RUNTIME_ERROR_CODE: |
| 286 | + unexpected_runtime_error |= {pdl_file_name_str} |
| 287 | + else: |
| 288 | + wrong_results[pdl_file_name_str] = actual_results |
235 | 289 |
|
236 | | - wrong_results[str(pdl_file_name)] = { |
237 | | - "actual": str(actual_result), |
238 | | - } |
239 | | - except PDLParseError: |
240 | | - actual_parse_error |= {str(pdl_file_name)} |
241 | | - except Exception as exc: |
242 | | - if str(pdl_file_name) not in set(str(p) for p in EXPECTED_RUNTIME_ERROR): |
243 | | - print(f"{pdl_file_name}: {exc}") # unexpected error: breakpoint |
244 | | - actual_runtime_error |= {str(pdl_file_name)} |
245 | | - print(exc) |
246 | | - |
247 | | - # Parse errors |
248 | | - expected_parse_error = set(str(p) for p in EXPECTED_PARSE_ERROR) |
249 | | - unexpected_parse_error = sorted(list(actual_parse_error - expected_parse_error)) |
250 | | - assert ( |
251 | | - len(unexpected_parse_error) == 0 |
252 | | - ), f"Unexpected parse error: {unexpected_parse_error}" |
253 | | - |
254 | | - # Runtime errors |
255 | | - expected_runtime_error = set(str(p) for p in EXPECTED_RUNTIME_ERROR) |
256 | | - unexpected_runtime_error = sorted( |
257 | | - list(actual_runtime_error - expected_runtime_error) |
258 | | - ) |
259 | | - assert ( |
260 | | - len(unexpected_runtime_error) == 0 |
261 | | - ), f"Unexpected runtime error: {unexpected_runtime_error}" |
262 | | - |
263 | | - # Unexpected valid |
264 | | - unexpected_valid = sorted( |
265 | | - list( |
266 | | - (expected_parse_error - actual_parse_error).union( |
267 | | - expected_runtime_error - actual_runtime_error |
268 | | - ) |
269 | | - ) |
270 | | - ) |
271 | | - assert len(unexpected_valid) == 0, f"Unexpected valid: {unexpected_valid}" |
272 | | - # Unexpected results |
| 290 | + assert len(unexpected_parse_error) == 0, f"Unexpected parse error: {unexpected_parse_error}" |
| 291 | + assert len(unexpected_runtime_error) == 0, f"Unexpected runtime error: {unexpected_runtime_error}" |
273 | 292 | assert len(wrong_results) == 0, f"Wrong results: {wrong_results}" |
0 commit comments