Skip to content

Commit 31b2aba

Browse files
committed
Add AI-powered Shiny test generation and evaluation
Introduces an AI-powered test generator (with CLI integration), evaluation suite with sample apps and scripts, and utility tools for documentation and quality control. Updates the CLI to support 'shiny generate test' for automated test creation using Anthropic or OpenAI models. Adds extensive documentation and example apps for robust evaluation and development workflows.
1 parent e51ffbb commit 31b2aba

File tree

41 files changed

+3958
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+3958
-0
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
name: Testing Documentation Update
2+
3+
on:
4+
push:
5+
paths:
6+
- 'docs/api/testing/**'
7+
- 'docs/_quartodoc-testing.yml'
8+
pull_request:
9+
paths:
10+
- 'docs/api/testing/**'
11+
- 'docs/_quartodoc-testing.yml'
12+
13+
jobs:
14+
update-testing-docs:
15+
runs-on: ubuntu-latest
16+
17+
steps:
18+
- name: Checkout repository
19+
uses: actions/checkout@v4
20+
21+
- name: Setup Node.js
22+
uses: actions/setup-node@v4
23+
with:
24+
node-version: '25'
25+
cache: 'npm'
26+
27+
- name: Install repomix
28+
run: npm install -g repomix
29+
30+
- name: Verify repomix installation
31+
run: repomix --version
32+
33+
- name: Setup Python
34+
uses: actions/setup-python@v4
35+
with:
36+
python-version: '3.12'
37+
38+
- name: Install Python dependencies
39+
run: |
40+
python -m pip install --upgrade pip
41+
make install-deps
42+
make install
43+
44+
- name: Run testing documentation processing
45+
run: |
46+
echo "Processing testing documentation changes..."
47+
repomix docs/api/testing -o shiny/testing/utils/scripts/repomix-output-testing.xml
48+
python shiny/testing/utils/scripts/process_docs.py --input shiny/testing/utils/scripts/repomix-output-testing.xml --output shiny/testing/generator/data/docs/documentation_testing.json
49+
50+
- name: Check for changes
51+
id: git-check
52+
run: |
53+
git diff --exit-code || echo "changes=true" >> $GITHUB_OUTPUT
54+
55+
- name: Commit and push changes
56+
if: steps.git-check.outputs.changes == 'true'
57+
run: |
58+
git config --local user.email "[email protected]"
59+
git config --local user.name "GitHub Action"
60+
git add .
61+
git commit -m "Auto-update testing documentation" || exit 0
62+
git push

pyrightconfig.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
"tests/playwright/deploys/*/app.py",
1212
"shiny/templates",
1313
"tests/playwright/ai_generated_apps",
14+
"shiny/testing/evaluation",
15+
"shiny/testing/generator",
16+
"shiny/testing/utils",
1417
],
1518
"typeCheckingMode": "strict",
1619
"reportImportCycles": "none",

shiny/_main.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,11 @@ def add() -> None:
530530
pass
531531

532532

