fm-weck 1.5.0__py3-none-any.whl → 1.5.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
fm_weck/__init__.py CHANGED
@@ -8,4 +8,4 @@
8
8
  from .config import Config # noqa: F401
9
9
  from .image_mgr import ImageMgr # noqa: F401
10
10
 
11
- __version__ = "1.5.0"
11
+ __version__ = "1.5.2"
fm_weck/cli.py CHANGED
@@ -419,8 +419,8 @@ def parse(raw_args: list[str]) -> Tuple[Callable[[], None], Namespace]:
419
419
  "TOOL",
420
420
  help="The tool for which to run the smoke test.",
421
421
  type=ToolQualifier,
422
- nargs=1,
423
- ).completer = ShellCompletion.versions_completer
422
+ ).completer = ShellCompletion.versions_completer # type: ignore[assignment]
423
+
424
424
  smoke_test.add_argument(
425
425
  "--gitlab-ci-mode",
426
426
  action="store_true",
@@ -428,6 +428,15 @@ def parse(raw_args: list[str]) -> Tuple[Callable[[], None], Namespace]:
428
428
  required=False,
429
429
  default=False,
430
430
  )
431
+ smoke_test.add_argument(
432
+ "--competition-year",
433
+ action="store",
434
+ type=int,
435
+ help="Automatically select the tool version used in the specified competition year (e.g., 2025). "
436
+ "Searches for SV-COMP or Test-Comp participation in that year.",
437
+ required=False,
438
+ default=None,
439
+ )
431
440
  smoke_test.set_defaults(main=main_smoke_test)
432
441
 
433
442
  with contextlib.suppress(ImportError):
@@ -483,6 +492,41 @@ def resolve_property_for_server(prop_name: str) -> Union[Path, str]:
483
492
  return prop_name
484
493
 
485
494
 
495
+ def get_version_for_competition_year(tool_path: Path, year: int) -> Optional[str]:
496
+ """
497
+ Find the tool version used in a competition for the given year.
498
+ Searches for SV-COMP or Test-Comp participation entries.
499
+
500
+ Args:
501
+ tool_path: Path to the tool's YAML file
502
+ year: Competition year (e.g., 2025)
503
+
504
+ Returns:
505
+ Version string if found, None otherwise
506
+ """
507
+ import yaml
508
+
509
+ if not tool_path.exists() or not tool_path.is_file():
510
+ return None
511
+
512
+ with tool_path.open("r") as f:
513
+ data = yaml.safe_load(f)
514
+
515
+ competition_participations = data.get("competition_participations", [])
516
+
517
+ # Search for competition entries matching the year
518
+ for participation in competition_participations:
519
+ competition = participation.get("competition", "")
520
+ # Match "SV-COMP 2025", "Test-Comp 2025", etc.
521
+ if f"{year}" in competition and ("SV-COMP" in competition or "Test-Comp" in competition):
522
+ version = participation.get("tool_version")
523
+ if version:
524
+ logger.info("Found version '%s' for %s in %s", version, tool_path.stem, competition)
525
+ return version
526
+
527
+ return None
528
+
529
+
486
530
  def set_log_options(loglevel: Optional[str], logfile: Optional[str], config: dict[str, Any]):
487
531
  level = "WARNING"
488
532
  level = loglevel.upper() if loglevel else config.get("logging", {}).get("level", level)
@@ -681,36 +725,129 @@ def check_client_options(timelimit_arg, output_path=None):
681
725
  return timelimit, output_path
682
726
 
683
727
 
