Skip to content

Commit d89fdf2

Browse files
committed
Move conformance tests to plain_modules
1 parent 52b756a commit d89fdf2

25 files changed

Lines changed: 544 additions & 180 deletions

CLAUDE.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,12 @@ Two logging modes:
212212
### Code Generation Process
213213
- Templates in `standard_template_library/` use Liquid2 syntax
214214
- Template search order: (1) `.plain` file dir, (2) `--template-dir`, (3) built-in standard lib
215-
- Generated code written to `build/` (configurable via `--build-folder`)
216-
- Each FRID render produces a git commit in build folder for rollback capability
215+
- Each module renders into its own tree under the build folder (configurable via `--build-folder`, default `plain_modules/`):
216+
- `<build-folder>/<module>/code/` — implementation code (its own git repo)
217+
- `<build-folder>/<module>/tests/` — conformance tests (its own git repo; only created when a conformance tests script is configured)
218+
- `<build-folder>/<module>/.codeplain/` — module metadata (`module_metadata.json`), not tracked in git
219+
- `<build-folder>/<module>/.memory/` — conformance test memory, not tracked in git
220+
- Each FRID render produces a git commit in the `code/` and `tests/` repos for rollback capability
217221

218222
### Configuration
219223
- `config.yaml` can be placed in `.plain` file dir or CWD

docs/plain2code_cli.md

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ usage: generate_cli.py [-h] [--verbose] [--base-folder BASE_FOLDER]
99
[--render-range RENDER_RANGE |
1010
--render-from RENDER_FROM] [--force-render]
1111
[--unittests-script UNITTESTS_SCRIPT]
12-
[--conformance-tests-folder CONFORMANCE_TESTS_FOLDER]
1312
[--conformance-tests-script CONFORMANCE_TESTS_SCRIPT]
1413
[--prepare-environment-script PREPARE_ENVIRONMENT_SCRIPT]
1514
[--test-script-timeout TEST_SCRIPT_TIMEOUT]
@@ -20,8 +19,8 @@ usage: generate_cli.py [-h] [--verbose] [--base-folder BASE_FOLDER]
2019
[--conformance-tests-dest CONFORMANCE_TESTS_DEST]
2120
[--render-machine-graph]
2221
[--logging-config-path LOGGING_CONFIG_PATH]
23-
[--headless]
24-
filename
22+
[--headless] [--status] [--version]
23+
[filename]
2524
2625
Render plain code to target code. Path arguments resolve based on where they
2726
were written: values given on the command line are resolved against the
@@ -39,7 +38,7 @@ positional arguments:
3938
4039
options:
4140
-h, --help show this help message and exit
42-
--verbose, -v Enable verbose output
41+
--verbose, -v Set default log level to DEBUG for TUI and file logs
4342
--base-folder BASE_FOLDER
4443
Base folder for the build files
4544
--build-folder BUILD_FOLDER
@@ -66,18 +65,17 @@ options:
6665
--force-render Force re-render of all the required modules.
6766
--unittests-script UNITTESTS_SCRIPT
6867
Shell script to run unit tests on generated code.
69-
Receives the build folder path as its first argument
70-
(default: 'plain_modules').
71-
--conformance-tests-folder CONFORMANCE_TESTS_FOLDER
72-
Folder for conformance test files
68+
Receives the module's code folder path as its first
69+
argument (e.g. `plain_modules/module_name/code`).
7370
--conformance-tests-script CONFORMANCE_TESTS_SCRIPT
7471
Path to conformance tests shell script. Every
7572
conformance test script should accept two arguments:
76-
1) Path to a folder (e.g. `plain_modules/module_name`)
77-
containing generated source code, 2) Path to a
78-
subfolder of the conformance tests folder (e.g.
79-
`conformance_tests/subfoldername`) containing test
80-
files.
73+
1) Path to a folder (e.g.
74+
`plain_modules/module_name/code`) containing generated
75+
source code, 2) Path to a subfolder of the module's
76+
tests folder (e.g.
77+
`plain_modules/module_name/tests/subfoldername`)
78+
containing test files.
8179
--prepare-environment-script PREPARE_ENVIRONMENT_SCRIPT
8280
Path to a shell script that prepares the testing
8381
environment. The script should accept the source code
@@ -90,9 +88,9 @@ options:
9088
--api-key API_KEY API key used to access the API. If not provided, the
9189
`CODEPLAIN_API_KEY` environment variable is used.
9290
--full-plain Full preview ***plain specification before code
93-
generation.Use when you want to preview context of all
94-
***plain primitives that are going to be included in
95-
order to render the given module.
91+
generation. Use when you want to preview context of
92+
all ***plain primitives that are going to be included
93+
in order to render the given module.
9694
--dry-run Dry run preview of the code generation (without
9795
actually making any changes).
9896
--replay-with REPLAY_WITH
@@ -109,10 +107,10 @@ options:
109107
Target folder to copy rendered contents of code to
110108
(used only if --copy-build is set).
111109
--copy-conformance-tests
112-
If set, copy the conformance tests of code in
113-
`--conformance-tests-folder` folder to `--conformance-
114-
tests-dest` folder successful rendering. Requires
115-
--conformance-tests-script.
110+
If set, copy the module's conformance tests (from
111+
`<build-folder>/<module>/tests`) to `--conformance-
112+
tests-dest` folder after successful rendering.
113+
Requires --conformance-tests-script.
116114
--conformance-tests-dest CONFORMANCE_TESTS_DEST
117115
Target folder to copy conformance tests of code to
118116
(used only if --copy-conformance-tests is set).
@@ -123,6 +121,10 @@ options:
123121
--headless Run in headless mode: no TUI, no terminal output
124122
except a single render-started message. All logs are
125123
written to the log file.
124+
--status Display account status including user information, API
125+
key label, and rendering credits. Does not render any
126+
code.
127+
--version Display the client version and exit.
126128
127129
configuration:
128130
--config-name CONFIG_NAME

