diff options
| author | Craig Jennings <c@cjennings.net> | 2026-04-19 16:12:02 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-04-19 16:12:02 -0500 |
| commit | b11cfd66b185a253fecf10ad06080ae165f32a74 (patch) | |
| tree | 95c19d266aff9515acc0ab2dac2a90285dd1103e /pairwise-tests/scripts/pict_helper.py | |
| parent | a8deb6af6a14bc5e56e86289a2858a0258558388 (diff) | |
| download | rulesets-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.py | 207 |
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 +""" |
