Skip to content

Commit 3f11add

Browse files
committed
feat: improve codegen testing and add verify target
- Add `make verify` for fast local validation suite - Add AGENTS.md with repository guidelines for AI assistants - Fix rust_writer.py: check for existing `impl Solution` before generating - Refactor test_code_generation.py: align with dev snippet fixture, add design problem test
1 parent 5279dc3 commit 3f11add

4 files changed

Lines changed: 112 additions & 47 deletions

File tree

AGENTS.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Repository Guidelines
2+
3+
## Project Structure & Module Organization
4+
5+
This repository stores LeetCode solutions, local runners, and code-generation tooling across several languages. Problem folders live under `problems/problems_<id>/` and usually contain `problem.md`, `problem_zh.md`, `solution.*`, `Solution.*`, `solution.md`, and testcase files. Shared language helpers are in `python/`, `golang/`, `typescript/`, `cpp/`, and `rust/`; reusable algorithm notes are in `algorithm_templates/`; metadata is in `data/`; automated tests are in `tests/`. Multithreading problems have their own layout under `multi_threading/<id>/`.
6+
7+
## Build, Test, and Development Commands
8+
9+
- `make verify`: run stable local checks across Python codegen/unit tests, TypeScript smoke tests, and Go helper packages.
10+
- `make test`: run the full Python pytest suite with `PYTHONPATH=.`.
11+
- `make test-unit`: run fast unit tests marked `unit`.
12+
- `make test-integration`: run integration tests that may require language runtimes.
13+
- `make test-codegen`: validate code generation behavior.
14+
- `make test-coverage`: run pytest with terminal and HTML coverage reports.
15+
- `npm test`: run the full TypeScript/Jest suite through `ts-jest`.
16+
- `make test-go-libs`: run stable Go helper package tests.
17+
- `python python/scripts/leetcode.py`: launch the interactive LeetCode helper for fetching/submitting problems.
18+
19+
Use `make help` for the maintained command list.
20+
21+
## Coding Style & Naming Conventions
22+
23+
Follow the style of the target language and existing generated files. Python uses pytest-compatible modules and 4-space indentation. Go code should be `gofmt` formatted. TypeScript uses lowercase filenames in `typescript/` and Jest test files such as `debug.test.ts`. Problem directories use `problems_<id>` naming, with language-specific solution names like `solution.py`, `solution.go`, `solution.ts`, `solution.rs`, `Solution.cpp`, and `Solution.java`.
24+
25+
## Testing Guidelines
26+
27+
Pytest discovers `test_*.py` and `*_test.py` under `tests/`; test classes start with `Test`, and test functions start with `test_`. Use markers from `pytest.ini`: `unit`, `integration`, `codegen`, and `slow`. For problem-level validation, run `make test-daily`, `make test-problems`, or the relevant language command. Add focused tests when changing code generation, language writers, shared models, or testcase parsing.
28+
29+
## Commit & Pull Request Guidelines
30+
31+
Recent commits use short prefixes such as `test:` and `fix:`; examples include `test: 3742 solution`, `test: [20260430] Add (3742)`, and `fix: 题解链接bug`. Keep messages imperative or descriptive, scoped to one change. Pull requests should summarize changed problems or tooling, list test commands run, mention required environment variables, and link related issues when applicable. Include screenshots only for documentation or UI-facing changes.
32+
33+
## Security & Configuration Tips
34+
35+
Do not commit `.env`, LeetCode cookies, PushDeer keys, or other secrets. Required local values include `COOKIE`, `PROBLEM_FOLDER`, `LANGUAGES`, `LEETCODE_USER`, and `PYTHONPATH=.`; see `README.md` for an example configuration.

Makefile

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
# Makefile for LeetCode project
22

3-
.PHONY: test test-unit test-integration test-codegen test-coverage clean help
3+
.PHONY: verify test test-unit test-integration test-codegen test-coverage test-typescript-smoke test-go-libs clean help
44

55
# Default target
66
help:
77
@echo "Available targets:"
8+
@echo " verify - Run the practical local verification suite"
89
@echo " test - Run all tests"
910
@echo " test-unit - Run unit tests only"
1011
@echo " test-integration - Run integration tests (requires language runtimes)"
1112
@echo " test-codegen - Run code generation tests"
1213
@echo " test-coverage - Run tests with coverage report"
1314
@echo " test-parallel - Run tests in parallel"
15+
@echo " test-typescript-smoke - Run stable TypeScript smoke tests"
16+
@echo " test-go-libs - Run stable Go helper package tests"
1417
@echo " test-daily - Run daily problem test (current problem)"
1518
@echo " test-problems - Run multiple problems test"
1619
@echo " clean - Clean up temporary files"
@@ -20,6 +23,12 @@ help:
2023
@echo " add-snippet - Add a problem to snippets (use PROBLEM=id)"
2124
@echo " print-snippet - Print original snippet (use PROBLEM=id LANG=lang)"
2225