docs/starting_a_plain_project_from_scratch.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,16 @@ my-new-project/
2222
├── run_unittests_[language].sh # Unit test script
2323
├── run_conformance_tests_[language].sh # Conformance test script
2424
├── build/ # Generated final code
25-
├── plain_modules/ # Generated modules code
26-
└── conformance_tests/ # Generated conformanece tests code
25+
└── plain_modules/ # Generated modules
26+
└── my_app/ # One folder per module
27+
├── .codeplain/ # Module metadata
28+
├── .memory/ # Conformance test memory
29+
├── code/ # Generated code
30+
└── tests/ # Generated conformance tests
2731
```
2832

33+
The `tests/` folder is only created when a conformance test script is configured.
34+
2935
In this guide we will cover how to create each of these step by step.
3036

3137
## 1. Define Your .plain File

examples/example_hello_world_golang/run.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ if [ $? -ne 0 ]; then
2020
exit 1
2121
fi
2222

23-
cd plain_modules/hello_world_golang
23+
cd plain_modules/hello_world_golang/code
2424

2525
# We need to compile the tests so that we can execute them in the current folder
2626
# (https://stackoverflow.com/questions/23847003/golang-tests-and-working-directory/29541248#29541248)
27-
go test -c ../../harness_tests/hello_world_test.go
27+
go test -c ../../../harness_tests/hello_world_test.go
2828

2929
# Check if test compilation has failed for the hello world example
3030
if [ $? -ne 0 ]; then

examples/example_hello_world_python/run.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ if [ $? -ne 0 ]; then
77
exit 1
88
fi
99

10-
cd plain_modules/hello_world_python
10+
cd plain_modules/hello_world_python/code
1111

12-
python ../../harness_tests/hello_world_display/test_hello_world.py
12+
python ../../../harness_tests/hello_world_display/test_hello_world.py
1313

1414
# Check if the test harness has failed for the hello world example
1515
if [ $? -ne 0 ]; then

examples/example_hello_world_react/run.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ if [ $? -ne 0 ]; then
2020
exit 1
2121
fi
2222

23-
../../test_scripts/run_conformance_tests_cypress.sh plain_modules/hello_world_react harness_tests/hello_world_display ${VERBOSE:+-v}
23+
../../test_scripts/run_conformance_tests_cypress.sh plain_modules/hello_world_react/code harness_tests/hello_world_display ${VERBOSE:+-v}
2424

2525
# Check if the test harness has failed for the hello world example
2626
if [ $? -ne 0 ]; then

git_utils.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ def init_git_repo(
6464
path_to_repo: Union[str, os.PathLike],
6565
module_name: Optional[str] = None,
6666
render_id: Optional[str] = None,
67-
initial_files: Optional[dict[str, str]] = None,
6867
) -> Repo:
6968
"""
7069
Initializes a new git repository in the given path.
@@ -79,10 +78,6 @@ def init_git_repo(
7978
repo = Repo.init(path_to_repo)
8079
_ensure_git_config(repo)
8180

82-
if initial_files:
83-
file_utils.store_response_files(path_to_repo, initial_files, [])
84-
repo.git.add(".")
85-
8681
repo.git.commit(
8782
"--allow-empty", "-m", _get_full_commit_message(INITIAL_COMMIT_MESSAGE, module_name, None, render_id)
8883
)
@@ -95,14 +90,9 @@ def clone_repo(
9590
new_repo_path: str,
9691
module_name: Optional[str] = None,
9792
render_id: Optional[str] = None,
98-
initial_files: Optional[dict[str, str]] = None,
9993
) -> Repo:
10094
repo = Repo.clone_from(source_repo_path, new_repo_path)
10195

102-
if initial_files:
103-
file_utils.store_response_files(new_repo_path, initial_files, [])
104-
repo.git.add(".")
105-
10696
repo.git.commit(
10797
"--allow-empty", "-m", _get_full_commit_message(INITIAL_COMMIT_MESSAGE, module_name, None, render_id)
10898
)

memory_management.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
import file_utils
55
from plain2code_console import console
6-
from plain_modules import CODEPLAIN_MEMORY_SUBFOLDER
76
from render_machine.implementation_code_helpers import ImplementationCodeHelpers
87
from render_machine.render_context import RenderContext
98

@@ -24,9 +23,9 @@ def fetch_memory_files(memory_folder: str) -> tuple[list[str], dict[str, str]]:
2423
console.debug(f"Loaded {len(memory_files_content)} memory files.")
2524
return memory_files, memory_files_content
2625

27-
def __init__(self, codeplain_api, module_name: str, conformance_tests_folder: str):
26+
def __init__(self, codeplain_api, memory_folder: str):
2827
self.codeplain_api = codeplain_api
29-
self.memory_folder = os.path.join(conformance_tests_folder, module_name, CODEPLAIN_MEMORY_SUBFOLDER)
28+
self.memory_folder = memory_folder
3029

3130
def create_conformance_tests_memory(
3231
self, render_context: RenderContext, exit_code: int, conformance_tests_issue: str

metadata_utils.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""Read/write helpers for the per-module metadata file (``module_metadata.json``).
2+
3+
These helpers operate purely on a metadata path or a plain dict. Module-specific
4+
logic — computing the hashes, assembling the payload from the spec and required
5+
modules — stays on :class:`plain_modules.PlainModule`.
6+
"""
7+
8+
from __future__ import annotations
9+
10+
import json
11+
import os
12+
13+
MODULE_METADATA_FILENAME = "module_metadata.json"
14+
MODULE_FUNCTIONALITIES = "functionalities"
15+
REQUIRED_MODULES_FUNCTIONALITIES = "required_modules_functionalities"
16+
17+
18+
def load_metadata(metadata_path: str) -> dict | None:
19+
"""Return the parsed metadata dict, or None if the file does not exist."""
20+
if not os.path.exists(metadata_path):
21+
return None
22+
23+
with open(metadata_path, "r", encoding="utf-8") as f:
24+
return json.load(f)
25+
26+
27+
def write_metadata(metadata_path: str, metadata: dict) -> None:
28+
"""Write metadata as indented JSON, creating the parent folder if needed."""
29+
os.makedirs(os.path.dirname(metadata_path), exist_ok=True)
30+
31+
with open(metadata_path, "w", encoding="utf-8") as f:
32+
json.dump(metadata, f, indent=4)
33+
34+
35+
def truncate_functionalities(metadata: dict, keep_count: int) -> bool:
36+
"""Trim the functionalities list in ``metadata`` in place to ``keep_count`` entries.
37+
38+
Returns True if the list was shortened, False if it was already short enough
39+
(in which case ``metadata`` is left untouched).
40+
"""
41+
functionalities = metadata.get(MODULE_FUNCTIONALITIES, [])
42+
if len(functionalities) <= keep_count:
43+
return False
44+
45+
metadata[MODULE_FUNCTIONALITIES] = functionalities[:keep_count]
46+
return True

module_renderer.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import argparse
2-
import os
32
import threading
43

54
from event_bus import EventBus
@@ -48,9 +47,8 @@ def _build_render_context_for_module(
4847
self.codeplainAPI,
4948
memory_manager,
5049
plain_module,
51-
build_folder=os.path.join(self.args.build_folder, plain_module.module_name),
50+
build_folder=plain_module.module_build_folder,
5251
build_dest=self.args.build_dest,
53-
conformance_tests_folder=self.args.conformance_tests_folder,
5452
conformance_tests_dest=self.args.conformance_tests_dest,
5553
unittests_script=self.args.unittests_script,
5654
conformance_tests_script=self.args.conformance_tests_script,
@@ -115,8 +113,7 @@ def _render_module(
115113

116114
memory_manager = MemoryManager(
117115
self.codeplainAPI,
118-
plain_module.module_name,
119-
self.args.conformance_tests_folder,
116+
plain_module.module_memory_folder,
120117
)
121118
render_context = self._build_render_context_for_module(
122119
plain_module,
@@ -165,7 +162,7 @@ def render_module(self) -> None:
165162
if self.args.copy_build:
166163
rendered_code_path = f"{self.args.build_dest}/"
167164
else:
168-
rendered_code_path = self.args.build_folder
165+
rendered_code_path = self.plain_module.module_build_folder
169166

170167
self.run_state.set_render_generated_code_path(rendered_code_path)
171168
self.event_bus.publish(RenderCompleted(rendered_code_path=rendered_code_path))

0 commit comments

Comments
 (0)