aboutsummaryrefslogtreecommitdiff
path: root/pairwise-tests/scripts/pict_helper.py
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-04-19 16:12:02 -0500
committerCraig Jennings <c@cjennings.net>2026-04-19 16:12:02 -0500
commitb11cfd66b185a253fecf10ad06080ae165f32a74 (patch)
tree95c19d266aff9515acc0ab2dac2a90285dd1103e /pairwise-tests/scripts/pict_helper.py
parenta8deb6af6a14bc5e56e86289a2858a0258558388 (diff)
downloadrulesets-b11cfd66b185a253fecf10ad06080ae165f32a74.tar.gz
rulesets-b11cfd66b185a253fecf10ad06080ae165f32a74.zip
feat: adopt pairwise-tests (PICT combinatorial) + cross-reference from existing testing skills
Forked verbatim from omkamal/pypict-claude-skill (MIT). LICENSE preserved. Renamed from `pict-test-designer` to `pairwise-tests` — technique-first naming so users invoking "pairwise" or "combinatorial" find it; PICT remains the tool under the hood. Bundle (skill-runtime only): pairwise-tests/SKILL.md (renamed, description rewritten) pairwise-tests/LICENSE (MIT, preserved) pairwise-tests/references/pict_syntax.md pairwise-tests/references/examples.md pairwise-tests/scripts/pict_helper.py (Python CLI for model gen / output fmt) pairwise-tests/scripts/README.md Upstream's repo-level docs (README, QUICKSTART, CONTRIBUTING, etc.) and `examples/` dir (ATM + gearbox walkthroughs — useful as reading, not as skill-runtime) omitted from the fork. Attribution footer added. Cross-references so /add-tests naturally routes to /pairwise-tests when warranted: - add-tests/SKILL.md Phase 2 step 8: if a function in scope has 3+ parameters each taking multiple values, surface `/pairwise-tests` to the user before proposing normal category coverage. Default continues with /add-tests; user picks pairwise explicitly. - claude-rules/testing.md: new "Combinatorial Coverage" section after the Normal/Boundary/Error categories. Explains when pairwise wins, when to skip (regulated / provably exhaustive contexts, ≤2 parameters, non- parametric testing), and points at /pairwise-tests. - languages/python/claude/rules/python-testing.md: new "Pairwise / Combinatorial for Parameter-Heavy Functions" subsection under the parametrize guidance. Explains the pytest workflow: /pairwise-tests generates the matrix, paste into pytest parametrize block, or use pypict helper directly. Mechanism note: cross-references are judgment-based — Claude reads the nudges in add-tests/testing/python-testing and acts on them when appropriate, not automatic dispatch. Craig can still invoke /pairwise-tests directly when he already knows he wants combinatorial coverage. Makefile SKILLS extended; make install symlinks /pairwise-tests globally.
Diffstat (limited to 'pairwise-tests/scripts/pict_helper.py')
-rw-r--r--pairwise-tests/scripts/pict_helper.py207
1 files changed, 207 insertions, 0 deletions
diff --git a/pairwise-tests/scripts/pict_helper.py b/pairwise-tests/scripts/pict_helper.py
new file mode 100644
index 0000000..3a64f91
--- /dev/null
+++ b/pairwise-tests/scripts/pict_helper.py
@@ -0,0 +1,207 @@
+#!/usr/bin/env python3
+"""
+PICT Helper Script
+
+This script provides utilities for working with PICT models and test cases.
+
+Note: This is a placeholder/example script. Full implementation coming soon!
+
+Requirements:
+ pip install pypict --break-system-packages
+
+Usage:
+ python pict_helper.py generate config.json
+ python pict_helper.py format output.txt
+ python pict_helper.py parse output.txt
+"""
+
+import sys
+import json
+from typing import Dict, List, Any
+
+def generate_model(config_file: str) -> str:
+ """
+ Generate a PICT model from a JSON configuration file.
+
+ Args:
+ config_file: Path to JSON config file
+
+ Returns:
+ PICT model as string
+
+ Example config.json:
+ {
+ "parameters": {
+ "Browser": ["Chrome", "Firefox", "Safari"],
+ "OS": ["Windows", "MacOS", "Linux"],
+ "Memory": ["4GB", "8GB", "16GB"]
+ },
+ "constraints": [
+ "IF [OS] = \"MacOS\" THEN [Browser] <> \"IE\"",
+ "IF [Memory] = \"4GB\" THEN [OS] <> \"MacOS\""
+ ]
+ }
+ """
+ try:
+ with open(config_file, 'r') as f:
+ config = json.load(f)
+
+ parameters = config.get('parameters', {})
+ constraints = config.get('constraints', [])
+
+ # Generate model
+ model_lines = []
+ model_lines.append("# Generated PICT Model")
+ model_lines.append("")
+
+ # Add parameters
+ for param_name, values in parameters.items():
+ values_str = ", ".join(values)
+ model_lines.append(f"{param_name}: {values_str}")
+
+ # Add constraints
+ if constraints:
+ model_lines.append("")
+ model_lines.append("# Constraints")
+ for constraint in constraints:
+ if not constraint.endswith(';'):
+ constraint += ';'
+ model_lines.append(constraint)
+
+ return "\n".join(model_lines)
+
+ except Exception as e:
+ print(f"Error generating model: {e}", file=sys.stderr)
+ return ""
+
+def format_output(output_file: str) -> str:
+ """
+ Format PICT output as a markdown table.
+
+ Args:
+ output_file: Path to PICT output file
+
+ Returns:
+ Markdown formatted table
+ """
+ try:
+ with open(output_file, 'r') as f:
+ lines = f.readlines()
+
+ if not lines:
+ return "No output to format"
+
+ # First line is header
+ header = lines[0].strip().split('\t')
+
+ # Create markdown table
+ table = []
+ table.append("| " + " | ".join(header) + " |")
+ table.append("|" + "|".join(["-" * (len(h) + 2) for h in header]) + "|")
+
+ # Add data rows
+ for line in lines[1:]:
+ if line.strip():
+ values = line.strip().split('\t')
+ table.append("| " + " | ".join(values) + " |")
+
+ return "\n".join(table)
+
+ except Exception as e:
+ print(f"Error formatting output: {e}", file=sys.stderr)
+ return ""
+
+def parse_output(output_file: str) -> List[Dict[str, str]]:
+ """
+ Parse PICT output into a list of dictionaries.
+
+ Args:
+ output_file: Path to PICT output file
+
+ Returns:
+ List of test case dictionaries
+ """
+ try:
+ with open(output_file, 'r') as f:
+ lines = f.readlines()
+
+ if not lines:
+ return []
+
+ # First line is header
+ header = lines[0].strip().split('\t')
+
+ # Parse data rows
+ test_cases = []
+ for i, line in enumerate(lines[1:], 1):
+ if line.strip():
+ values = line.strip().split('\t')
+ test_case = {"test_id": i}
+ for h, v in zip(header, values):
+ test_case[h] = v
+ test_cases.append(test_case)
+
+ return test_cases
+
+ except Exception as e:
+ print(f"Error parsing output: {e}", file=sys.stderr)
+ return []
+
+def main():
+ """Main entry point for the script."""
+ if len(sys.argv) < 2:
+ print("Usage:")
+ print(" python pict_helper.py generate <config.json>")
+ print(" python pict_helper.py format <output.txt>")
+ print(" python pict_helper.py parse <output.txt>")
+ sys.exit(1)
+
+ command = sys.argv[1]
+
+ if command == "generate" and len(sys.argv) >= 3:
+ config_file = sys.argv[2]
+ model = generate_model(config_file)
+ print(model)
+
+ elif command == "format" and len(sys.argv) >= 3:
+ output_file = sys.argv[2]
+ table = format_output(output_file)
+ print(table)
+
+ elif command == "parse" and len(sys.argv) >= 3:
+ output_file = sys.argv[2]
+ test_cases = parse_output(output_file)
+ print(json.dumps(test_cases, indent=2))
+
+ else:
+ print(f"Unknown command: {command}", file=sys.stderr)
+ sys.exit(1)
+
+if __name__ == "__main__":
+ main()
+
+# Example usage:
+"""
+# 1. Create a config.json file:
+{
+ "parameters": {
+ "Browser": ["Chrome", "Firefox", "Safari"],
+ "OS": ["Windows", "MacOS", "Linux"]
+ },
+ "constraints": [
+ "IF [OS] = \"MacOS\" THEN [Browser] <> \"IE\""
+ ]
+}
+
+# 2. Generate PICT model:
+python pict_helper.py generate config.json > model.txt
+
+# 3. Run PICT (if installed):
+pict model.txt > output.txt
+
+# 4. Format as markdown:
+python pict_helper.py format output.txt
+
+# 5. Parse to JSON:
+python pict_helper.py parse output.txt
+"""