26+
# Run the practical local verification suite
27+
verify:
28+
PYTHONPATH=. pytest tests/ -m "(unit or codegen) and not slow" -v
29+
npm test -- --runTestsByPath typescript/debug.test.ts
30+
go test ./golang/models ./golang/node_random ./golang/node_neighbours ./golang/tree_next ./golang/double_linked_node_child
31+
2332
# Run all unit tests
2433
test-unit:
2534
PYTHONPATH=. pytest tests/ -m unit -v
@@ -44,6 +53,14 @@ test-coverage:
4453
test-parallel:
4554
PYTHONPATH=. pytest tests/ -v -n auto
4655

56+
# Run stable TypeScript smoke tests
57+
test-typescript-smoke:
58+
npm test -- --runTestsByPath typescript/debug.test.ts
59+
60+
# Run stable Go helper package tests
61+
test-go-libs:
62+
go test ./golang/models ./golang/node_random ./golang/node_neighbours ./golang/tree_next ./golang/double_linked_node_child
63+
4764
# Run daily problem test
4865
test-daily:
4966
PYTHONPATH=. python python/test.py

python/lc_libs/rust_writer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def write_solution(
7474
if not RustWriter.is_snake_case(f"{problem_folder}_{problem_id}"):
7575
add_title = f"#![allow(non_snake_case)]\n"
7676
code = code or code_default
77-
if "object will be instantiated and called as such:" in code:
77+
if "object will be instantiated and called as such:" in code and "impl Solution" not in code:
7878
struct_map = RustWriter._parse_rust_structs(code_default)
7979
solve_part = RustWriter._generate_solve_function(struct_map)
8080
return SOLUTION_TEMPLATE_RUST.format(add_title, "\n".join([]), "",

tests/test_code_generation.py

Lines changed: 58 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@
2828
# Languages that are fully supported
2929
SUPPORTED_LANGUAGES = ["python3", "cpp", "java", "typescript", "golang", "rust"]
3030

31+
# These cases are intentionally sourced from python/dev/question_code_snippets.json.
32+
# Keep them aligned with that fixture so targeted codegen tests do not silently skip.
33+
DEV_SNIPPET_CASES = {
34+
"simple": "1",
35+
"list_node": "23",
36+
"tree_node": "919",
37+
"design": "1656",
38+
}
39+
3140

3241
def get_writer(lang_slug: str) -> Optional[lc_libs.LanguageWriter]:
3342
"""Get the Writer instance for a language slug."""
@@ -40,6 +49,14 @@ def get_writer(lang_slug: str) -> Optional[lc_libs.LanguageWriter]:
4049
return cls()
4150

4251

52+
def get_problem_snippets(question_snippets: List[Dict[str, Any]], problem_id: str) -> List[Dict[str, Any]]:
53+
"""Return snippet records for a problem that must exist in the dev fixture."""
54+
for item in question_snippets:
55+
if problem_id in item:
56+
return item[problem_id]
57+
raise AssertionError(f"Problem {problem_id} not found in python/dev/question_code_snippets.json")
58+
59+
4360
class TestCodeGeneration:
4461
"""Tests for code generation across languages."""
4562

@@ -68,17 +85,8 @@ def test_writer_has_solution_file(self, lang_slug: str):
6885
@pytest.mark.codegen
6986
def test_generate_simple_two_sum(self, question_snippets: List[Dict[str, Any]], temp_dir: Path):
7087
"""Test code generation for Two Sum problem (simple function signature)."""
71-
problem_id = "1"
72-
problem_data = None
73-
74-
# Find Two Sum in snippets
75-
for item in question_snippets:
76-
if problem_id in item:
77-
problem_data = item[problem_id]
78-
break
79-
80-
if problem_data is None:
81-
pytest.skip(f"Problem {problem_id} not found in snippets")
88+
problem_id = DEV_SNIPPET_CASES["simple"]
89+
problem_data = get_problem_snippets(question_snippets, problem_id)
8290

8391
for code_snippet in problem_data:
8492
lang_slug = code_snippet.get("langSlug", "")
@@ -105,17 +113,8 @@ def test_generate_simple_two_sum(self, question_snippets: List[Dict[str, Any]],
105113
@pytest.mark.codegen
106114
def test_generate_tree_node_problem(self, question_snippets: List[Dict[str, Any]], temp_dir: Path):
107115
"""Test code generation for a TreeNode problem."""
108-
# Problem 226: Invert Binary Tree
109-
problem_id = "226"
110-
problem_data = None
111-
112-
for item in question_snippets:
113-
if problem_id in item:
114-
problem_data = item[problem_id]
115-
break
116-
117-
if problem_data is None:
118-
pytest.skip(f"Problem {problem_id} not found in snippets")
116+
problem_id = DEV_SNIPPET_CASES["tree_node"]
117+
problem_data = get_problem_snippets(question_snippets, problem_id)
119118

120119
for code_snippet in problem_data:
121120
lang_slug = code_snippet.get("langSlug", "")
@@ -143,17 +142,8 @@ def test_generate_tree_node_problem(self, question_snippets: List[Dict[str, Any]
143142
@pytest.mark.codegen
144143
def test_generate_list_node_problem(self, question_snippets: List[Dict[str, Any]], temp_dir: Path):
145144
"""Test code generation for a ListNode problem."""
146-
# Problem 206: Reverse Linked List
147-
problem_id = "206"
148-
problem_data = None
149-
150-
for item in question_snippets:
151-
if problem_id in item:
152-
problem_data = item[problem_id]
153-
break
154-
155-
if problem_data is None:
156-
pytest.skip(f"Problem {problem_id} not found in snippets")
145+
problem_id = DEV_SNIPPET_CASES["list_node"]
146+
problem_data = get_problem_snippets(question_snippets, problem_id)
157147

158148
for code_snippet in problem_data:
159149
lang_slug = code_snippet.get("langSlug", "")
@@ -179,18 +169,43 @@ def test_generate_list_node_problem(self, question_snippets: List[Dict[str, Any]
179169
pytest.skip(f"Language {lang_slug} not fully implemented: {e}")
180170

181171
@pytest.mark.codegen
182-
@pytest.mark.slow
183-
def test_generate_all_snippets(self, question_snippets: List[Dict[str, Any]]):
184-
"""Test code generation for all problems in snippets.
172+
def test_generate_design_problem(self, question_snippets: List[Dict[str, Any]], temp_dir: Path):
173+
"""Test code generation for a class/design problem from the dev snippets."""
174+
problem_id = DEV_SNIPPET_CASES["design"]
175+
problem_data = get_problem_snippets(question_snippets, problem_id)
176+
177+
for code_snippet in problem_data:
178+
lang_slug = code_snippet.get("langSlug", "")
179+
if lang_slug not in SUPPORTED_LANGUAGES:
180+
continue
181+
182+
writer = get_writer(lang_slug)
183+
if writer is None:
184+
continue
185+
186+
try:
187+
generated = writer.write_solution(
188+
code_snippet["code"],
189+
None,
190+
format_question_id(problem_id),
191+
"problems"
192+
)
193+
assert generated, f"Generated code should not be empty for {lang_slug}"
194+
assert "orderedstream" in generated.lower() and "insert" in generated, \
195+
f"Generated code should preserve design/class structure for {lang_slug}"
196+
except NotImplementedError as e:
197+
pytest.skip(f"Language {lang_slug} not fully implemented: {e}")
185198

186-
This is a comprehensive test that validates all code snippets.
187-
Marked as slow, run with: pytest -m slow
188-
"""
199+
@pytest.mark.codegen
200+
def test_generate_all_snippets(self, question_snippets: List[Dict[str, Any]]):
201+
"""Test code generation for all supported snippets in python/dev."""
189202
errors = []
190203
generated_count = {}
204+
problems_seen = set()
191205

192206
for item in question_snippets:
193207
for problem_id, code_list in item.items():
208+
problems_seen.add(problem_id)
194209
for code_snippet in code_list:
195210
lang_slug = code_snippet.get("langSlug", "")
196211
if lang_slug not in SUPPORTED_LANGUAGES:
@@ -214,14 +229,12 @@ def test_generate_all_snippets(self, question_snippets: List[Dict[str, Any]]):
214229
except Exception as e:
215230
errors.append(f"Problem {problem_id} ({lang_slug}): {type(e).__name__}: {e}")
216231

217-
# Log summary
218232
print(f"\nCode generation summary: {generated_count}")
219233

220-
# Allow some errors but report them
234+
assert problems_seen, "No problems found in python/dev/question_code_snippets.json"
235+
assert generated_count, "No supported snippets generated from python/dev/question_code_snippets.json"
221236
if errors:
222-
error_rate = len(errors) / max(sum(generated_count.values()), 1)
223-
if error_rate > 0.1: # More than 10% failure rate
224-
pytest.fail(f"Too many code generation failures:\n" + "\n".join(errors[:10]))
237+
pytest.fail("Code generation failures:\n" + "\n".join(errors[:20]))
225238

226239

227240
class TestWriterEnvironment:
@@ -304,4 +317,4 @@ def test_resolve_circular_link(self, tmp_path: Path):
304317
json.dump({"link_to": "1", "link_folder": "problems"}, f)
305318

306319
with pytest.raises(ValueError, match="Circular link"):
307-
LanguageWriter._resolve_link(problem_1)
320+
LanguageWriter._resolve_link(problem_1)

0 commit comments

Comments
 (0)