Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 34 additions & 21 deletions .github/scripts/generate_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,46 +40,60 @@ def file_name_without_ext(path: str) -> str:
return os.path.basename(path).replace(".kt", "")


def build_prompt(file_path: str, content: str, package_name: str) -> str:
class_name = file_name_without_ext(file_path)
return f"""You are an expert Kotlin developer specializing in unit testing for Kotlin Multiplatform (KMP) projects.
# Static system prompt shared across all files in a batch.
# Marked with cache_control so Claude caches it after the first call,
# saving ~70% on token costs when processing multiple files.
_SYSTEM_PROMPT = """\
You are an expert Kotlin developer specializing in unit testing for Kotlin Multiplatform (KMP) projects.

## Project context
- **CraftD** is a Server Driven UI library for Android/KMP.
- The server returns a list of `SimpleProperties` (key + value as `JsonElement`).
- The lib renders native components dynamically via `CraftDBuilder`.
- Stack: Kotlin, JUnit4, MockK, kotlinx.serialization, Jetpack Compose.

## Your task
Generate comprehensive unit tests for the Kotlin source file below.

## Requirements
## Requirements (apply to every file)
- Framework: **JUnit4** (`@RunWith(JUnit4::class)`) + **MockK** (`io.mockk.*`)
- Package: `{package_name}`
- Test class name: `{class_name}Test`
- Test method style: backtick notation — e.g. `` `given null JsonElement when convertToElement then returns null`() ``
- **Data classes**: test construction with all params, default values, `copy()`, `equals`/`hashCode`.
- **Extension functions with JsonElement**: test null input, valid JSON deserialization, malformed/wrong-type JSON (expect `null` return).
- **DiffUtil callbacks**: test `areItemsTheSame` (same key, different key) and `areContentsTheSame` (equal value, different value, `AbstractMap` comparison edge case).
- **Enums**: verify all enum constant names exist using `enumValueOf`.
- **Mapper functions** (`toSimpleProperties`, `toListSimpleProperties`): test with sample data, empty list, and null-value fields.
- Do **not** include explanations or markdown fences.
- Output **only** the complete Kotlin test file, starting with `package {package_name}`.

## Source file
Path: `{file_path}`

```kotlin
{content}
```
- Output **only** the complete Kotlin test file, starting with `package <package>`.\
"""


def call_claude(client: anthropic.Anthropic, prompt: str) -> str:
def call_claude(
client: anthropic.Anthropic,
file_path: str,
content: str,
package_name: str,
) -> str:
class_name = file_name_without_ext(file_path)
message = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=4096,
messages=[{"role": "user", "content": prompt}],
system=[
{
"type": "text",
"text": _SYSTEM_PROMPT,
"cache_control": {"type": "ephemeral"},
}
],
messages=[
{
"role": "user",
"content": (
f"Generate unit tests for the Kotlin source file below.\n\n"
f"Package: `{package_name}`\n"
f"Test class name: `{class_name}Test`\n\n"
f"## Source file\nPath: `{file_path}`\n\n"
f"```kotlin\n{content}\n```"
),
}
],
)
return message.content[0].text

Expand Down Expand Up @@ -151,8 +165,7 @@ def main() -> None:
continue

try:
prompt = build_prompt(file_path, content, package_name)
test_content = call_claude(client, prompt)
test_content = call_claude(client, file_path, content, package_name)

os.makedirs(os.path.dirname(test_path), exist_ok=True)
with open(test_path, "w", encoding="utf-8") as f:
Expand Down
Loading