533+
@main.group(help="""Generate files for your Shiny app using AI.""")
534+
def generate() -> None:
535+
pass
536+
537+
533538
@add.command(
534539
help="""Add a test file for a specified Shiny app.
535540
@@ -564,6 +569,52 @@ def test(
564569
add_test_file(app_file=app, test_file=test_file)
565570

566571

572+
@generate.command(
573+
"test",
574+
help="""Generate AI-powered test file for a specified Shiny app.
575+
576+
Generate a comprehensive test file for a specified app using AI. The generator
577+
will analyze your app code and create appropriate test cases with assertions.
578+
579+
After creating the test file, you can use `pytest` to run the tests:
580+
581+
pytest TEST_FILE
582+
""",
583+
)
584+
@click.option(
585+
"--app",
586+
"-a",
587+
type=str,
588+
help="Path to the app file for which you want to generate a test file.",
589+
)
590+
@click.option(
591+
"--output",
592+
"-o",
593+
type=str,
594+
help="Path for the generated test file. If not provided, will be auto-generated.",
595+
)
596+
@click.option(
597+
"--provider",
598+
type=click.Choice(["anthropic", "openai"]),
599+
default="anthropic",
600+
help="AI provider to use for test generation.",
601+
)
602+
@click.option(
603+
"--model",
604+
type=str,
605+
help="Specific model to use (optional). Examples: haiku3.5, sonnet, gpt-4.1, o3-mini",
606+
)
607+
def test_generate(
608+
app: str | None,
609+
output: str | None,
610+
provider: str,
611+
model: str | None,
612+
) -> None:
613+
from ._main_generate_test import generate_test_file
614+
615+
generate_test_file(app_file=app, output_file=output, provider=provider, model=model)
616+
617+
567618
@main.command(
568619
help="""Create a Shiny application from a template.
569620

shiny/_main_generate_test.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
from __future__ import annotations
2+
3+
import sys
4+
from pathlib import Path
5+
6+
import click
7+
import questionary
8+
9+
from ._main_utils import cli_action, cli_bold, cli_code, path_rel_wd
10+
11+
12+
def generate_test_file(
13+
*,
14+
app_file: str | None,
15+
output_file: str | None,
16+
provider: str,
17+
model: str | None,
18+
):
19+
"""Generate AI-powered test file for a Shiny app."""
20+
21+
# Get app file path
22+
if app_file is None:
23+
24+
def path_exists(x: str) -> bool | str:
25+
if not isinstance(x, (str, Path)):
26+
return False
27+
path = Path(x)
28+
if path.is_dir():
29+
return "Please provide a file path to your Shiny app"
30+
return path.exists() or f"Shiny app file can not be found: {x}"
31+
32+
app_file_val = questionary.path(
33+
"Enter the path to the app file:",
34+
default=path_rel_wd("app.py"),
35+
validate=path_exists,
36+
).ask()
37+
else:
38+
app_file_val = app_file
39+
40+
# User quit early
41+
if app_file_val is None:
42+
sys.exit(1)
43+
44+
app_path = Path(app_file_val)
45+
46+
# Make sure app file exists
47+
if not app_path.exists():
48+
click.echo(f"❌ Error: App file does not exist: {app_path}")
49+
sys.exit(1)
50+
51+
# Get output file path if not provided
52+
if output_file is None:
53+
suggested_output = app_path.parent / f"test_{app_path.stem}.py"
54+
55+
def output_path_valid(x: str) -> bool | str:
56+
if not isinstance(x, (str, Path)):
57+
return False
58+
path = Path(x)
59+
if path.is_dir():
60+
return "Please provide a file path for your test file."
61+
if path.exists():
62+
return "Test file already exists. Please provide a new file name."
63+
if not path.name.startswith("test_"):
64+
return "Test file must start with 'test_'"
65+
return True
66+
67+
output_file_val = questionary.path(
68+
"Enter the path for the generated test file:",
69+
default=str(suggested_output),
70+
validate=output_path_valid,
71+
).ask()
72+
else:
73+
output_file_val = output_file
74+
75+
# User quit early
76+
if output_file_val is None:
77+
sys.exit(1)
78+
79+
output_path = Path(output_file_val)
80+
81+
# Validate output file
82+
if output_path.exists():
83+
click.echo(f"❌ Error: Test file already exists: {output_path}")
84+
sys.exit(1)
85+
86+
if not output_path.name.startswith("test_"):
87+
click.echo("❌ Error: Test file must start with 'test_'")
88+
sys.exit(1)
89+
90+
# Import and use the test generator
91+
try:
92+
# Import the test generator from the new testing module structure
93+
from .testing import ShinyTestGenerator
94+
except ImportError as e:
95+
click.echo(f"❌ Error: Could not import ShinyTestGenerator: {e}")
96+
click.echo("Make sure the shiny testing dependencies are installed.")
97+
sys.exit(1)
98+
99+
click.echo(f"🤖 Generating test using {provider} provider...")
100+
if model:
101+
click.echo(f"📝 Using model: {model}")
102+
103+
try:
104+
# Create the generator
105+
generator = ShinyTestGenerator(provider=provider) # type: ignore
106+
107+
# Generate the test
108+
_, test_file_path = generator.generate_test_from_file(
109+
app_file_path=str(app_path),
110+
model=model,
111+
output_file=str(output_path),
112+
)
113+
114+
click.echo(f"✅ Test file generated successfully: {test_file_path}")
115+
click.echo()
116+
click.echo(cli_action(cli_bold("Next steps:")))
117+
click.echo(
118+
f"- Run {cli_code('pytest ' + str(test_file_path))} to run the generated test"
119+
)
120+
click.echo("- Review and customize the test as needed")
121+
122+
except Exception as e:
123+
click.echo(f"❌ Error generating test: {e}")
124+
sys.exit(1)

shiny/testing/README.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Shiny Testing Framework
2+
3+
This directory contains the comprehensive testing framework for Shiny applications, including AI-powered test generation, evaluation tools, and utility scripts.
4+
5+
## Components
6+
7+
### 1. Generator (`generator/`)
8+
9+
The core AI-powered test generation system that creates comprehensive test files for Shiny applications.
10+
11+
**Key Features:**
12+
13+
- Support for multiple AI providers (Anthropic, OpenAI)
14+
- Model selection and configuration
15+
- Template-based test generation
16+
- File and code-based input processing
17+
18+
**Usage:**
19+
20+
```python
21+
from shiny.testing import ShinyTestGenerator
22+
23+
generator = ShinyTestGenerator(provider="anthropic")
24+
test_code, test_file = generator.generate_test_from_file("app.py")
25+
```
26+
27+
### 2. Evaluation (`evaluation/`)
28+
29+
Framework for evaluating the performance and quality of the test generator.
30+
31+
**Components:**
32+
33+
- **apps/**: Collection of diverse Shiny applications for testing
34+
- **scripts/**: Evaluation execution and metadata management
35+
- **results/**: Storage for evaluation outcomes and analysis
36+
37+
**Usage:**
38+
39+
```bash
40+
python evaluation/scripts/evaluation.py
41+
```
42+
43+
### 3. Utils (`utils/`)
44+
45+
Utility tools for processing documentation, analyzing results, and quality gating.
46+
47+
**Key Scripts:**
48+
49+
- `process_docs.py`: Convert XML documentation to JSON format
50+
- `process_results.py`: Analyze evaluation results and generate summaries
51+
- `quality_gate.py`: Validate performance against quality thresholds
52+
53+
## CLI Integration
54+
55+
The test generator is integrated into the Shiny CLI:
56+
57+
```bash
58+
# Generate test with interactive prompts
59+
shiny generate test
60+
61+
# Generate test with specific parameters
62+
shiny generate test --app app.py --output test_app.py --provider anthropic
63+
64+
# Use different models
65+
shiny generate test --app app.py --provider openai --model gpt-4.1-nano
66+
```
67+
68+
## Getting Started
69+
70+
1. **Install Dependencies**: Ensure you have the required AI provider SDKs and API keys
71+
2. **Generate Tests**: Use the CLI or Python API to generate tests
72+
3. **Run Evaluations**: Use the evaluation framework to assess generator performance
73+
4. **Quality Control**: Use utility scripts for processing and validation
74+
75+
## Development Workflow
76+
77+
1. **Add Test Apps**: Place new evaluation apps in `evaluation/apps/`
78+
2. **Update Documentation**: Modify `generator/data/docs/` for API changes
79+
3. **Run Evaluations**: Execute evaluation scripts to test performance
80+
4. **Process Results**: Use utility scripts to analyze outcomes
81+
5. **Quality Gate**: Validate results meet quality standards

shiny/testing/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .generator import ShinyTestGenerator
2+
3+
__all__ = ["ShinyTestGenerator"]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""
2+
Evaluation Module
3+
4+
Contains evaluation apps, scripts, and results for testing the Shiny test generator.
5+
"""

shiny/testing/evaluation/apps/__init__.py

Whitespace-only changes.

shiny/testing/evaluation/apps/app_01_core_basic/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)