684
- def main_smoke_test(args: argparse.Namespace):
685
- from .serve import setup_fm_tool
686
- from .smoke_test_mode import run_smoke_test, run_smoke_test_gitlab_ci
687
-
688
- try:
689
- tool = resolve_tool(args.TOOL[0])
690
- except KeyError:
691
- logger.error("Unknown tool: %s", args.TOOL[0].tool)
692
- return 1
693
-
694
- fm_data, shelve_space = setup_fm_tool(
695
- fm_tool=tool,
696
- version=args.TOOL[0].version,
697
- configuration=Config(),
728
+ def _do_smoke_test_mode(fm_data, shelve_space, tool, gitlab_ci_mode) -> int:
729
+ from .smoke_test_mode import (
730
+ run_smoke_test,
731
+ run_smoke_test_gitlab_ci,
698
732
  )
699
733
 
700
- if args.gitlab_ci_mode:
734
+ # GitLab CI mode installs packages directly and runs the script on the host
735
+ if gitlab_ci_mode:
701
736
  from subprocess import CalledProcessError
702
737
 
703
738
  try:
704
739
  run_smoke_test_gitlab_ci(fm_data, shelve_space)
740
+ return 0
705
741
  except CalledProcessError as e:
742
+ logger.error(
743
+ "Smoke test script failed in GitLab CI mode (exit code %d).\n- Tool: %s\n- Script directory: %s\n",
744
+ e.returncode,
745
+ tool.stem,
746
+ shelve_space,
747
+ )
706
748
  return e.returncode
707
749
 
708
- return 0
750
+ # Containerized mode: run script inside the configured image
751
+ result = run_smoke_test(fm_data, shelve_space, Config())
752
+ if result.exit_code != 0:
753
+ # Print a concise but informative error for CI logs
754
+ output_lines = result.raw_output.splitlines()
755
+ tail = "\n".join(output_lines[-50:]) if output_lines else "<no output captured>"
756
+ logger.error(
757
+ "Smoke test failed (exit code %d).\n"
758
+ "- Tool: %s\n"
759
+ "- Tool cache dir (host): %s\n"
760
+ "- Script name: smoketest.sh or smoke_test.sh\n"
761
+ "Last 50 lines of output:\n%s",
762
+ result.exit_code,
763
+ tool.stem,
764
+ shelve_space,
765
+ tail,
766
+ )
767
+ return result.exit_code
768
+
769
+ return 0
770
+
771
+
772
+ def main_smoke_test(args: argparse.Namespace):
773
+ from fm_tools.exceptions import DownloadUnsuccessfulException, UnsupportedDOIException
774
+
775
+ from .serve import setup_fm_tool
776
+ from .smoke_test_mode import (
777
+ NoSmokeTestFileError,
778
+ SmokeTestError,
779
+ SmokeTestFileIsEmptyError,
780
+ SmokeTestFileIsNotExecutableError,
781
+ )
782
+
783
+ try:
784
+ tool = resolve_tool(args.TOOL)
785
+ except KeyError:
786
+ logger.error("Unknown tool: %s", args.TOOL.tool)
787
+ return 1
788
+
789
+ # Handle --competition-year flag
790
+ version = args.TOOL.version
791
+ if args.competition_year:
792
+ if version:
793
+ logger.warning(
794
+ "Both explicit version '%s' and --competition-year %d specified. "
795
+ "Using competition year to determine version.",
796
+ version,
797
+ args.competition_year,
798
+ )
799
+ competition_version = get_version_for_competition_year(tool, args.competition_year)
800
+ if competition_version:
801
+ logger.info("Using version '%s' for competition year %d", competition_version, args.competition_year)
802
+ version = competition_version
803
+ else:
804
+ logger.error("No competition participation found for year %d in tool %s", args.competition_year, tool.stem)
805
+ return 1
806
+
807
+ try:
808
+ fm_data, shelve_space = setup_fm_tool(
809
+ fm_tool=tool,
810
+ version=version,
811
+ configuration=Config(),
812
+ )
813
+ except (DownloadUnsuccessfulException, UnsupportedDOIException) as e:
814
+ if "code: 504" in str(e).lower():
815
+ print(
816
+ "Failed to download the tool due to a timeout (504 Gateway Timeout). "
817
+ "This issue is likely caused by Zenodo. Retry by rerunning the smoke test.",
818
+ )
819
+ else:
820
+ print(f"There was an error while downloading and unpacking the tool:\n{e}")
709
821
 
710
822
  try:
711
- run_smoke_test(fm_data, shelve_space, Config())
823
+ return _do_smoke_test_mode(fm_data, shelve_space, tool, args.gitlab_ci_mode)
824
+ except NoSmokeTestFileError as e:
825
+ print(
826
+ f"{e}\n"
827
+ "Expected a smoke test script named 'smoketest.sh' or 'smoke_test.sh' in the tool directory.\n"
828
+ "Action: Add a minimal script 'smoke_test.sh' to the root of the tool directory that exercises the tool.\n"
829
+ "The top level contents of the tool directory were:\n"
830
+ f"{os.linesep.join([str(p.name) for p in shelve_space.iterdir()])}",
831
+ )
832
+ return 1
833
+ except SmokeTestFileIsEmptyError as e:
834
+ print(
835
+ f"{e}\nAction: Populate the smoke test script with at least one command that validates basic startup.",
836
+ )
837
+ return 1
838
+ except SmokeTestFileIsNotExecutableError as e:
839
+ print(
840
+ f"{e}\nAction: Make the smoke test script executable. On linux, you can do this by running:\n"
841
+ f" chmod +x {e.smoke_test_file}",
842
+ )
843
+ return 1
712
844
  except ValueError as e:
713
- logger.error(e)
845
+ # e.g., invalid shelve space path, or other validation errors
846
+ print(f"Smoke test setup failed: {e}")
847
+ return 1
848
+ except SmokeTestError as e:
849
+ # Fallback for any other smoke-test specific errors
850
+ print(f"Error starting the smoke test: {e}")
714
851
  return 1
715
852
 
716
853
 
fm_weck/config.py CHANGED
@@ -135,6 +135,7 @@ class Config(object):
135
135
  """Makes sure relative Paths in the config are relative to the config file."""
136
136
 
137
137
  path = fn(self, *args, **kwargs)
138
+ path = path.expanduser()
138
139
 
139
140
  if not self._config_source:
140
141
  return path
fm_weck/engine.py CHANGED
@@ -66,7 +66,7 @@ class Engine(ABC):
66
66
  overlay_tool_dir: Optional[str] = None
67
67
  image: Optional[str] = None
68
68
  dry_run: bool = False
69
- work_dir: Optional[Path] = Path(CWD_MOUNT_LOCATION)
69
+ work_dir: Path = Path(CWD_MOUNT_LOCATION)
70
70
 
71
71
  def __init__(self, image: Union[str, FmImageConfig]):
72
72
  self._tmp_output_dir = Path(mkdtemp("fm_weck_output")).resolve()
@@ -84,7 +84,7 @@ class Engine(ABC):
84
84
  shutil.rmtree(self._tmp_output_dir)
85
85
 
86
86
  def get_workdir(self) -> str:
87
- return Path(CWD_MOUNT_LOCATION).as_posix()
87
+ return self.work_dir.as_posix()
88
88
 
89
89
  def set_log_file(self, log_file: Path):
90
90
  self.log_file = log_file
@@ -142,7 +142,7 @@ class Engine(ABC):
142
142
  "--entrypoint",
143
143
  '[""]',
144
144
  "-v",
145
- f"{Path.cwd().absolute()}:{self.get_workdir()}",
145
+ f"{Path.cwd().absolute()}:{Path(CWD_MOUNT_LOCATION).as_posix()}",
146
146
  "-v",
147
147
  f"{Config().cache_location}:{CACHE_MOUNT_LOCATION}",
148
148
  "-v",
@@ -6,6 +6,7 @@
6
6
  # SPDX-License-Identifier: Apache-2.0
7
7
 
8
8
  import logging
9
+ import os
9
10
  import subprocess
10
11
  from pathlib import Path
11
12
 
@@ -16,6 +17,67 @@ from .engine import CACHE_MOUNT_LOCATION, Engine
16
17
  logger = logging.getLogger(__name__)
17
18
 
18
19
 
20
+ class SmokeTestError(Exception):
21
+ """Custom exception for smoke test errors."""
22
+
23
+ pass
24
+
25
+
26
+ class NoSmokeTestFileError(SmokeTestError):
27
+ """Exception raised when no smoke test file is found."""
28
+
29
+ pass
30
+
31
+
32
+ class SmokeTestFileIsEmptyError(SmokeTestError):
33
+ """Exception raised when the smoke test file is empty."""
34
+
35
+ pass
36
+
37
+
38
+ class SmokeTestFileIsNotExecutableError(SmokeTestError):
39
+ """Exception raised when the smoke test file is not executable."""
40
+
41
+ smoke_test_file: Path
42
+
43
+ def __init__(self, smoke_test_file: Path):
44
+ self.smoke_test_file = smoke_test_file
45
+ super().__init__(f"Smoke test file is not executable: {smoke_test_file}")
46
+
47
+ pass
48
+
49
+
50
+ def locate_and_check_smoke_test_file(shelve_space: Path) -> str:
51
+ """Check if the smoke test file exists and is not empty.
52
+
53
+ Args:
54
+ shelve_space: Path to the shelve space directory.
55
+
56
+ Returns:
57
+ The relative path to the smoke test file as a string.
58
+
59
+ Raises:
60
+ NoSmokeTestFileError: If the smoke test file does not exist.
61
+ SmokeTestFileIsEmptyError: If the smoke test file is empty.
62
+ """
63
+ if not (shelve_space / "smoketest.sh").exists() and not (shelve_space / "smoke_test.sh").exists():
64
+ raise NoSmokeTestFileError(f"Smoke test file not found in: {shelve_space}")
65
+
66
+ if (shelve_space / "smoketest.sh").exists():
67
+ file_path = shelve_space / "smoketest.sh"
68
+ else:
69
+ # Since we checked for existence above, this must exist
70
+ file_path = shelve_space / "smoke_test.sh"
71
+
72
+ if not file_path.stat().st_mode & os.X_OK:
73
+ raise SmokeTestFileIsNotExecutableError(file_path.relative_to(shelve_space))
74
+
75
+ if file_path.stat().st_size == 0:
76
+ raise SmokeTestFileIsEmptyError(f"Smoke test file is empty: {file_path}")
77
+
78
+ return f"./{file_path.name}"
79
+
80
+
19
81
  def run_smoke_test(fm_data, shelve_space, config):
20
82
  if not shelve_space.exists() or not shelve_space.is_dir():
21
83
  raise ValueError(f"Invalid shelve space path: {shelve_space}")
@@ -25,15 +87,10 @@ def run_smoke_test(fm_data, shelve_space, config):
25
87
  tool_dir = shelve_space.relative_to(config.cache_location)
26
88
  engine.work_dir = CACHE_MOUNT_LOCATION / tool_dir
27
89
 
28
- # Check for smoketest.sh first, then smoke_test.sh
29
- if (shelve_space / "smoketest.sh").exists():
30
- command = "./smoketest.sh"
31
- elif (shelve_space / "smoke_test.sh").exists():
32
- command = "./smoke_test.sh"
33
- else:
34
- raise ValueError(f"Smoke test script not found in {shelve_space}. Expected ./smoketest.sh or ./smoke_test.sh")
90
+ command = locate_and_check_smoke_test_file(shelve_space)
35
91
 
36
- engine.run(command)
92
+ # Return the result so callers can react to failures and print diagnostics
93
+ return engine.run(command)
37
94
 
38
95
 
39
96
  def run_smoke_test_gitlab_ci(fm_data: FmToolVersion, tool_dir: Path):
@@ -61,21 +118,11 @@ def run_smoke_test_gitlab_ci(fm_data: FmToolVersion, tool_dir: Path):
61
118
  else:
62
119
  logger.info("No required packages specified for this tool")
63
120
 
64
- # Run the smoke test script
65
- # Check for smoketest.sh first, then smoke_test.sh
66
- smoke_test_script = tool_dir / "smoketest.sh"
67
- if not smoke_test_script.exists():
68
- smoke_test_script = tool_dir / "smoke_test.sh"
69
-
70
- if not smoke_test_script.exists():
71
- raise ValueError(
72
- f"Smoke test script not found in downloaded tool directory: {tool_dir}. "
73
- f"Expected ./smoketest.sh or ./smoke_test.sh"
74
- )
121
+ script_command = locate_and_check_smoke_test_file(tool_dir)
75
122
 
76
- logger.info("Running smoke test script: %s", smoke_test_script)
123
+ logger.info("Running smoke test script: %s", script_command)
77
124
  try:
78
- subprocess.run([f"./{smoke_test_script.name}"], cwd=tool_dir, check=True)
125
+ subprocess.run([script_command], cwd=tool_dir, check=True)
79
126
  logger.info("Smoke test completed successfully")
80
127
  except subprocess.CalledProcessError as e:
81
128
  logger.error("Smoke test failed with return code %d", e.returncode)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fm-weck
3
- Version: 1.5.0
3
+ Version: 1.5.2
4
4
  Author-email: Henrik Wachowitz <henrik.wachowitz@ifi.lmu.de>
5
5
  Maintainer-email: Henrik Wachowitz <henrik.wachowitz@ifi.lmu.de>
6
6
  Classifier: Development Status :: 4 - Beta
@@ -1,10 +1,10 @@
1
- fm_weck/__init__.py,sha256=v0ltYQ0MeG-hs4LVU6TIPeboGSEmIMSDHqOGvQdQR_s,351
1
+ fm_weck/__init__.py,sha256=P8xygPjF_sqOcyrYtuILR2BsTY8VZAPkvhl0WRrgKSc,351
2
2
  fm_weck/__main__.py,sha256=IfNDAqM6MK6P7KsQoW3wOHPOscB8evdVlS9C7R4wd_0,391
3
3
  fm_weck/cache_mgr.py,sha256=3-OQFmCeswazXmX08ND4oEHFOR07ZDCwWzjmFTDkOSE,1373
4
4
  fm_weck/capture.py,sha256=iogn3JvCZEjD0SQL8Xa3TzmZAWifd9ZIP81c3JIsdUQ,982
5
- fm_weck/cli.py,sha256=dPpJqyF0iXqZuRiPATypDbc6TyaUWzUFpb1qc2uo-mg,23657
6
- fm_weck/config.py,sha256=8XXlHbb9cW1N1jatNFY5AnaRdxsSz-ohCrqq5t90RAc,8099
7
- fm_weck/engine.py,sha256=HfRj5Okewteco7A-CccCIbk2aEGDh4K896YyQo7h1gE,21733
5
+ fm_weck/cli.py,sha256=wDRzNUvocQmBKicP7aeJPWLmiIlHd_nnH87vEowMNk4,29097
6
+ fm_weck/config.py,sha256=DHIdGA4OhVAHAPJyB--G19EwGshAirmb6PGwoJQtZaE,8136
7
+ fm_weck/engine.py,sha256=P48zCTeMHI3imE0ho5AxhDNTRY3mX0Nzd0gBzjqRCX4,21729
8
8
  fm_weck/exceptions.py,sha256=AfqTt6gxZPUQ0rKqwgdGTyfIjWmU3xBFIJxQnMLbLGo,2465
9
9
  fm_weck/file_util.py,sha256=FG_uBuNWGWbSivBv0dYzwugMkGfdS_iFY-hG6GLDD54,799
10
10
  fm_weck/image_mgr.py,sha256=lkn1nWuwKtMhtVf_PFZOOJftY5uyE0EydY2f-sefHGE,1943
@@ -12,7 +12,7 @@ fm_weck/run_result.py,sha256=srg3w2MvC-2YqgpRtqrat2DoxhErtlc5FQO3uaFaGTI,1253
12
12
  fm_weck/runexec_mode.py,sha256=UamxVvYm0XErPjR2sRJaLMX8uHBzRcgCTWbQIZjdju0,2195
13
13
  fm_weck/runexec_util.py,sha256=YBvVIPpmEousZVxbZ5NS8jzpKPLyws31kIFE2z3Ki2E,1370
14
14
  fm_weck/serve.py,sha256=RUz_3v15By_CvcBJlNBw-mKuDubAiIha5EfayFBnqps,10728
15
- fm_weck/smoke_test_mode.py,sha256=SYXLf1mhdGEOS9YtJ7XuR8j_zfN9zUWhu1KSm4zgAR4,2990
15
+ fm_weck/smoke_test_mode.py,sha256=5n2LdYyTxATFl-yaDVfkie-IiNACL3Hs2c_CARqMvek,4196
16
16
  fm_weck/tmp_file.py,sha256=oJiE8VGTPxhl-bXdtbM8eNqQ4e9ECPG1jDmiboVDo_k,1956
17
17
  fm_weck/version_listing.py,sha256=caaoC3n9R-Ao2sEQ_ngOVO3bnKr7cNVeH6EiA8jO5Sc,864
18
18
  fm_weck/grpc_service/__init__.py,sha256=TvQSR0pVeh4MMMT40VfzJFyZTHpAOI7C808vjJpWiOs,390
@@ -165,7 +165,7 @@ fm_weck/resources/fm_tools/wit4java.yml,sha256=ylfze2XbV4zKkVUH57Veqn7G49gW0Byxd
165
165
  fm_weck/resources/fm_tools/witch.yml,sha256=wwe6lrI2sxGKVZbLeipa38rPhB2pcSUFi9uVngtXGUQ,1795
166
166
  fm_weck/resources/fm_tools/witnesslint.yml,sha256=EvMBcm5fx6lgSLRmHSKXSxXIJKZ-BrxLwTXI4GQ6FMs,6812
167
167
  fm_weck/resources/fm_tools/witnessmap.yml,sha256=FyZtEloxpWBBjLn9kyqoen2kPjOkH2r4fxAj5gfV8Bg,1692
168
- fm_weck-1.5.0.dist-info/METADATA,sha256=UqR47gDzSfO-MS7U0lEmpMoYJ5Xe6Vx_4cxMXg7qq-0,3339
169
- fm_weck-1.5.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
170
- fm_weck-1.5.0.dist-info/entry_points.txt,sha256=toWpKCSY1u593MPnI_xW5gnwlnkerP4AvmPQ1s2nPgY,50
171
- fm_weck-1.5.0.dist-info/RECORD,,
168
+ fm_weck-1.5.2.dist-info/METADATA,sha256=1a0oYolO5XlQSwC0tT2qWCdzzroGb-PdS6YQbFEZUME,3339
169
+ fm_weck-1.5.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
170
+ fm_weck-1.5.2.dist-info/entry_points.txt,sha256=toWpKCSY1u593MPnI_xW5gnwlnkerP4AvmPQ1s2nPgY,50
171
+ fm_weck-1.5.2.dist-info/RECORD,,