|
1 | 1 | import subprocess |
2 | | -from typing import List, Dict, Optional |
| 2 | +from typing import List, Dict, Optional, Tuple |
3 | 3 | from jinja2 import Environment, FileSystemLoader |
4 | 4 | from dataclasses import dataclass, field |
5 | 5 | import shutil |
@@ -353,26 +353,59 @@ def __write_env_file(self): |
353 | 353 | for key, value in self.env.items(): |
354 | 354 | env_file.write(f"{key}={value}\n") |
355 | 355 |
|
356 | | - def __unpack_reference_results(self): |
357 | | - with tarfile.open(self.reference_result.path) as reference_results_tared: |
358 | | - # specify which folder to extract to |
359 | | - reference_results_tared.extractall(self.system_test_dir / PRECICE_REL_REFERENCE_DIR) |
360 | | - logging.debug( |
361 | | - f"extracting {self.reference_result.path} into {self.system_test_dir / PRECICE_REL_REFERENCE_DIR}") |
| 356 | + def __unpack_reference_results(self) -> Tuple[bool, str]: |
| 357 | + if not self.reference_result.path.exists(): |
| 358 | + error_message = ( |
| 359 | + f"Reference results archive was not found for {self}. " |
| 360 | + f"Expected file: {self.reference_result.path}. " |
| 361 | + "Please generate the reference results first or update tests.yaml accordingly.") |
| 362 | + logging.error(error_message) |
| 363 | + return False, error_message |
| 364 | + |
| 365 | + try: |
| 366 | + # Base directory where reference results should be extracted |
| 367 | + dest_dir = self.system_test_dir / PRECICE_REL_REFERENCE_DIR |
| 368 | + dest_dir.mkdir(parents=True, exist_ok=True) |
| 369 | + dest_dir_resolved = dest_dir.resolve() |
| 370 | + |
| 371 | + with tarfile.open(self.reference_result.path) as reference_results_tared: |
| 372 | + # Validate that each member will be extracted within dest_dir |
| 373 | + for member in reference_results_tared.getmembers(): |
| 374 | + member_path = dest_dir / member.name |
| 375 | + member_path_resolved = member_path.resolve() |
| 376 | + # Ensure the resolved member path is within the destination directory |
| 377 | + if os.path.commonpath([str(dest_dir_resolved), str( |
| 378 | + member_path_resolved)]) != str(dest_dir_resolved): |
| 379 | + logging.error( |
| 380 | + f"Unsafe path detected in reference results archive {self.reference_result.path} " |
| 381 | + f"for {self}: {member.name}") |
| 382 | + return False |
| 383 | + |
| 384 | + # All paths are safe; extract into the destination directory |
| 385 | + reference_results_tared.extractall(dest_dir) |
| 386 | + |
| 387 | + logging.debug( |
| 388 | + f"extracting {self.reference_result.path} into {dest_dir}") |
| 389 | + return True, "" |
| 390 | + except (tarfile.TarError, OSError) as e: |
| 391 | + error_message = ( |
| 392 | + f"Could not unpack reference results archive {self.reference_result.path} for {self}: {e}") |
| 393 | + logging.error(error_message) |
| 394 | + return False, error_message |
362 | 395 |
|
363 | 396 | def _run_field_compare(self): |
364 | 397 | """ |
365 | | - Writes the Docker Compose file to disk, executes docker-compose up, and handles the process output. |
366 | | -
|
367 | | - Args: |
368 | | - docker_compose_content: The content of the Docker Compose file. |
| 398 | + Executes the field comparison step after unpacking reference results. |
369 | 399 |
|
370 | 400 | Returns: |
371 | | - A SystemtestResult object containing the state. |
| 401 | + A FieldCompareResult object containing the command outcome and logs. |
372 | 402 | """ |
373 | 403 | logging.debug(f"Running fieldcompare for {self}") |
374 | 404 | time_start = time.perf_counter() |
375 | | - self.__unpack_reference_results() |
| 405 | + unpack_success, unpack_error_message = self.__unpack_reference_results() |
| 406 | + if not unpack_success: |
| 407 | + elapsed_time = time.perf_counter() - time_start |
| 408 | + return FieldCompareResult(1, [], [unpack_error_message], self, elapsed_time) |
376 | 409 | docker_compose_content = self.__get_field_compare_compose_file() |
377 | 410 | stdout_data = [] |
378 | 411 | stderr_data = [] |
|
0 commit comments