tlaplus-cli 0.3.1__tar.gz → 0.3.2__tar.gz
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.
- {tlaplus_cli-0.3.1/src/tlaplus_cli.egg-info → tlaplus_cli-0.3.2}/PKG-INFO +33 -9
- {tlaplus_cli-0.3.1 → tlaplus_cli-0.3.2}/README.md +32 -8
- {tlaplus_cli-0.3.1 → tlaplus_cli-0.3.2}/pyproject.toml +2 -1
- {tlaplus_cli-0.3.1 → tlaplus_cli-0.3.2}/src/tlaplus_cli/build_tlc_module.py +13 -14
- {tlaplus_cli-0.3.1 → tlaplus_cli-0.3.2}/src/tlaplus_cli/cli.py +5 -2
- tlaplus_cli-0.3.2/src/tlaplus_cli/project.py +35 -0
- {tlaplus_cli-0.3.1 → tlaplus_cli-0.3.2}/src/tlaplus_cli/run_tlc.py +37 -18
- {tlaplus_cli-0.3.1 → tlaplus_cli-0.3.2}/src/tlaplus_cli/tools_manager.py +5 -8
- {tlaplus_cli-0.3.1 → tlaplus_cli-0.3.2/src/tlaplus_cli.egg-info}/PKG-INFO +33 -9
- {tlaplus_cli-0.3.1 → tlaplus_cli-0.3.2}/src/tlaplus_cli.egg-info/SOURCES.txt +3 -0
- tlaplus_cli-0.3.2/tests/test_build_tlc_module.py +161 -0
- {tlaplus_cli-0.3.1 → tlaplus_cli-0.3.2}/tests/test_java_module_naming.py +6 -5
- tlaplus_cli-0.3.2/tests/test_project.py +46 -0
- {tlaplus_cli-0.3.1 → tlaplus_cli-0.3.2}/tests/test_run_tlc.py +8 -8
- tlaplus_cli-0.3.2/tests/test_spec_resolution.py +200 -0
- {tlaplus_cli-0.3.1 → tlaplus_cli-0.3.2}/tests/test_tools_manager.py +15 -10
- tlaplus_cli-0.3.1/tests/test_build_tlc_module.py +0 -77
- {tlaplus_cli-0.3.1 → tlaplus_cli-0.3.2}/LICENSE +0 -0
- {tlaplus_cli-0.3.1 → tlaplus_cli-0.3.2}/MANIFEST.in +0 -0
- {tlaplus_cli-0.3.1 → tlaplus_cli-0.3.2}/setup.cfg +0 -0
- {tlaplus_cli-0.3.1 → tlaplus_cli-0.3.2}/src/tlaplus_cli/__init__.py +0 -0
- {tlaplus_cli-0.3.1 → tlaplus_cli-0.3.2}/src/tlaplus_cli/check_java.py +0 -0
- {tlaplus_cli-0.3.1 → tlaplus_cli-0.3.2}/src/tlaplus_cli/config.py +0 -0
- {tlaplus_cli-0.3.1 → tlaplus_cli-0.3.2}/src/tlaplus_cli/py.typed +0 -0
- {tlaplus_cli-0.3.1 → tlaplus_cli-0.3.2}/src/tlaplus_cli/resources/default_config.yaml +0 -0
- {tlaplus_cli-0.3.1 → tlaplus_cli-0.3.2}/src/tlaplus_cli/settings.py +0 -0
- {tlaplus_cli-0.3.1 → tlaplus_cli-0.3.2}/src/tlaplus_cli/version_manager.py +0 -0
- {tlaplus_cli-0.3.1 → tlaplus_cli-0.3.2}/src/tlaplus_cli.egg-info/dependency_links.txt +0 -0
- {tlaplus_cli-0.3.1 → tlaplus_cli-0.3.2}/src/tlaplus_cli.egg-info/entry_points.txt +0 -0
- {tlaplus_cli-0.3.1 → tlaplus_cli-0.3.2}/src/tlaplus_cli.egg-info/requires.txt +0 -0
- {tlaplus_cli-0.3.1 → tlaplus_cli-0.3.2}/src/tlaplus_cli.egg-info/top_level.txt +0 -0
- {tlaplus_cli-0.3.1 → tlaplus_cli-0.3.2}/tests/test_check_java.py +0 -0
- {tlaplus_cli-0.3.1 → tlaplus_cli-0.3.2}/tests/test_cli.py +0 -0
- {tlaplus_cli-0.3.1 → tlaplus_cli-0.3.2}/tests/test_version_manager.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tlaplus-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: TLA+ tools: download TLC, compile custom modules, run model checker
|
|
5
5
|
Author-email: Denis Nikolskiy <codeomatics@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -86,7 +86,7 @@ tla tools upgrade
|
|
|
86
86
|
> [!NOTE]
|
|
87
87
|
> If the target version to upgrade is not yet installed locally, the CLI will automatically download it.
|
|
88
88
|
|
|
89
|
-
Show the
|
|
89
|
+
Show the absolute path to the pinned version's `tla2tools.jar`:
|
|
90
90
|
```bash
|
|
91
91
|
tla tools path
|
|
92
92
|
```
|
|
@@ -98,7 +98,6 @@ tla tools path v1.8.0
|
|
|
98
98
|
|
|
99
99
|
Example output:
|
|
100
100
|
```text
|
|
101
|
-
TLC2 Version 2.19 of 08 August 2024 (rev: 5a47802)
|
|
102
101
|
/home/bob/.cache/tla/tools/v1.8.0-5a47802/tla2tools.jar
|
|
103
102
|
```
|
|
104
103
|
|
|
@@ -139,23 +138,48 @@ For example (runs `queue.tla`):
|
|
|
139
138
|
tla tlc queue
|
|
140
139
|
```
|
|
141
140
|
|
|
142
|
-
|
|
141
|
+
#### Project-Aware Execution
|
|
142
|
+
|
|
143
|
+
The CLI intelligently resolves the specification file and its project structure. It will check the following locations:
|
|
144
|
+
1. `<spec_name>` (if it's a direct path to a file, e.g., `models/my_model.tla`)
|
|
145
|
+
2. `<spec_name>.tla`
|
|
146
|
+
3. `spec/<spec_name>.tla` (checks an inner `spec/` folder relative to the spec's location)
|
|
147
|
+
|
|
148
|
+
Furthermore, it automatically discovers the **project root** by looking for `modules/`, `classes/`, or `lib/` directories adjacent to the spec file or its parent. When a project root is found:
|
|
149
|
+
- The project's `classes/` directory is added to the Java classpath.
|
|
150
|
+
- Any `*.jar` files found in the project's `lib/` directory are added to the Java classpath.
|
|
151
|
+
- The `-DTLA-Library` system property is set to the project's `modules/` directory, allowing TLC to find your custom Java overrides.
|
|
152
|
+
|
|
153
|
+
To check the currently pinned `tla2tools.jar` path and its TLC version:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
tla tlc --version
|
|
157
|
+
```
|
|
143
158
|
|
|
144
159
|
### Compile Custom Java Modules
|
|
145
160
|
|
|
146
|
-
|
|
161
|
+
Java modules (overrides) are compiled using the pinned version of the toolset.
|
|
162
|
+
|
|
163
|
+
Compile modules in the current project:
|
|
164
|
+
```bash
|
|
165
|
+
tla modules build
|
|
166
|
+
```
|
|
147
167
|
|
|
148
|
-
Compile modules:
|
|
168
|
+
Compile modules in a specific project path:
|
|
149
169
|
```bash
|
|
150
|
-
tla build
|
|
170
|
+
tla modules build path/to/project
|
|
151
171
|
```
|
|
152
172
|
|
|
153
173
|
Verbose output:
|
|
154
174
|
```bash
|
|
155
|
-
tla build --verbose
|
|
175
|
+
tla modules build --verbose
|
|
156
176
|
```
|
|
157
177
|
|
|
158
|
-
|
|
178
|
+
The build command:
|
|
179
|
+
1. Resolves the project root (defaults to `workspace.root` from config).
|
|
180
|
+
2. Includes `lib/*.jar` files from the project root in the `javac` classpath.
|
|
181
|
+
3. Compiles `.java` files from the project's `modules/` directory into its `classes/` directory.
|
|
182
|
+
4. Generates the necessary Java service provider configuration for TLC overrides.
|
|
159
183
|
|
|
160
184
|
### Check Java Version
|
|
161
185
|
|
|
@@ -57,7 +57,7 @@ tla tools upgrade
|
|
|
57
57
|
> [!NOTE]
|
|
58
58
|
> If the target version to upgrade is not yet installed locally, the CLI will automatically download it.
|
|
59
59
|
|
|
60
|
-
Show the
|
|
60
|
+
Show the absolute path to the pinned version's `tla2tools.jar`:
|
|
61
61
|
```bash
|
|
62
62
|
tla tools path
|
|
63
63
|
```
|
|
@@ -69,7 +69,6 @@ tla tools path v1.8.0
|
|
|
69
69
|
|
|
70
70
|
Example output:
|
|
71
71
|
```text
|
|
72
|
-
TLC2 Version 2.19 of 08 August 2024 (rev: 5a47802)
|
|
73
72
|
/home/bob/.cache/tla/tools/v1.8.0-5a47802/tla2tools.jar
|
|
74
73
|
```
|
|
75
74
|
|
|
@@ -110,23 +109,48 @@ For example (runs `queue.tla`):
|
|
|
110
109
|
tla tlc queue
|
|
111
110
|
```
|
|
112
111
|
|
|
113
|
-
|
|
112
|
+
#### Project-Aware Execution
|
|
113
|
+
|
|
114
|
+
The CLI intelligently resolves the specification file and its project structure. It will check the following locations:
|
|
115
|
+
1. `<spec_name>` (if it's a direct path to a file, e.g., `models/my_model.tla`)
|
|
116
|
+
2. `<spec_name>.tla`
|
|
117
|
+
3. `spec/<spec_name>.tla` (checks an inner `spec/` folder relative to the spec's location)
|
|
118
|
+
|
|
119
|
+
Furthermore, it automatically discovers the **project root** by looking for `modules/`, `classes/`, or `lib/` directories adjacent to the spec file or its parent. When a project root is found:
|
|
120
|
+
- The project's `classes/` directory is added to the Java classpath.
|
|
121
|
+
- Any `*.jar` files found in the project's `lib/` directory are added to the Java classpath.
|
|
122
|
+
- The `-DTLA-Library` system property is set to the project's `modules/` directory, allowing TLC to find your custom Java overrides.
|
|
123
|
+
|
|
124
|
+
To check the currently pinned `tla2tools.jar` path and its TLC version:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
tla tlc --version
|
|
128
|
+
```
|
|
114
129
|
|
|
115
130
|
### Compile Custom Java Modules
|
|
116
131
|
|
|
117
|
-
|
|
132
|
+
Java modules (overrides) are compiled using the pinned version of the toolset.
|
|
133
|
+
|
|
134
|
+
Compile modules in the current project:
|
|
135
|
+
```bash
|
|
136
|
+
tla modules build
|
|
137
|
+
```
|
|
118
138
|
|
|
119
|
-
Compile modules:
|
|
139
|
+
Compile modules in a specific project path:
|
|
120
140
|
```bash
|
|
121
|
-
tla build
|
|
141
|
+
tla modules build path/to/project
|
|
122
142
|
```
|
|
123
143
|
|
|
124
144
|
Verbose output:
|
|
125
145
|
```bash
|
|
126
|
-
tla build --verbose
|
|
146
|
+
tla modules build --verbose
|
|
127
147
|
```
|
|
128
148
|
|
|
129
|
-
|
|
149
|
+
The build command:
|
|
150
|
+
1. Resolves the project root (defaults to `workspace.root` from config).
|
|
151
|
+
2. Includes `lib/*.jar` files from the project root in the `javac` classpath.
|
|
152
|
+
3. Compiles `.java` files from the project's `modules/` directory into its `classes/` directory.
|
|
153
|
+
4. Generates the necessary Java service provider configuration for TLC overrides.
|
|
130
154
|
|
|
131
155
|
### Check Java Version
|
|
132
156
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tlaplus-cli"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.2"
|
|
8
8
|
description = "TLA+ tools: download TLC, compile custom modules, run model checker"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -101,6 +101,7 @@ ignore = [
|
|
|
101
101
|
[tool.ruff.lint.per-file-ignores]
|
|
102
102
|
"tests/*" = [
|
|
103
103
|
"ARG001", # Unused arguments are common in tests (fixtures)
|
|
104
|
+
"PLR0913", # Many arguments are common in tests (fixtures)
|
|
104
105
|
]
|
|
105
106
|
|
|
106
107
|
[tool.poe.tasks]
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"""Compile custom TLC modules (Java)."""
|
|
2
2
|
|
|
3
|
+
import os
|
|
3
4
|
import subprocess
|
|
5
|
+
from pathlib import Path
|
|
4
6
|
|
|
5
7
|
import typer
|
|
6
8
|
|
|
@@ -9,10 +11,12 @@ from tlaplus_cli.version_manager import get_pinned_version_dir
|
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
def build(
|
|
14
|
+
path: str | None = typer.Argument(None, help="Project root directory (defaults to workspace root)."),
|
|
12
15
|
verbose: bool = typer.Option(False, "--verbose", "-v", help="Show compilation output."),
|
|
13
16
|
) -> None:
|
|
14
17
|
"""Compile custom Java modules."""
|
|
15
18
|
config = load_config()
|
|
19
|
+
base_dir = Path(path).resolve() if path is not None else workspace_root()
|
|
16
20
|
|
|
17
21
|
# Fallback chain: pinned directory -> legacy jar
|
|
18
22
|
pinned_dir = get_pinned_version_dir()
|
|
@@ -20,31 +24,29 @@ def build(
|
|
|
20
24
|
legacy = cache_dir() / "tla2tools.jar"
|
|
21
25
|
jar_path = pinned_jar if (pinned_jar and pinned_jar.exists()) else legacy
|
|
22
26
|
|
|
23
|
-
ws_root = workspace_root()
|
|
24
|
-
modules_dir = ws_root / config.workspace.modules_dir
|
|
25
|
-
classes_dir = ws_root / config.workspace.classes_dir
|
|
26
|
-
|
|
27
27
|
if not jar_path.exists():
|
|
28
|
-
typer.echo("Error: tla2tools.jar not found.", err=True)
|
|
29
|
-
typer.echo("Run 'tla tlc install' first.", err=True)
|
|
28
|
+
typer.echo("Error: tla2tools.jar not found.\nRun 'tla tools install' first.", err=True)
|
|
30
29
|
raise typer.Exit(1)
|
|
31
30
|
|
|
31
|
+
modules_dir = base_dir / config.workspace.modules_dir
|
|
32
|
+
classes_dir = base_dir / config.workspace.classes_dir
|
|
33
|
+
|
|
34
|
+
lib_dir = base_dir / "lib"
|
|
35
|
+
lib_jars = sorted(lib_dir.glob("*.jar")) if lib_dir.is_dir() else []
|
|
36
|
+
classpath = os.pathsep.join([str(jar_path)] + [str(j) for j in lib_jars])
|
|
37
|
+
|
|
32
38
|
if not modules_dir.exists():
|
|
33
39
|
typer.echo(f"Error: modules directory not found: {modules_dir}", err=True)
|
|
34
40
|
raise typer.Exit(1)
|
|
35
41
|
|
|
36
|
-
# Find all .java files in modules dir
|
|
37
42
|
java_files = list(modules_dir.rglob("*.java"))
|
|
38
43
|
if not java_files:
|
|
39
44
|
typer.echo(f"No Java source files found in {modules_dir}", err=True)
|
|
40
45
|
return
|
|
41
46
|
|
|
42
47
|
typer.echo(f"Compiling {len(java_files)} Java files from {modules_dir} ...")
|
|
43
|
-
|
|
44
|
-
# Ensure output dir exists
|
|
45
48
|
classes_dir.mkdir(parents=True, exist_ok=True)
|
|
46
|
-
|
|
47
|
-
cmd = ["javac", "-cp", str(jar_path), "-d", str(classes_dir), *[str(f) for f in java_files]]
|
|
49
|
+
cmd = ["javac", "-cp", classpath, "-d", str(classes_dir), *[str(f) for f in java_files]]
|
|
48
50
|
|
|
49
51
|
try:
|
|
50
52
|
result = subprocess.run(cmd, check=True, capture_output=not verbose, text=True)
|
|
@@ -52,14 +54,11 @@ def build(
|
|
|
52
54
|
if verbose and result.stdout:
|
|
53
55
|
typer.echo(result.stdout)
|
|
54
56
|
|
|
55
|
-
# Create META-INF/services/tlc2.overrides.ITLCOverrides
|
|
56
57
|
meta_inf = classes_dir / "META-INF" / "services"
|
|
57
58
|
meta_inf.mkdir(parents=True, exist_ok=True)
|
|
58
59
|
service_file = meta_inf / "tlc2.overrides.ITLCOverrides"
|
|
59
|
-
|
|
60
60
|
with service_file.open("w") as f:
|
|
61
61
|
f.write(f"{config.tlc.overrides_class}\n")
|
|
62
|
-
|
|
63
62
|
typer.echo(f"Created service file at {service_file}")
|
|
64
63
|
|
|
65
64
|
except subprocess.CalledProcessError as e:
|
|
@@ -47,12 +47,15 @@ def root(
|
|
|
47
47
|
|
|
48
48
|
# --- Subcommands ---
|
|
49
49
|
|
|
50
|
-
# Attach typer subgroups
|
|
50
|
+
# Attach typer subgroups
|
|
51
|
+
modules_app = typer.Typer(name="modules", help="Manage TLA+ Java modules.", no_args_is_help=True)
|
|
52
|
+
modules_app.command(name="build")(build_tlc_cmd)
|
|
53
|
+
|
|
54
|
+
app.add_typer(modules_app, name="modules")
|
|
51
55
|
app.add_typer(tools_app, name="tools")
|
|
52
56
|
app.add_typer(fetch_cache_app, name="fetch-cache")
|
|
53
57
|
|
|
54
58
|
app.command(name="tlc")(run_tlc_cmd)
|
|
55
|
-
app.command(name="build")(build_tlc_cmd)
|
|
56
59
|
|
|
57
60
|
|
|
58
61
|
@app.command(name="check-java")
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Project root discovery utilities."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _is_project_root(directory: Path, modules_dir: Path | str, classes_dir: Path | str, lib_dir: Path | str) -> bool:
|
|
7
|
+
"""Return True if directory contains at least one project structure marker."""
|
|
8
|
+
return (
|
|
9
|
+
(directory / classes_dir).is_dir()
|
|
10
|
+
or (directory / modules_dir).is_dir()
|
|
11
|
+
or (directory / lib_dir).is_dir()
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def find_project_root(
|
|
16
|
+
spec_file: Path,
|
|
17
|
+
*,
|
|
18
|
+
modules_dir: Path | str,
|
|
19
|
+
classes_dir: Path | str,
|
|
20
|
+
lib_dir: Path | str = "lib",
|
|
21
|
+
) -> Path | None:
|
|
22
|
+
"""Resolve the project root relative to a .tla spec file.
|
|
23
|
+
|
|
24
|
+
Resolution strategy (in order):
|
|
25
|
+
1. Check the spec file's own directory.
|
|
26
|
+
2. Check the parent of the spec file's directory (handles spec/ and tests/ subdirs).
|
|
27
|
+
3. Return None if neither directory qualifies.
|
|
28
|
+
"""
|
|
29
|
+
spec_dir = spec_file.parent.resolve()
|
|
30
|
+
|
|
31
|
+
for candidate in (spec_dir, spec_dir.parent):
|
|
32
|
+
if _is_project_root(candidate, modules_dir, classes_dir, lib_dir):
|
|
33
|
+
return candidate
|
|
34
|
+
|
|
35
|
+
return None
|
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import subprocess
|
|
5
|
+
from pathlib import Path
|
|
5
6
|
|
|
6
7
|
import typer
|
|
7
8
|
|
|
8
9
|
from tlaplus_cli.check_java import check_java_version
|
|
9
|
-
from tlaplus_cli.config import cache_dir, load_config
|
|
10
|
+
from tlaplus_cli.config import cache_dir, load_config
|
|
11
|
+
from tlaplus_cli.project import find_project_root
|
|
10
12
|
from tlaplus_cli.version_manager import get_pinned_version_dir
|
|
11
13
|
|
|
12
14
|
|
|
@@ -62,25 +64,42 @@ def tlc(
|
|
|
62
64
|
jar_path = pinned_jar if (pinned_jar and pinned_jar.exists()) else legacy
|
|
63
65
|
|
|
64
66
|
if not jar_path.exists():
|
|
65
|
-
typer.echo("Error: tla2tools.jar not found.", err=True)
|
|
66
|
-
typer.echo("Run 'tla tools install' first.", err=True)
|
|
67
|
+
typer.echo("Error: tla2tools.jar not found.\nRun 'tla tools install' first.", err=True)
|
|
67
68
|
raise typer.Exit(1)
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
typer.echo(f"Error: specification not found: {spec_file}", err=True)
|
|
74
|
-
raise typer.Exit(1)
|
|
75
|
-
|
|
76
|
-
classpath = str(jar_path)
|
|
77
|
-
classes_path = ws_root / config.workspace.classes_dir
|
|
78
|
-
if classes_path.exists():
|
|
79
|
-
classpath = f"{classes_path}{os.pathsep}{classpath}"
|
|
70
|
+
# Spec resolution logic
|
|
71
|
+
spec_path = Path(spec)
|
|
72
|
+
candidates = [spec_path, spec_path.with_suffix(".tla"),
|
|
73
|
+
spec_path.parent / "spec" / (spec_path.name + ".tla")]
|
|
80
74
|
|
|
81
|
-
|
|
75
|
+
spec_file = next((c for c in candidates if c.is_file()), None)
|
|
76
|
+
if not spec_file:
|
|
77
|
+
typer.echo("Error: Could not find a TLA+ spec file. Looked in the following locations:", err=True)
|
|
78
|
+
for c in candidates:
|
|
79
|
+
typer.echo(f"- {c}", err=True)
|
|
80
|
+
raise typer.Exit(1)
|
|
82
81
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
spec_file = spec_file.absolute()
|
|
83
|
+
project_root = find_project_root(
|
|
84
|
+
spec_file, modules_dir=config.workspace.modules_dir, classes_dir=config.workspace.classes_dir
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
classpath_parts = [str(jar_path)]
|
|
88
|
+
extra_jvm_opts: list[str] = []
|
|
89
|
+
if project_root:
|
|
90
|
+
classes_path = project_root / config.workspace.classes_dir
|
|
91
|
+
if classes_path.is_dir():
|
|
92
|
+
classpath_parts.insert(0, str(classes_path))
|
|
93
|
+
lib_dir = project_root / "lib"
|
|
94
|
+
if lib_dir.is_dir():
|
|
95
|
+
classpath_parts.extend(str(j) for j in sorted(lib_dir.glob("*.jar")))
|
|
96
|
+
modules_path = project_root / config.workspace.modules_dir
|
|
97
|
+
if modules_path.is_dir():
|
|
98
|
+
extra_jvm_opts.append(f"-DTLA-Library={modules_path}")
|
|
99
|
+
|
|
100
|
+
cmd = ["java", *config.java.opts, *extra_jvm_opts, "-cp", os.pathsep.join(classpath_parts),
|
|
101
|
+
config.tlc.java_class, spec_file.name]
|
|
102
|
+
|
|
103
|
+
typer.echo(f"Running TLC on {spec_file.name} ...\nCommand: {' '.join(cmd)}")
|
|
104
|
+
result = subprocess.run(cmd, cwd=str(spec_file.parent))
|
|
86
105
|
raise typer.Exit(result.returncode)
|
|
@@ -205,7 +205,7 @@ def path(version: str = typer.Argument(None)) -> None:
|
|
|
205
205
|
if pinned_dir:
|
|
206
206
|
jar = pinned_dir / "tla2tools.jar"
|
|
207
207
|
if jar.exists():
|
|
208
|
-
_print_version_path(
|
|
208
|
+
_print_version_path(jar)
|
|
209
209
|
return
|
|
210
210
|
typer.echo("Error: No pinned version found.", err=True)
|
|
211
211
|
raise typer.Exit(1)
|
|
@@ -213,18 +213,15 @@ def path(version: str = typer.Argument(None)) -> None:
|
|
|
213
213
|
for lv in list_local_versions():
|
|
214
214
|
if lv.name == version:
|
|
215
215
|
jar = lv.path / "tla2tools.jar"
|
|
216
|
-
_print_version_path(
|
|
216
|
+
_print_version_path(jar)
|
|
217
217
|
return
|
|
218
218
|
typer.echo(f"Error: Version {version} not found locally.", err=True)
|
|
219
219
|
raise typer.Exit(1)
|
|
220
220
|
|
|
221
221
|
|
|
222
|
-
def _print_version_path(
|
|
223
|
-
"""Print the
|
|
224
|
-
|
|
225
|
-
if meta and meta.get("tlc2_version_string"):
|
|
226
|
-
typer.echo(meta["tlc2_version_string"])
|
|
227
|
-
typer.echo(str(jar_path))
|
|
222
|
+
def _print_version_path(jar_path: Path) -> None:
|
|
223
|
+
"""Print the absolute path to tla2tools.jar."""
|
|
224
|
+
typer.echo(str(jar_path.absolute()))
|
|
228
225
|
|
|
229
226
|
|
|
230
227
|
@tools_app.command()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tlaplus-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: TLA+ tools: download TLC, compile custom modules, run model checker
|
|
5
5
|
Author-email: Denis Nikolskiy <codeomatics@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -86,7 +86,7 @@ tla tools upgrade
|
|
|
86
86
|
> [!NOTE]
|
|
87
87
|
> If the target version to upgrade is not yet installed locally, the CLI will automatically download it.
|
|
88
88
|
|
|
89
|
-
Show the
|
|
89
|
+
Show the absolute path to the pinned version's `tla2tools.jar`:
|
|
90
90
|
```bash
|
|
91
91
|
tla tools path
|
|
92
92
|
```
|
|
@@ -98,7 +98,6 @@ tla tools path v1.8.0
|
|
|
98
98
|
|
|
99
99
|
Example output:
|
|
100
100
|
```text
|
|
101
|
-
TLC2 Version 2.19 of 08 August 2024 (rev: 5a47802)
|
|
102
101
|
/home/bob/.cache/tla/tools/v1.8.0-5a47802/tla2tools.jar
|
|
103
102
|
```
|
|
104
103
|
|
|
@@ -139,23 +138,48 @@ For example (runs `queue.tla`):
|
|
|
139
138
|
tla tlc queue
|
|
140
139
|
```
|
|
141
140
|
|
|
142
|
-
|
|
141
|
+
#### Project-Aware Execution
|
|
142
|
+
|
|
143
|
+
The CLI intelligently resolves the specification file and its project structure. It will check the following locations:
|
|
144
|
+
1. `<spec_name>` (if it's a direct path to a file, e.g., `models/my_model.tla`)
|
|
145
|
+
2. `<spec_name>.tla`
|
|
146
|
+
3. `spec/<spec_name>.tla` (checks an inner `spec/` folder relative to the spec's location)
|
|
147
|
+
|
|
148
|
+
Furthermore, it automatically discovers the **project root** by looking for `modules/`, `classes/`, or `lib/` directories adjacent to the spec file or its parent. When a project root is found:
|
|
149
|
+
- The project's `classes/` directory is added to the Java classpath.
|
|
150
|
+
- Any `*.jar` files found in the project's `lib/` directory are added to the Java classpath.
|
|
151
|
+
- The `-DTLA-Library` system property is set to the project's `modules/` directory, allowing TLC to find your custom Java overrides.
|
|
152
|
+
|
|
153
|
+
To check the currently pinned `tla2tools.jar` path and its TLC version:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
tla tlc --version
|
|
157
|
+
```
|
|
143
158
|
|
|
144
159
|
### Compile Custom Java Modules
|
|
145
160
|
|
|
146
|
-
|
|
161
|
+
Java modules (overrides) are compiled using the pinned version of the toolset.
|
|
162
|
+
|
|
163
|
+
Compile modules in the current project:
|
|
164
|
+
```bash
|
|
165
|
+
tla modules build
|
|
166
|
+
```
|
|
147
167
|
|
|
148
|
-
Compile modules:
|
|
168
|
+
Compile modules in a specific project path:
|
|
149
169
|
```bash
|
|
150
|
-
tla build
|
|
170
|
+
tla modules build path/to/project
|
|
151
171
|
```
|
|
152
172
|
|
|
153
173
|
Verbose output:
|
|
154
174
|
```bash
|
|
155
|
-
tla build --verbose
|
|
175
|
+
tla modules build --verbose
|
|
156
176
|
```
|
|
157
177
|
|
|
158
|
-
|
|
178
|
+
The build command:
|
|
179
|
+
1. Resolves the project root (defaults to `workspace.root` from config).
|
|
180
|
+
2. Includes `lib/*.jar` files from the project root in the `javac` classpath.
|
|
181
|
+
3. Compiles `.java` files from the project's `modules/` directory into its `classes/` directory.
|
|
182
|
+
4. Generates the necessary Java service provider configuration for TLC overrides.
|
|
159
183
|
|
|
160
184
|
### Check Java Version
|
|
161
185
|
|
|
@@ -7,6 +7,7 @@ src/tlaplus_cli/build_tlc_module.py
|
|
|
7
7
|
src/tlaplus_cli/check_java.py
|
|
8
8
|
src/tlaplus_cli/cli.py
|
|
9
9
|
src/tlaplus_cli/config.py
|
|
10
|
+
src/tlaplus_cli/project.py
|
|
10
11
|
src/tlaplus_cli/py.typed
|
|
11
12
|
src/tlaplus_cli/run_tlc.py
|
|
12
13
|
src/tlaplus_cli/settings.py
|
|
@@ -23,6 +24,8 @@ tests/test_build_tlc_module.py
|
|
|
23
24
|
tests/test_check_java.py
|
|
24
25
|
tests/test_cli.py
|
|
25
26
|
tests/test_java_module_naming.py
|
|
27
|
+
tests/test_project.py
|
|
26
28
|
tests/test_run_tlc.py
|
|
29
|
+
tests/test_spec_resolution.py
|
|
27
30
|
tests/test_tools_manager.py
|
|
28
31
|
tests/test_version_manager.py
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from typer.testing import CliRunner
|
|
5
|
+
|
|
6
|
+
from tlaplus_cli.cli import app
|
|
7
|
+
|
|
8
|
+
runner = CliRunner()
|
|
9
|
+
|
|
10
|
+
# Define paths relative to the test file
|
|
11
|
+
# Check if javac is available
|
|
12
|
+
JAVAC_AVAILABLE = shutil.which("javac") is not None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.mark.skipif(not JAVAC_AVAILABLE, reason="javac not found")
|
|
16
|
+
def test_build_integration(mocker, tmp_path, queue_dir, base_settings):
|
|
17
|
+
"""
|
|
18
|
+
Integration test for build_tlc_module.build().
|
|
19
|
+
Uses the actual files in tla-example and the real tla2tools.jar (if available).
|
|
20
|
+
Verified output classes are generated in a temporary directory.
|
|
21
|
+
"""
|
|
22
|
+
# Create a temporary classes directory
|
|
23
|
+
classes_dir = tmp_path / "classes"
|
|
24
|
+
|
|
25
|
+
# Configure base_settings for this test
|
|
26
|
+
base_settings.workspace.root = queue_dir
|
|
27
|
+
base_settings.workspace.classes_dir = classes_dir
|
|
28
|
+
|
|
29
|
+
# Patch load_config to return our mock config
|
|
30
|
+
mocker.patch("tlaplus_cli.build_tlc_module.load_config", return_value=base_settings)
|
|
31
|
+
|
|
32
|
+
# Patch workspace_root to return the example directory
|
|
33
|
+
mocker.patch("tlaplus_cli.build_tlc_module.workspace_root", return_value=queue_dir)
|
|
34
|
+
|
|
35
|
+
# Run build
|
|
36
|
+
result = runner.invoke(app, ["modules", "build", "--verbose"])
|
|
37
|
+
assert result.exit_code == 0, f"Build failed with {result.exit_code}: {result.stdout}"
|
|
38
|
+
|
|
39
|
+
# Verify outputs
|
|
40
|
+
assert classes_dir.exists(), "Classes directory not created"
|
|
41
|
+
|
|
42
|
+
# Verify TLCOverrides.class exists
|
|
43
|
+
# The source is modules/tlc2/overrides/TLCOverrides.java
|
|
44
|
+
# Package is tlc2.overrides, so output should be in tlc2/overrides/
|
|
45
|
+
output_class = classes_dir / "tlc2/overrides/TLCOverrides.class"
|
|
46
|
+
assert output_class.exists(), f"Class file not found at {output_class}"
|
|
47
|
+
|
|
48
|
+
# Verify META-INF service file
|
|
49
|
+
service_file = classes_dir / "META-INF/services/tlc2.overrides.ITLCOverrides"
|
|
50
|
+
assert service_file.exists(), "Service file not found"
|
|
51
|
+
|
|
52
|
+
content = service_file.read_text().strip()
|
|
53
|
+
assert content == "tlc2.overrides.TLCOverrides", f"Unexpected service file content: {content}"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@pytest.mark.skipif(not JAVAC_AVAILABLE, reason="javac not found")
|
|
57
|
+
def test_build_custom_overrides(mocker, tmp_path, queue_dir, base_settings):
|
|
58
|
+
"""
|
|
59
|
+
Test build with custom overrides class configuration.
|
|
60
|
+
"""
|
|
61
|
+
classes_dir = tmp_path / "classes"
|
|
62
|
+
|
|
63
|
+
# Configure custom override
|
|
64
|
+
custom_override = "com.example.MyOverrides"
|
|
65
|
+
base_settings.tlc.overrides_class = custom_override
|
|
66
|
+
base_settings.workspace.root = queue_dir
|
|
67
|
+
base_settings.workspace.classes_dir = classes_dir
|
|
68
|
+
|
|
69
|
+
mocker.patch("tlaplus_cli.build_tlc_module.load_config", return_value=base_settings)
|
|
70
|
+
mocker.patch("tlaplus_cli.build_tlc_module.workspace_root", return_value=queue_dir)
|
|
71
|
+
|
|
72
|
+
result = runner.invoke(app, ["modules", "build"])
|
|
73
|
+
assert result.exit_code == 0, f"Build failed: {result.stdout}"
|
|
74
|
+
|
|
75
|
+
service_file = classes_dir / "META-INF/services/tlc2.overrides.ITLCOverrides"
|
|
76
|
+
assert service_file.exists()
|
|
77
|
+
assert service_file.read_text().strip() == custom_override
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_build_with_explicit_path(mocker, tmp_path, base_settings):
|
|
81
|
+
"""modules build with explicit path uses that path as project root."""
|
|
82
|
+
project_dir = tmp_path / "my_project"
|
|
83
|
+
modules_dir = project_dir / "modules"
|
|
84
|
+
modules_dir.mkdir(parents=True)
|
|
85
|
+
(modules_dir / "Foo.java").write_text("class Foo {}")
|
|
86
|
+
|
|
87
|
+
mocker.patch("tlaplus_cli.build_tlc_module.load_config", return_value=base_settings)
|
|
88
|
+
mocker.patch("tlaplus_cli.build_tlc_module.workspace_root", return_value=tmp_path)
|
|
89
|
+
|
|
90
|
+
mock_run = mocker.patch("tlaplus_cli.build_tlc_module.subprocess.run")
|
|
91
|
+
mock_run.return_value.returncode = 0
|
|
92
|
+
|
|
93
|
+
# Patch jar path to exist
|
|
94
|
+
pinned_dir = tmp_path / "tools" / "v1.8.0"
|
|
95
|
+
pinned_dir.mkdir(parents=True)
|
|
96
|
+
(pinned_dir / "tla2tools.jar").write_bytes(b"fake")
|
|
97
|
+
mocker.patch("tlaplus_cli.build_tlc_module.get_pinned_version_dir", return_value=pinned_dir)
|
|
98
|
+
|
|
99
|
+
result = runner.invoke(app, ["modules", "build", str(project_dir)])
|
|
100
|
+
assert result.exit_code == 0
|
|
101
|
+
args, _ = mock_run.call_args
|
|
102
|
+
cmd = args[0]
|
|
103
|
+
assert str(project_dir / "classes") in cmd # -d target
|
|
104
|
+
assert str(modules_dir / "Foo.java") in cmd # source file
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_build_without_path_uses_workspace_root(mocker, tmp_path, base_settings):
|
|
108
|
+
"""modules build without path uses workspace_root from config."""
|
|
109
|
+
project_dir = tmp_path / "workspace"
|
|
110
|
+
project_dir.mkdir()
|
|
111
|
+
modules_dir = project_dir / "modules"
|
|
112
|
+
modules_dir.mkdir()
|
|
113
|
+
(modules_dir / "Bar.java").write_text("class Bar {}")
|
|
114
|
+
|
|
115
|
+
base_settings.workspace.root = project_dir
|
|
116
|
+
mocker.patch("tlaplus_cli.build_tlc_module.load_config", return_value=base_settings)
|
|
117
|
+
mocker.patch("tlaplus_cli.build_tlc_module.workspace_root", return_value=project_dir)
|
|
118
|
+
|
|
119
|
+
mock_run = mocker.patch("tlaplus_cli.build_tlc_module.subprocess.run")
|
|
120
|
+
mock_run.return_value.returncode = 0
|
|
121
|
+
|
|
122
|
+
# Patch jar path to exist
|
|
123
|
+
pinned_dir = tmp_path / "tools" / "v1.8.0"
|
|
124
|
+
pinned_dir.mkdir(parents=True)
|
|
125
|
+
(pinned_dir / "tla2tools.jar").write_bytes(b"fake")
|
|
126
|
+
mocker.patch("tlaplus_cli.build_tlc_module.get_pinned_version_dir", return_value=pinned_dir)
|
|
127
|
+
|
|
128
|
+
result = runner.invoke(app, ["modules", "build"])
|
|
129
|
+
assert result.exit_code == 0
|
|
130
|
+
args, _ = mock_run.call_args
|
|
131
|
+
cmd = args[0]
|
|
132
|
+
assert str(project_dir / "classes") in cmd
|
|
133
|
+
assert str(modules_dir / "Bar.java") in cmd
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def test_build_includes_lib_jars_in_classpath(mocker, tmp_path, base_settings):
|
|
137
|
+
"""javac classpath includes *.jar files from project lib/ directory."""
|
|
138
|
+
project_dir = tmp_path / "my_project"
|
|
139
|
+
(project_dir / "modules").mkdir(parents=True)
|
|
140
|
+
(project_dir / "modules" / "Foo.java").write_text("class Foo {}")
|
|
141
|
+
lib_dir = project_dir / "lib"
|
|
142
|
+
lib_dir.mkdir()
|
|
143
|
+
extra_jar = lib_dir / "extra.jar"
|
|
144
|
+
extra_jar.write_bytes(b"fake")
|
|
145
|
+
|
|
146
|
+
pinned_dir = tmp_path / "tools" / "v1.8.0"
|
|
147
|
+
pinned_dir.mkdir(parents=True)
|
|
148
|
+
tla_jar = pinned_dir / "tla2tools.jar"
|
|
149
|
+
tla_jar.write_bytes(b"fake")
|
|
150
|
+
|
|
151
|
+
mocker.patch("tlaplus_cli.build_tlc_module.load_config", return_value=base_settings)
|
|
152
|
+
mocker.patch("tlaplus_cli.build_tlc_module.get_pinned_version_dir", return_value=pinned_dir)
|
|
153
|
+
|
|
154
|
+
mock_run = mocker.patch("tlaplus_cli.build_tlc_module.subprocess.run")
|
|
155
|
+
mock_run.return_value.returncode = 0
|
|
156
|
+
|
|
157
|
+
result = runner.invoke(app, ["modules", "build", str(project_dir)])
|
|
158
|
+
assert result.exit_code == 0
|
|
159
|
+
args, _ = mock_run.call_args
|
|
160
|
+
cmd_str = " ".join(args[0])
|
|
161
|
+
assert str(extra_jar) in cmd_str
|
|
@@ -37,21 +37,21 @@ def setup_naming_env(tmp_path, mocker, base_settings, fixture_dir):
|
|
|
37
37
|
mocker.patch("tlaplus_cli.build_tlc_module.load_config", return_value=base_settings)
|
|
38
38
|
mocker.patch("tlaplus_cli.build_tlc_module.workspace_root", return_value=fixture_dir)
|
|
39
39
|
mocker.patch("tlaplus_cli.run_tlc.load_config", return_value=base_settings)
|
|
40
|
-
mocker.patch("tlaplus_cli.run_tlc.workspace_root", return_value=fixture_dir)
|
|
41
40
|
|
|
42
41
|
|
|
43
42
|
@pytest.mark.skipif(not JAVA_AVAILABLE, reason="java not found")
|
|
44
43
|
@pytest.mark.skipif(not JAVAC_AVAILABLE, reason="javac not found")
|
|
45
|
-
def test_tlc_overrides_naming_works(tmp_path, mocker, base_settings, capfd, naming_fixed_dir):
|
|
44
|
+
def test_tlc_overrides_naming_works(tmp_path, mocker, base_settings, capfd, naming_fixed_dir, monkeypatch):
|
|
46
45
|
"""
|
|
47
46
|
Tests that naming the class 'TLCOverrides' in 'tlc2.overrides' package works.
|
|
48
47
|
This is the currently supported approach.
|
|
49
48
|
"""
|
|
50
49
|
setup_naming_env(tmp_path, mocker, base_settings, naming_fixed_dir)
|
|
51
50
|
|
|
52
|
-
res_build = runner.invoke(app, ["build"])
|
|
51
|
+
res_build = runner.invoke(app, ["modules", "build"])
|
|
53
52
|
assert res_build.exit_code == 0, f"Module compilation failed: {res_build.stdout}"
|
|
54
53
|
|
|
54
|
+
monkeypatch.chdir(naming_fixed_dir)
|
|
55
55
|
res_tlc = runner.invoke(app, ["tlc", "test_spec"])
|
|
56
56
|
assert res_tlc.exit_code == 0, f"TLC run failed: {res_tlc.stdout}"
|
|
57
57
|
|
|
@@ -61,7 +61,7 @@ def test_tlc_overrides_naming_works(tmp_path, mocker, base_settings, capfd, nami
|
|
|
61
61
|
|
|
62
62
|
@pytest.mark.skipif(not JAVA_AVAILABLE, reason="java not found")
|
|
63
63
|
@pytest.mark.skipif(not JAVAC_AVAILABLE, reason="javac not found")
|
|
64
|
-
def test_module_name_class_naming_fails(tmp_path, mocker, base_settings, capfd, naming_dynamic_dir):
|
|
64
|
+
def test_module_name_class_naming_fails(tmp_path, mocker, base_settings, capfd, naming_dynamic_dir, monkeypatch):
|
|
65
65
|
"""
|
|
66
66
|
Tests that naming the class after the module ('TestModule') fails to load the override.
|
|
67
67
|
According to current TLC behavior, this doesn't work. We want this test to pass
|
|
@@ -69,9 +69,10 @@ def test_module_name_class_naming_fails(tmp_path, mocker, base_settings, capfd,
|
|
|
69
69
|
"""
|
|
70
70
|
setup_naming_env(tmp_path, mocker, base_settings, naming_dynamic_dir)
|
|
71
71
|
|
|
72
|
-
res_build = runner.invoke(app, ["build"])
|
|
72
|
+
res_build = runner.invoke(app, ["modules", "build"])
|
|
73
73
|
assert res_build.exit_code == 0, f"Module compilation failed: {res_build.stdout}"
|
|
74
74
|
|
|
75
|
+
monkeypatch.chdir(naming_dynamic_dir)
|
|
75
76
|
res_tlc = runner.invoke(app, ["tlc", "test_spec"])
|
|
76
77
|
assert res_tlc.exit_code == 0, f"TLC run failed: {res_tlc.stdout}"
|
|
77
78
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from tlaplus_cli.project import find_project_root
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_project_root_at_spec_dir(tmp_path):
|
|
5
|
+
"""Root found at spec's own directory when it contains classes/."""
|
|
6
|
+
spec = tmp_path / "queue.tla"
|
|
7
|
+
spec.write_text("MODULE queue\n===\n")
|
|
8
|
+
(tmp_path / "classes").mkdir()
|
|
9
|
+
root = find_project_root(spec, modules_dir="modules", classes_dir="classes", lib_dir="lib")
|
|
10
|
+
assert root == tmp_path
|
|
11
|
+
|
|
12
|
+
def test_project_root_at_parent_of_spec_dir(tmp_path):
|
|
13
|
+
"""Root found one level up when spec is inside a spec/ subdirectory."""
|
|
14
|
+
spec_subdir = tmp_path / "spec"
|
|
15
|
+
spec_subdir.mkdir()
|
|
16
|
+
spec = spec_subdir / "queue.tla"
|
|
17
|
+
spec.write_text("MODULE queue\n===\n")
|
|
18
|
+
(tmp_path / "classes").mkdir()
|
|
19
|
+
root = find_project_root(spec, modules_dir="modules", classes_dir="classes", lib_dir="lib")
|
|
20
|
+
assert root == tmp_path
|
|
21
|
+
|
|
22
|
+
def test_project_root_none_when_no_structure(tmp_path):
|
|
23
|
+
"""Returns None if neither spec dir nor its parent contain project directories."""
|
|
24
|
+
spec = tmp_path / "spec" / "queue.tla"
|
|
25
|
+
spec.parent.mkdir()
|
|
26
|
+
spec.write_text("MODULE queue\n===\n")
|
|
27
|
+
root = find_project_root(spec, modules_dir="modules", classes_dir="classes", lib_dir="lib")
|
|
28
|
+
assert root is None
|
|
29
|
+
|
|
30
|
+
def test_project_root_detected_by_lib(tmp_path):
|
|
31
|
+
"""Root found based on lib/ directory even without classes/."""
|
|
32
|
+
spec = tmp_path / "tests" / "queue_test.tla"
|
|
33
|
+
spec.parent.mkdir()
|
|
34
|
+
spec.write_text("MODULE queue_test\n===\n")
|
|
35
|
+
(tmp_path / "lib").mkdir()
|
|
36
|
+
root = find_project_root(spec, modules_dir="modules", classes_dir="classes", lib_dir="lib")
|
|
37
|
+
assert root == tmp_path
|
|
38
|
+
|
|
39
|
+
def test_project_root_detected_by_modules(tmp_path):
|
|
40
|
+
"""Root found based on modules/ directory alone."""
|
|
41
|
+
spec = tmp_path / "spec" / "queue.tla"
|
|
42
|
+
spec.parent.mkdir()
|
|
43
|
+
spec.write_text("MODULE queue\n===\n")
|
|
44
|
+
(tmp_path / "modules").mkdir()
|
|
45
|
+
root = find_project_root(spec, modules_dir="modules", classes_dir="classes", lib_dir="lib")
|
|
46
|
+
assert root == tmp_path
|
|
@@ -27,7 +27,7 @@ def compiled_modules(tmp_path_factory):
|
|
|
27
27
|
|
|
28
28
|
@pytest.mark.skipif(not JAVA_AVAILABLE, reason="java not found")
|
|
29
29
|
@pytest.mark.skipif(not JAVAC_AVAILABLE, reason="javac not found")
|
|
30
|
-
def test_tlc_integration(mocker, tmp_path, capfd, queue_dir, base_settings):
|
|
30
|
+
def test_tlc_integration(mocker, tmp_path, capfd, queue_dir, base_settings, monkeypatch):
|
|
31
31
|
"""
|
|
32
32
|
Integration test for run_tlc.tlc().
|
|
33
33
|
1. Compiles the modules (prerequisite).
|
|
@@ -41,7 +41,6 @@ def test_tlc_integration(mocker, tmp_path, capfd, queue_dir, base_settings):
|
|
|
41
41
|
base_settings.workspace.classes_dir = classes_dir
|
|
42
42
|
|
|
43
43
|
mocker.patch("tlaplus_cli.run_tlc.load_config", return_value=base_settings)
|
|
44
|
-
mocker.patch("tlaplus_cli.run_tlc.workspace_root", return_value=queue_dir)
|
|
45
44
|
|
|
46
45
|
# We also need to patch build_tlc_module's config loading to compile first
|
|
47
46
|
mocker.patch("tlaplus_cli.build_tlc_module.load_config", return_value=base_settings)
|
|
@@ -49,14 +48,14 @@ def test_tlc_integration(mocker, tmp_path, capfd, queue_dir, base_settings):
|
|
|
49
48
|
mocker.patch("tlaplus_cli.build_tlc_module.workspace_root", return_value=queue_dir)
|
|
50
49
|
|
|
51
50
|
# 1. Compile modules
|
|
52
|
-
res_build = runner.invoke(app, ["build"])
|
|
51
|
+
res_build = runner.invoke(app, ["modules", "build"])
|
|
53
52
|
assert res_build.exit_code == 0, f"Module compilation failed: {res_build.stdout}"
|
|
54
|
-
|
|
55
53
|
# 2. Run TLC
|
|
56
54
|
# Verify that classes_dir is populated
|
|
57
55
|
assert (classes_dir / "tlc2/overrides/TLCOverrides.class").exists()
|
|
58
56
|
|
|
59
57
|
# Run TLC on "queue" spec
|
|
58
|
+
monkeypatch.chdir(queue_dir)
|
|
60
59
|
res_tlc = runner.invoke(app, ["tlc", "queue"])
|
|
61
60
|
assert res_tlc.exit_code == 0, f"TLC run failed: {res_tlc.stdout}"
|
|
62
61
|
|
|
@@ -73,17 +72,17 @@ def test_tlc_integration(mocker, tmp_path, capfd, queue_dir, base_settings):
|
|
|
73
72
|
|
|
74
73
|
|
|
75
74
|
def test_tlc_version_flag(mocker, capfd, base_settings, tmp_path):
|
|
76
|
-
"""Test that 'tla tlc --version' prints the jar path and version."""
|
|
75
|
+
"""Test that 'tla tlc --version' prints the jar path and only the first line of TLC version."""
|
|
77
76
|
mocker.patch("tlaplus_cli.run_tlc.load_config", return_value=base_settings)
|
|
78
77
|
|
|
79
78
|
# Create a fake pinned version dir with a jar
|
|
80
|
-
pinned_dir = tmp_path / "tools" / "v1.8.0-abcdef1"
|
|
79
|
+
pinned_dir = (tmp_path / "tools" / "v1.8.0-abcdef1").absolute()
|
|
81
80
|
pinned_dir.mkdir(parents=True)
|
|
82
81
|
(pinned_dir / "tla2tools.jar").write_bytes(b"fake jar")
|
|
83
82
|
mocker.patch("tlaplus_cli.run_tlc.get_pinned_version_dir", return_value=pinned_dir)
|
|
84
83
|
|
|
85
84
|
mock_result = mocker.MagicMock()
|
|
86
|
-
mock_result.stdout = "TLC2 Version Mock
|
|
85
|
+
mock_result.stdout = "TLC2 Version Mock\nSome other output"
|
|
87
86
|
mock_result.stderr = ""
|
|
88
87
|
mocker.patch("tlaplus_cli.run_tlc.subprocess.run", return_value=mock_result)
|
|
89
88
|
|
|
@@ -92,5 +91,6 @@ def test_tlc_version_flag(mocker, capfd, base_settings, tmp_path):
|
|
|
92
91
|
|
|
93
92
|
captured = capfd.readouterr()
|
|
94
93
|
stdout = captured.out + result.stdout
|
|
95
|
-
assert "tla2tools.jar path:" in stdout
|
|
94
|
+
assert f"tla2tools.jar path: {pinned_dir / 'tla2tools.jar'}" in stdout
|
|
96
95
|
assert "TLC2 Version Mock" in stdout
|
|
96
|
+
assert "Some other output" not in stdout
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from typer.testing import CliRunner
|
|
5
|
+
|
|
6
|
+
from tlaplus_cli.cli import app
|
|
7
|
+
|
|
8
|
+
runner = CliRunner()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.fixture
|
|
12
|
+
def mock_tlc_env(mocker, tmp_path, base_settings):
|
|
13
|
+
mocker.patch("tlaplus_cli.run_tlc.load_config", return_value=base_settings)
|
|
14
|
+
mocker.patch("tlaplus_cli.run_tlc.check_java_version")
|
|
15
|
+
|
|
16
|
+
pinned_dir = (tmp_path / "tools" / "v1.8.0").absolute()
|
|
17
|
+
pinned_dir.mkdir(parents=True)
|
|
18
|
+
(pinned_dir / "tla2tools.jar").write_bytes(b"fake")
|
|
19
|
+
mocker.patch("tlaplus_cli.run_tlc.get_pinned_version_dir", return_value=pinned_dir)
|
|
20
|
+
|
|
21
|
+
mock_run = mocker.patch("tlaplus_cli.run_tlc.subprocess.run")
|
|
22
|
+
mock_run.return_value.returncode = 0
|
|
23
|
+
return mock_run
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_scenario_1_explicit_extension(mock_tlc_env, tmp_path):
|
|
27
|
+
"""Scenario 1: Model with explicit .tla extension in CWD."""
|
|
28
|
+
spec_name = "my_model.tla"
|
|
29
|
+
with runner.isolated_filesystem(temp_dir=tmp_path):
|
|
30
|
+
Path(spec_name).write_text("MODULE my_model\n===\n")
|
|
31
|
+
result = runner.invoke(app, ["tlc", spec_name])
|
|
32
|
+
|
|
33
|
+
assert result.exit_code == 0
|
|
34
|
+
args, _ = mock_tlc_env.call_args
|
|
35
|
+
assert "my_model.tla" in args[0]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_scenario_2_missing_extension_cwd(mock_tlc_env, tmp_path):
|
|
39
|
+
"""Scenario 2: Model with missing .tla extension in CWD."""
|
|
40
|
+
spec_name = "my_model"
|
|
41
|
+
with runner.isolated_filesystem(temp_dir=tmp_path):
|
|
42
|
+
Path(f"{spec_name}.tla").write_text("MODULE my_model\n===\n")
|
|
43
|
+
result = runner.invoke(app, ["tlc", spec_name])
|
|
44
|
+
|
|
45
|
+
assert result.exit_code == 0
|
|
46
|
+
args, _ = mock_tlc_env.call_args
|
|
47
|
+
assert "my_model.tla" in args[0]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_scenario_3_inside_spec_directory_cwd(mock_tlc_env, tmp_path):
|
|
51
|
+
"""Scenario 3: Model inside a spec/ subdirectory in CWD."""
|
|
52
|
+
spec_name = "my_model"
|
|
53
|
+
with runner.isolated_filesystem(temp_dir=tmp_path):
|
|
54
|
+
Path("spec").mkdir()
|
|
55
|
+
Path("spec/my_model.tla").write_text("MODULE my_model\n===\n")
|
|
56
|
+
result = runner.invoke(app, ["tlc", spec_name])
|
|
57
|
+
|
|
58
|
+
assert result.exit_code == 0
|
|
59
|
+
args, _ = mock_tlc_env.call_args
|
|
60
|
+
assert "my_model.tla" in args[0]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_scenario_4_spec_not_found_cwd(mock_tlc_env, tmp_path):
|
|
64
|
+
"""Scenario 4: Spec not found in CWD (validate multiline error)."""
|
|
65
|
+
spec_name = "missing_model"
|
|
66
|
+
with runner.isolated_filesystem(temp_dir=tmp_path):
|
|
67
|
+
result = runner.invoke(app, ["tlc", spec_name])
|
|
68
|
+
|
|
69
|
+
assert result.exit_code == 1
|
|
70
|
+
assert "Error: Could not find a TLA+ spec file. Looked in the following locations:" in result.output
|
|
71
|
+
assert f"- {spec_name}" in result.output
|
|
72
|
+
assert f"- {spec_name}.tla" in result.output
|
|
73
|
+
assert f"- spec/{spec_name}.tla" in result.output
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_scenario_5_missing_extension_path(mock_tlc_env, tmp_path):
|
|
77
|
+
"""Scenario 5: Spec missing .tla extension, but inside a specified path."""
|
|
78
|
+
spec_path = "subdir/my_model"
|
|
79
|
+
with runner.isolated_filesystem(temp_dir=tmp_path):
|
|
80
|
+
Path("subdir").mkdir()
|
|
81
|
+
Path("subdir/my_model.tla").write_text("MODULE my_model\n===\n")
|
|
82
|
+
result = runner.invoke(app, ["tlc", spec_path])
|
|
83
|
+
|
|
84
|
+
assert result.exit_code == 0
|
|
85
|
+
args, _ = mock_tlc_env.call_args
|
|
86
|
+
assert "my_model.tla" in args[0]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_scenario_6_inside_spec_directory_path(mock_tlc_env, tmp_path):
|
|
90
|
+
"""Scenario 6: Spec inside a spec/ subdirectory within a specified path."""
|
|
91
|
+
spec_path = "subdir/my_model"
|
|
92
|
+
with runner.isolated_filesystem(temp_dir=tmp_path):
|
|
93
|
+
Path("subdir/spec").mkdir(parents=True)
|
|
94
|
+
Path("subdir/spec/my_model.tla").write_text("MODULE my_model\n===\n")
|
|
95
|
+
result = runner.invoke(app, ["tlc", spec_path])
|
|
96
|
+
|
|
97
|
+
assert result.exit_code == 0
|
|
98
|
+
args, _ = mock_tlc_env.call_args
|
|
99
|
+
assert "my_model.tla" in args[0]
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_scenario_7_spec_not_found_path(mock_tlc_env, tmp_path):
|
|
103
|
+
"""Scenario 7: Spec not found with a path (validate exactly where it looked)."""
|
|
104
|
+
spec_path = "subdir/missing_model"
|
|
105
|
+
with runner.isolated_filesystem(temp_dir=tmp_path):
|
|
106
|
+
Path("subdir").mkdir()
|
|
107
|
+
result = runner.invoke(app, ["tlc", spec_path])
|
|
108
|
+
|
|
109
|
+
assert result.exit_code == 1
|
|
110
|
+
assert "Error: Could not find a TLA+ spec file. Looked in the following locations:" in result.output
|
|
111
|
+
assert f"- {spec_path}" in result.output
|
|
112
|
+
assert f"- {spec_path}.tla" in result.output
|
|
113
|
+
assert "- subdir/spec/missing_model.tla" in result.output
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_tlc_classpath_includes_project_classes(mock_tlc_env, tmp_path, base_settings, mocker):
|
|
117
|
+
"""When a classes/ directory exists next to the spec, it must appear in the JVM classpath."""
|
|
118
|
+
project_dir = tmp_path / "my_project"
|
|
119
|
+
spec_dir = project_dir
|
|
120
|
+
spec_dir.mkdir(parents=True)
|
|
121
|
+
(spec_dir / "queue.tla").write_text("MODULE queue\n===\n")
|
|
122
|
+
(project_dir / "classes").mkdir()
|
|
123
|
+
|
|
124
|
+
mocker.patch("tlaplus_cli.run_tlc.load_config", return_value=base_settings)
|
|
125
|
+
|
|
126
|
+
with runner.isolated_filesystem(temp_dir=tmp_path):
|
|
127
|
+
result = runner.invoke(app, ["tlc", str(spec_dir / "queue.tla")])
|
|
128
|
+
|
|
129
|
+
assert result.exit_code == 0
|
|
130
|
+
args, _ = mock_tlc_env.call_args
|
|
131
|
+
cmd = args[0]
|
|
132
|
+
cp_index = cmd.index("-cp")
|
|
133
|
+
classpath = cmd[cp_index + 1]
|
|
134
|
+
assert str(project_dir / "classes") in classpath
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def test_tlc_classpath_includes_lib_jars(mock_tlc_env, tmp_path, base_settings, mocker):
|
|
138
|
+
"""When a lib/ directory with JARs exists, they must appear in the JVM classpath."""
|
|
139
|
+
project_dir = tmp_path / "my_project"
|
|
140
|
+
spec_dir = project_dir / "spec"
|
|
141
|
+
spec_dir.mkdir(parents=True)
|
|
142
|
+
(spec_dir / "queue.tla").write_text("MODULE queue\n===\n")
|
|
143
|
+
lib_dir = project_dir / "lib"
|
|
144
|
+
lib_dir.mkdir()
|
|
145
|
+
extra_jar = lib_dir / "extra.jar"
|
|
146
|
+
extra_jar.write_bytes(b"fake")
|
|
147
|
+
|
|
148
|
+
mocker.patch("tlaplus_cli.run_tlc.load_config", return_value=base_settings)
|
|
149
|
+
|
|
150
|
+
with runner.isolated_filesystem(temp_dir=tmp_path):
|
|
151
|
+
result = runner.invoke(app, ["tlc", str(spec_dir / "queue.tla")])
|
|
152
|
+
|
|
153
|
+
assert result.exit_code == 0
|
|
154
|
+
args, _ = mock_tlc_env.call_args
|
|
155
|
+
cmd = args[0]
|
|
156
|
+
cp_index = cmd.index("-cp")
|
|
157
|
+
classpath = cmd[cp_index + 1]
|
|
158
|
+
assert str(extra_jar) in classpath
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def test_tlc_tla_library_set_when_modules_dir_exists(mock_tlc_env, tmp_path, base_settings, mocker):
|
|
162
|
+
"""-DTLA-Library is set when a modules/ directory is found."""
|
|
163
|
+
project_dir = tmp_path / "my_project"
|
|
164
|
+
spec_dir = project_dir / "tests"
|
|
165
|
+
spec_dir.mkdir(parents=True)
|
|
166
|
+
(spec_dir / "queue_test.tla").write_text("MODULE queue_test\n===\n")
|
|
167
|
+
modules_dir = project_dir / "modules"
|
|
168
|
+
modules_dir.mkdir()
|
|
169
|
+
|
|
170
|
+
mocker.patch("tlaplus_cli.run_tlc.load_config", return_value=base_settings)
|
|
171
|
+
|
|
172
|
+
with runner.isolated_filesystem(temp_dir=tmp_path):
|
|
173
|
+
result = runner.invoke(app, ["tlc", str(spec_dir / "queue_test.tla")])
|
|
174
|
+
|
|
175
|
+
assert result.exit_code == 0
|
|
176
|
+
args, _ = mock_tlc_env.call_args
|
|
177
|
+
cmd = args[0]
|
|
178
|
+
assert f"-DTLA-Library={modules_dir}" in cmd
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def test_tlc_no_project_dirs_no_extra_classpath(mock_tlc_env, tmp_path, base_settings, mocker):
|
|
182
|
+
"""A spec without adjacent project dirs has no extra classpath entries."""
|
|
183
|
+
spec_dir = tmp_path / "standalone"
|
|
184
|
+
spec_dir.mkdir()
|
|
185
|
+
(spec_dir / "simple.tla").write_text("MODULE simple\n===\n")
|
|
186
|
+
|
|
187
|
+
mocker.patch("tlaplus_cli.run_tlc.load_config", return_value=base_settings)
|
|
188
|
+
|
|
189
|
+
with runner.isolated_filesystem(temp_dir=tmp_path):
|
|
190
|
+
result = runner.invoke(app, ["tlc", str(spec_dir / "simple.tla")])
|
|
191
|
+
|
|
192
|
+
assert result.exit_code == 0
|
|
193
|
+
args, _ = mock_tlc_env.call_args
|
|
194
|
+
cmd = args[0]
|
|
195
|
+
cp_index = cmd.index("-cp")
|
|
196
|
+
classpath = cmd[cp_index + 1]
|
|
197
|
+
# Should only contain the tla2tools.jar
|
|
198
|
+
assert "tla2tools.jar" in classpath
|
|
199
|
+
assert "classes" not in classpath
|
|
200
|
+
assert "lib" not in classpath
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import shutil
|
|
3
|
+
from pathlib import Path
|
|
3
4
|
from unittest.mock import MagicMock
|
|
4
5
|
|
|
5
6
|
import pytest
|
|
@@ -374,23 +375,24 @@ def test_tlc_upgrade_already_up_to_date(mock_github_api, mock_cache, mock_load_c
|
|
|
374
375
|
|
|
375
376
|
|
|
376
377
|
def test_tlc_path_pinned_with_metadata(mock_load_config, mock_cache, installed_v180):
|
|
377
|
-
"""path
|
|
378
|
+
"""path strictly outputs the absolute path to tla2tools.jar, even if metadata exists."""
|
|
378
379
|
pin_file = mock_cache / "tools" / "tools-pinned-version.txt"
|
|
379
380
|
pin_file.write_text("v1.8.0-aaaaaaa")
|
|
380
381
|
# Write metadata
|
|
381
|
-
|
|
382
382
|
meta = {"tlc2_version_string": "TLC2 Version 2.19 of 08 August 2024 (rev: 5a47802)"}
|
|
383
383
|
(installed_v180 / "meta-tla2tools.json").write_text(json.dumps(meta))
|
|
384
384
|
|
|
385
385
|
result = runner.invoke(app, ["tools", "path"])
|
|
386
386
|
assert result.exit_code == 0
|
|
387
387
|
lines = result.stdout.strip().splitlines()
|
|
388
|
-
assert lines
|
|
389
|
-
assert "tla2tools.jar" in lines[
|
|
388
|
+
assert len(lines) == 1
|
|
389
|
+
assert "tla2tools.jar" in lines[0]
|
|
390
|
+
assert Path(lines[0]).is_absolute()
|
|
391
|
+
assert "TLC2 Version" not in result.stdout
|
|
390
392
|
|
|
391
393
|
|
|
392
394
|
def test_tlc_path_pinned_without_metadata(mock_load_config, mock_cache, installed_v180):
|
|
393
|
-
"""path
|
|
395
|
+
"""path strictly outputs the absolute path to tla2tools.jar when no metadata exists."""
|
|
394
396
|
pin_file = mock_cache / "tools" / "tools-pinned-version.txt"
|
|
395
397
|
pin_file.write_text("v1.8.0-aaaaaaa")
|
|
396
398
|
|
|
@@ -399,28 +401,31 @@ def test_tlc_path_pinned_without_metadata(mock_load_config, mock_cache, installe
|
|
|
399
401
|
lines = result.stdout.strip().splitlines()
|
|
400
402
|
assert len(lines) == 1
|
|
401
403
|
assert "tla2tools.jar" in lines[0]
|
|
404
|
+
assert Path(lines[0]).is_absolute()
|
|
402
405
|
|
|
403
406
|
|
|
404
407
|
def test_tlc_path_version_with_metadata(mock_load_config, mock_cache, installed_v180):
|
|
405
|
-
"""path <version>
|
|
406
|
-
|
|
408
|
+
"""path <version> strictly outputs the absolute path for a specific installed version."""
|
|
407
409
|
meta = {"tlc2_version_string": "TLC2 Version 2.19 of 08 August 2024 (rev: 5a47802)"}
|
|
408
410
|
(installed_v180 / "meta-tla2tools.json").write_text(json.dumps(meta))
|
|
409
411
|
|
|
410
412
|
result = runner.invoke(app, ["tools", "path", "v1.8.0"])
|
|
411
413
|
assert result.exit_code == 0
|
|
412
414
|
lines = result.stdout.strip().splitlines()
|
|
413
|
-
assert lines
|
|
414
|
-
assert "tla2tools.jar" in lines[
|
|
415
|
+
assert len(lines) == 1
|
|
416
|
+
assert "tla2tools.jar" in lines[0]
|
|
417
|
+
assert Path(lines[0]).is_absolute()
|
|
418
|
+
assert "TLC2 Version" not in result.stdout
|
|
415
419
|
|
|
416
420
|
|
|
417
421
|
def test_tlc_path_version_without_metadata(mock_load_config, mock_cache, installed_v180):
|
|
418
|
-
"""path <version>
|
|
422
|
+
"""path <version> strictly outputs the absolute path when no metadata exists."""
|
|
419
423
|
result = runner.invoke(app, ["tools", "path", "v1.8.0"])
|
|
420
424
|
assert result.exit_code == 0
|
|
421
425
|
lines = result.stdout.strip().splitlines()
|
|
422
426
|
assert len(lines) == 1
|
|
423
427
|
assert "tla2tools.jar" in lines[0]
|
|
428
|
+
assert Path(lines[0]).is_absolute()
|
|
424
429
|
|
|
425
430
|
|
|
426
431
|
def test_tlc_path_not_found(mock_load_config, mock_cache):
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import shutil
|
|
2
|
-
|
|
3
|
-
import pytest
|
|
4
|
-
from typer.testing import CliRunner
|
|
5
|
-
|
|
6
|
-
from tlaplus_cli.cli import app
|
|
7
|
-
|
|
8
|
-
runner = CliRunner()
|
|
9
|
-
|
|
10
|
-
# Define paths relative to the test file
|
|
11
|
-
# Check if javac is available
|
|
12
|
-
JAVAC_AVAILABLE = shutil.which("javac") is not None
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@pytest.mark.skipif(not JAVAC_AVAILABLE, reason="javac not found")
|
|
16
|
-
def test_build_integration(mocker, tmp_path, queue_dir, base_settings):
|
|
17
|
-
"""
|
|
18
|
-
Integration test for build_tlc_module.build().
|
|
19
|
-
Uses the actual files in tla-example and the real tla2tools.jar (if available).
|
|
20
|
-
Verified output classes are generated in a temporary directory.
|
|
21
|
-
"""
|
|
22
|
-
# Create a temporary classes directory
|
|
23
|
-
classes_dir = tmp_path / "classes"
|
|
24
|
-
|
|
25
|
-
# Configure base_settings for this test
|
|
26
|
-
base_settings.workspace.root = queue_dir
|
|
27
|
-
base_settings.workspace.classes_dir = classes_dir
|
|
28
|
-
|
|
29
|
-
# Patch load_config to return our mock config
|
|
30
|
-
mocker.patch("tlaplus_cli.build_tlc_module.load_config", return_value=base_settings)
|
|
31
|
-
|
|
32
|
-
# Patch workspace_root to return the example directory
|
|
33
|
-
mocker.patch("tlaplus_cli.build_tlc_module.workspace_root", return_value=queue_dir)
|
|
34
|
-
|
|
35
|
-
# Run build
|
|
36
|
-
result = runner.invoke(app, ["build", "--verbose"])
|
|
37
|
-
assert result.exit_code == 0, f"Build failed with {result.exit_code}: {result.stdout}"
|
|
38
|
-
|
|
39
|
-
# Verify outputs
|
|
40
|
-
assert classes_dir.exists(), "Classes directory not created"
|
|
41
|
-
|
|
42
|
-
# Verify TLCOverrides.class exists
|
|
43
|
-
# The source is modules/tlc2/overrides/TLCOverrides.java
|
|
44
|
-
# Package is tlc2.overrides, so output should be in tlc2/overrides/
|
|
45
|
-
output_class = classes_dir / "tlc2/overrides/TLCOverrides.class"
|
|
46
|
-
assert output_class.exists(), f"Class file not found at {output_class}"
|
|
47
|
-
|
|
48
|
-
# Verify META-INF service file
|
|
49
|
-
service_file = classes_dir / "META-INF/services/tlc2.overrides.ITLCOverrides"
|
|
50
|
-
assert service_file.exists(), "Service file not found"
|
|
51
|
-
|
|
52
|
-
content = service_file.read_text().strip()
|
|
53
|
-
assert content == "tlc2.overrides.TLCOverrides", f"Unexpected service file content: {content}"
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
@pytest.mark.skipif(not JAVAC_AVAILABLE, reason="javac not found")
|
|
57
|
-
def test_build_custom_overrides(mocker, tmp_path, queue_dir, base_settings):
|
|
58
|
-
"""
|
|
59
|
-
Test build with custom overrides class configuration.
|
|
60
|
-
"""
|
|
61
|
-
classes_dir = tmp_path / "classes"
|
|
62
|
-
|
|
63
|
-
# Configure custom override
|
|
64
|
-
custom_override = "com.example.MyOverrides"
|
|
65
|
-
base_settings.tlc.overrides_class = custom_override
|
|
66
|
-
base_settings.workspace.root = queue_dir
|
|
67
|
-
base_settings.workspace.classes_dir = classes_dir
|
|
68
|
-
|
|
69
|
-
mocker.patch("tlaplus_cli.build_tlc_module.load_config", return_value=base_settings)
|
|
70
|
-
mocker.patch("tlaplus_cli.build_tlc_module.workspace_root", return_value=queue_dir)
|
|
71
|
-
|
|
72
|
-
result = runner.invoke(app, ["build"])
|
|
73
|
-
assert result.exit_code == 0, f"Build failed: {result.stdout}"
|
|
74
|
-
|
|
75
|
-
service_file = classes_dir / "META-INF/services/tlc2.overrides.ITLCOverrides"
|
|
76
|
-
assert service_file.exists()
|
|
77
|
-
assert service_file.read_text().strip() == custom_override
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|