Add custom nodes, Civitai loras (LFS), and vast.ai setup script
Some checks failed
Python Linting / Run Ruff (push) Has been cancelled
Python Linting / Run Pylint (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.10, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.11, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.12, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-unix-nightly (12.1, , linux, 3.11, [self-hosted Linux], nightly) (push) Has been cancelled
Execution Tests / test (macos-latest) (push) Has been cancelled
Execution Tests / test (ubuntu-latest) (push) Has been cancelled
Execution Tests / test (windows-latest) (push) Has been cancelled
Test server launches without errors / test (push) Has been cancelled
Unit Tests / test (macos-latest) (push) Has been cancelled
Unit Tests / test (ubuntu-latest) (push) Has been cancelled
Unit Tests / test (windows-2022) (push) Has been cancelled

Includes 30 custom nodes committed directly, 7 Civitai-exclusive
loras stored via Git LFS, and a setup script that installs all
dependencies and downloads HuggingFace-hosted models on vast.ai.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-09 00:55:26 +00:00
parent 2b70ab9ad0
commit f09734b0ee
2274 changed files with 748556 additions and 3 deletions

View File

@@ -0,0 +1,39 @@
# Wildcard System Documentation
Progressive on-demand wildcard loading system for ComfyUI Impact Pack.
## Documentation Structure
- **[WILDCARD_SYSTEM_PRD.md](WILDCARD_SYSTEM_PRD.md)** - Product requirements and specifications
- **[WILDCARD_SYSTEM_DESIGN.md](WILDCARD_SYSTEM_DESIGN.md)** - Technical architecture and implementation
- **[WILDCARD_TESTING_GUIDE.md](WILDCARD_TESTING_GUIDE.md)** - Testing procedures and validation
## Quick Links
- Test Suite: `../../tests/`
- Test Samples: `../../tests/wildcards/samples/`
- Implementation: `../../modules/impact/wildcards.py`
- Server API: `../../modules/impact/impact_server.py`
## Test Execution
```bash
cd tests/
# Run all test suites
bash test_encoding.sh # UTF-8 multi-language (15 tests)
bash test_error_handling.sh # Error handling (10 tests)
bash test_edge_cases.sh # Edge cases (20 tests)
bash test_deep_nesting.sh # 7-level nesting (15 tests)
bash test_ondemand_loading.sh # On-demand loading (8 tests)
bash test_config_quotes.sh # Config quotes (5 tests)
```
## Status
**Production Ready**
- 73 tests, 100% pass rate (6 test suites)
- Complete PRD coverage
- Zero implementation bugs
- UTF-8 encoding verified
- Error handling validated

View File

@@ -0,0 +1,151 @@
# Wildcard System - Project Summary
## Overview
Progressive on-demand wildcard loading system for ComfyUI Impact Pack with dynamic prompt support, UTF-8 encoding, and comprehensive testing.
**Status**: ✅ Production Ready
**Test Coverage**: 86 tests, 100% pass rate
**Documentation**: Complete PRD, design docs, and testing guide
---
## Core Features
- **Wildcard Expansion**: `__wildcard__` syntax with transitive multi-level expansion
- **Dynamic Prompts**:
- Basic selection: `{option1|option2|option3}`
- Weighted selection: `{10::common|1::rare}` (weight comes first)
- Multi-select: `{2$$, $$red|blue|green}` with custom separators
- **UTF-8 Support**: Korean, Chinese, Arabic, emoji, special characters
- **Pattern Matching**: Depth-agnostic `__*/name__` syntax
- **On-Demand Loading**: Progressive lazy loading with configurable cache limits
- **Error Handling**: Circular reference detection, graceful fallbacks
---
## Architecture
### Implementation
- `modules/impact/wildcards.py` - Core LazyWildcardLoader and expansion engine
- `modules/impact/impact_server.py` - Server API endpoint (/impact/wildcards)
- `modules/impact/config.py` - Configuration with quoted path support
### Key Design Decisions
- **Lazy Loading**: Memory-efficient progressive loading strategy
- **Transitive Expansion**: Multi-level wildcard references through directory hierarchy
- **Case-Insensitive Matching**: Fuzzy matching for user convenience
- **Circular Reference Detection**: Max 100 iterations with clear error messages
---
## Testing
### Test Suites (86 tests)
1. **UTF-8 Encoding** (15 tests) - Multi-language support validation
2. **Error Handling** (10 tests) - Graceful error recovery
3. **Edge Cases** (20 tests) - Boundary conditions and special scenarios
4. **Deep Nesting** (17 tests) - 7-level transitive expansion + pattern matching
5. **On-Demand Loading** (8 tests) - Progressive loading with cache limits
6. **Config Quotes** (5 tests) - Configuration path handling
7. **Dynamic Prompts** (11 tests) - Statistical validation of dynamic features
### Test Infrastructure
- Dedicated ports per suite (8188-8198)
- Automated server lifecycle management
- Comprehensive logging in `/tmp/`
- 100% pass rate with statistical validation
---
## Documentation
- **[README](README.md)** - Quick start and feature overview
- **[PRD](WILDCARD_SYSTEM_PRD.md)** - Complete product requirements
- **[Design](WILDCARD_SYSTEM_DESIGN.md)** - Technical architecture
- **[Testing Guide](WILDCARD_TESTING_GUIDE.md)** - Test procedures and validation
---
## Quick Start
### Basic Usage
```python
# Simple wildcard
"a photo of __animal__"
# Dynamic prompt
"a {red|blue|green} __vehicle__"
# Weighted selection (weight comes FIRST)
"{10::common|1::rare} scene"
# Multi-select
"{2$$, $$happy|sad|angry|excited} person"
```
### Running Tests
```bash
cd tests/
bash test_encoding.sh
bash test_error_handling.sh
bash test_edge_cases.sh
bash test_deep_nesting.sh
bash test_ondemand_loading.sh
bash test_config_quotes.sh
bash test_dynamic_prompts_full.sh
```
---
## Key Implementations
### Weighted Selection Syntax
**Correct**: `{weight::option}` - Weight comes FIRST
- `{10::common|1::rare}` → 91% common, 9% rare ✅
- `{5::red|3::green|2::blue}` → 50%, 30%, 20% ✅
**Incorrect**: `{option::weight}` - Treated as equal weights
- `{common::10|rare::1}` → 50% each ❌
### Empty Line Filtering
Filter empty lines AND comment lines:
```python
[x for x in lines if x.strip() and not x.strip().startswith('#')]
```
### Config Path Quotes
Strip quotes from configuration paths:
```python
custom_wildcards_path = default_conf.get('custom_wildcards', '').strip('\'"')
```
---
## Limitations
- Weighted selection supports integers and simple decimals only
- Complex decimal weights may conflict with multiselect pattern detection
- Circular references limited to 100 iterations
- Prefer integer weight ratios for clarity
---
## Performance
- **Lazy Loading**: Only load wildcards when needed
- **On-Demand Mode**: Progressive loading based on cache limits
- **Memory Efficient**: Configurable cache size (0.5MB - 100MB)
- **Fast Lookup**: Optimized directory traversal with pattern matching
---
## Production Ready
✅ Zero known bugs
✅ Complete PRD coverage
✅ 100% test pass rate
✅ Statistical validation
✅ Comprehensive documentation
✅ Multi-language support
✅ Graceful error handling

View File

@@ -0,0 +1,817 @@
# Wildcard System - Design Document
**Document Type**: Technical Design Document
**Product**: ComfyUI Impact Pack Wildcard System
**Version**: 2.0 (Depth-Agnostic Matching)
**Last Updated**: 2025-11-18
**Status**: Released
---
## 1. System Architecture
### 1.1 High-Level Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ ComfyUI Frontend │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ ImpactWildcardProcessor / ImpactWildcardEncode │ │
│ │ - Wildcard Prompt (editable) │ │
│ │ - Populated Prompt (read-only in Populate mode) │ │
│ │ - Mode: Populate / Fixed │ │
│ │ - UI Indicator: 🟢 Full Cache / 🔵 On-Demand │ │
│ └──────────────────────────────────────────────────────┘ │
└────────────────────────┬─────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Impact Server (API) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ POST /impact/wildcards │ │
│ │ GET /impact/wildcards/list │ │
│ │ GET /impact/wildcards/list/loaded │ │
│ │ GET /impact/wildcards/refresh │ │
│ └──────────────────────────────────────────────────────┘ │
└────────────────────────┬─────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Wildcard Processing Engine │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ process() - Main entry point │ │
│ │ ├─ process_comment_out() │ │
│ │ ├─ replace_options() - {a|b|c} │ │
│ │ └─ replace_wildcard() - __wildcard__ │ │
│ │ │ │
│ │ get_wildcard_value() │ │
│ │ ├─ Direct lookup │ │
│ │ ├─ Depth-agnostic fallback ⭐ NEW │ │
│ │ └─ On-demand file loading │ │
│ │ │ │
│ │ get_wildcard_options() - {option1|__wild__|option3} │ │
│ │ └─ Pattern matching for wildcards in options │ │
│ └──────────────────────────────────────────────────────┘ │
└────────────────────────┬─────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Loading System │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Startup Phase │ │
│ │ ├─ calculate_directory_size() - Early termination │ │
│ │ ├─ Determine mode (Full Cache / On-Demand) │ │
│ │ └─ scan_wildcard_metadata() - TXT metadata only │ │
│ │ │ │
│ │ Full Cache Mode │ │
│ │ └─ load_wildcards() - Load all data │ │
│ │ │ │
│ │ On-Demand Mode ⭐ NEW │ │
│ │ ├─ Pre-load: YAML files (keys in content) │ │
│ │ └─ On-demand: TXT files (path = key) │ │
│ └──────────────────────────────────────────────────────┘ │
└────────────────────────┬─────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Data Storage │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ wildcard_dict = {} │ │
│ │ - Full cache: All wildcard data │ │
│ │ - On-demand: Not used │ │
│ │ │ │
│ │ available_wildcards = {} ⭐ NEW │ │
│ │ - On-demand only: Metadata (path → file) │ │
│ │ - Example: {"dragon": "/path/dragon.txt"} │ │
│ │ │ │
│ │ loaded_wildcards = {} ⭐ NEW │ │
│ │ - On-demand only: Loaded data cache │ │
│ │ - Example: {"dragon": ["red dragon", "blue..."]} │ │
│ └──────────────────────────────────────────────────────┘ │
└────────────────────────┬─────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ File System │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ wildcards/ (bundled) │ │
│ │ custom_wildcards/ (user-defined) │ │
│ │ ├─ *.txt files (one option per line) │ │
│ │ └─ *.yaml files (nested structure) │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
---
## 2. Core Components
### 2.1 Processing Engine
#### 2.1.1 process()
**Purpose**: Main entry point for wildcard text processing
**Flow**:
```python
def process(text, seed=None):
1. process_comment_out(text) # Remove # comments
2. random.seed(seed) # Deterministic generation
3. replace_options(text) # Process {a|b|c}
4. replace_wildcard(text) # Process __wildcard__
5. return processed_text
```
**Features**:
- Maximum 100 iterations for nested expansion
- Deterministic with seed
- Supports transitive wildcards
---
#### 2.1.2 replace_options()
**Purpose**: Process dynamic prompts `{option1|option2}`
**Supported Syntax**:
```python
{a|b|c} # Random selection
{3::a|2::b|c} # Weighted (3:2:1 ratio)
{2$$, $$a|b|c|d} # Multi-select 2, comma-separated
{2-4$$; $$a|b|c|d} # Multi-select 2-4, semicolon-separated
{a|{b|c}|d} # Nested options
```
**Algorithm**:
1. Parse weight prefix (`::`)
2. Calculate normalized probabilities
3. Use `np.random.choice()` with probabilities
4. Handle multi-select with custom separators
---
#### 2.1.3 replace_wildcard()
**Purpose**: Process wildcard references `__wildcard__`
**Flow**:
```python
def replace_wildcard(string):
for each __match__:
1. keyword = normalize(match)
2. options = get_wildcard_value(keyword)
3. if options:
random select from options
elif '*' in keyword:
pattern matching (for __*/name__)
else:
keep unchanged
4. replace in string
```
**Pattern Matching** (`__*/name__`):
```python
if keyword.startswith('*/'):
base_name = keyword[2:] # "*/dragon" → "dragon"
for k in wildcards:
if matches_pattern(k, base_name):
collect options
combine all options
```
---
### 2.2 Depth-Agnostic Matching ⭐ NEW
#### 2.2.1 get_wildcard_value()
**Purpose**: Retrieve wildcard data with automatic depth-agnostic fallback
**Algorithm**:
```python
def get_wildcard_value(key):
# Phase 1: Direct lookup
if key in loaded_wildcards:
return loaded_wildcards[key]
# Phase 2: File discovery
file_path = find_wildcard_file(key)
if file_path:
load and cache
return data
# Phase 3: Depth-agnostic fallback ⭐ NEW
matched_keys = []
for k in available_wildcards:
if matches_depth_agnostic(k, key):
matched_keys.append(k)
if matched_keys:
# Combine all matched wildcards
all_options = []
for mk in matched_keys:
all_options.extend(get_wildcard_value(mk))
# Cache combined result
loaded_wildcards[key] = all_options
return all_options
return None
```
**Pattern Matching Logic**:
```python
def matches_depth_agnostic(stored_key, search_key):
"""
Examples:
search_key = "dragon"
stored_key = "dragon" → True (exact)
stored_key = "custom_wildcards/dragon" → True (ends with)
stored_key = "dragon/wizard" → True (starts with)
stored_key = "a/b/dragon/c/d" → True (contains)
"""
return (stored_key == search_key or
stored_key.endswith('/' + search_key) or
stored_key.startswith(search_key + '/') or
('/' + search_key + '/') in stored_key)
```
**Benefits**:
- Works with any directory structure
- No configuration needed
- Combines multiple sources for variety
- Cached for performance
---
### 2.3 Loading System
#### 2.3.1 Mode Detection
**Decision Algorithm**:
```python
def determine_loading_mode():
total_size = calculate_directory_size()
cache_limit = config.wildcard_cache_limit_mb * 1024 * 1024
if total_size >= cache_limit:
return ON_DEMAND_MODE
else:
return FULL_CACHE_MODE
```
**Early Termination**:
```python
def calculate_directory_size():
size = 0
for file in walk(directory):
size += file_size
if size >= cache_limit:
return size # Early termination
return size
```
**Performance**: < 1 second for 10GB+ collections
---
#### 2.3.2 Metadata Scanning ⭐ NEW
**Purpose**: Discover TXT wildcards without loading data
**Algorithm**:
```python
def scan_wildcard_metadata(path):
for file in walk(path):
if file.endswith('.txt'):
rel_path = relpath(file, path)
key = normalize(remove_extension(rel_path))
available_wildcards[key] = file # Store path only
```
**Storage**:
```python
available_wildcards = {
"dragon": "/path/custom_wildcards/dragon.txt",
"custom_wildcards/dragon": "/path/custom_wildcards/dragon.txt",
"dragon/wizard": "/path/dragon/wizard.txt",
...
}
```
**Memory**: ~50 bytes per file (path string)
---
#### 2.3.3 On-Demand Loading ⭐ NEW
**Purpose**: Load wildcard data only when accessed
**Flow**:
```
User request: __dragon__
get_wildcard_value("dragon")
Not in cache → find_wildcard_file("dragon")
File not found → Depth-agnostic fallback
Pattern match: ["custom_wildcards/dragon", "dragon/wizard", ...]
Load each matched file
Combine all options
Cache result: loaded_wildcards["dragon"] = combined_options
Return combined_options
```
**YAML Pre-Loading**:
```python
def load_yaml_wildcards():
"""
YAML wildcards CANNOT be on-demand because:
- Keys are inside file content, not file path
- Must parse entire file to discover keys
Example:
File: colors.yaml
Content:
warm: [red, orange, yellow]
cold: [blue, green, purple]
To know "__colors/warm__" exists, must parse entire file.
"""
for yaml_file in find_yaml_files():
data = yaml.load(yaml_file)
for key, value in data.items():
loaded_wildcards[key] = value
```
---
### 2.4 Data Structures
#### 2.4.1 Global State
```python
# Configuration
_on_demand_mode = False # True if on-demand mode active
wildcard_dict = {} # Full cache mode storage
available_wildcards = {} # On-demand metadata (key → file path)
loaded_wildcards = {} # On-demand loaded data (key → options)
# Thread safety
wildcard_lock = threading.Lock()
```
#### 2.4.2 Key Normalization
```python
def wildcard_normalize(x):
"""
Normalize wildcard keys for consistent lookup
Examples:
"Dragon""dragon" (lowercase)
"dragon.txt""dragon" (remove extension)
"folder/Dragon""folder/dragon" (lowercase)
"""
return x.lower().replace('\\', '/')
```
---
## 3. API Design
### 3.1 POST /impact/wildcards
**Purpose**: Process wildcard text
**Request**:
```json
{
"text": "a {red|blue} __flowers__",
"seed": 42
}
```
**Response**:
```json
{
"text": "a red rose"
}
```
**Implementation**:
```python
@app.post("/impact/wildcards")
def process_wildcards(request):
text = request.json["text"]
seed = request.json.get("seed")
result = process(text, seed)
return {"text": result}
```
---
### 3.2 GET /impact/wildcards/list/loaded ⭐ NEW
**Purpose**: Track progressive loading
**Response**:
```json
{
"data": ["__dragon__", "__flowers__"],
"on_demand_mode": true,
"total_available": 1000
}
```
**Implementation**:
```python
@app.get("/impact/wildcards/list/loaded")
def get_loaded_wildcards():
with wildcard_lock:
if _on_demand_mode:
return {
"data": [f"__{k}__" for k in loaded_wildcards.keys()],
"on_demand_mode": True,
"total_available": len(available_wildcards)
}
else:
return {
"data": [f"__{k}__" for k in wildcard_dict.keys()],
"on_demand_mode": False,
"total_available": len(wildcard_dict)
}
```
---
### 3.3 GET /impact/wildcards/refresh
**Purpose**: Reload all wildcards
**Implementation**:
```python
@app.get("/impact/wildcards/refresh")
def refresh_wildcards():
global wildcard_dict, loaded_wildcards, available_wildcards
with wildcard_lock:
# Clear all caches
wildcard_dict.clear()
loaded_wildcards.clear()
available_wildcards.clear()
# Re-initialize
wildcard_load()
return {"status": "ok"}
```
---
## 4. File Format Support
### 4.1 TXT Format
**Structure**:
```
# flowers.txt
rose
tulip
# Comments start with #
sunflower
```
**Parsing**:
```python
def load_txt_wildcard(file_path):
with open(file_path) as f:
lines = f.read().splitlines()
return [x for x in lines if not x.strip().startswith('#')]
```
**On-Demand**: ✅ Fully supported
---
### 4.2 YAML Format
**Structure**:
```yaml
# colors.yaml
warm:
- red
- orange
- yellow
cold:
- blue
- green
- purple
```
**Usage**: `__colors/warm__`, `__colors/cold__`
**Parsing**:
```python
def load_yaml_wildcard(file_path):
data = yaml.load(file_path)
for key, value in data.items():
if isinstance(value, list):
loaded_wildcards[key] = value
elif isinstance(value, dict):
# Recursive for nested structure
load_nested(key, value)
```
**On-Demand**: ⚠️ Always pre-loaded (keys in content)
---
## 5. UI Integration
### 5.1 ImpactWildcardProcessor Node
**Features**:
- **Wildcard Prompt**: User input with wildcard syntax
- **Populated Prompt**: Processed result
- **Mode Selector**: Populate / Fixed
- **Populate**: Process wildcards on queue, populate result
- **Fixed**: Use populated text as-is (for saved images)
**UI Indicator**:
- 🟢 **Full Cache**: All wildcards loaded
- 🔵 **On-Demand**: Progressive loading active (shows count)
---
### 5.2 ImpactWildcardEncode Node
**Additional Features**:
- **LoRA Loading**: `<lora:name:model_weight:clip_weight>`
- **LoRA Block Weight**: `<lora:name:1.0:1.0:LBW=spec;>`
- **BREAK Syntax**: Separate encoding with Concat
- **Clip Integration**: Returns processed model + clip
**Special Syntax**:
```
<lora:chunli:1.0:1.0:LBW=B11:0,0,0,0,0,0,0,0,0,0,A,0,0,0,0,0,0;A=0.;>
```
---
### 5.3 Detailer Wildcard Features
**Ordering**:
- `[ASC]`: Ascending order (x, y)
- `[DSC]`: Descending order (x, y)
- `[ASC-SIZE]`: Ascending by area
- `[DSC-SIZE]`: Descending by area
- `[RND]`: Random order
**Control**:
- `[SEP]`: Separate prompts per detection area
- `[SKIP]`: Skip detailing for this area
- `[STOP]`: Stop detailing (including current area)
- `[LAB]`: Label-based application
- `[CONCAT]`: Concatenate with positive conditioning
**Example**:
```
[ASC]
1girl, blue eyes, smile [SEP]
1boy, brown eyes [SEP]
```
---
## 6. Performance Optimization
### 6.1 Startup Optimization
**Techniques**:
1. **Early Termination**: Stop size calculation at cache limit
2. **Metadata Only**: Don't load TXT file content
3. **YAML Pre-loading**: Small files, pre-load is acceptable
**Results**:
- 10GB collection: 20-60 min → < 1 min (95%+ improvement)
---
### 6.2 Runtime Optimization
**Techniques**:
1. **Caching**: Store loaded wildcards in memory
2. **Depth-Agnostic Caching**: Cache combined pattern results
3. **NumPy Random**: Fast random generation
**Results**:
- First access: < 50ms
- Cached access: < 1ms
---
### 6.3 Memory Optimization
**Techniques**:
1. **Progressive Loading**: Load only accessed wildcards
2. **Metadata Storage**: Store paths, not data
3. **Combined Caching**: Cache pattern match results
**Results**:
- Initial: < 100MB (vs 1GB+ in old implementation)
- Growth: Linear with usage, not total size
---
## 7. Error Handling
### 7.1 File Not Found
**Scenario**: Wildcard file doesn't exist
**Handling**:
```python
def get_wildcard_value(key):
file_path = find_wildcard_file(key)
if file_path is None:
# Try depth-agnostic fallback
matched = find_pattern_matches(key)
if matched:
return combine_matched(matched)
# No match found - log warning, return None
logging.warning(f"Wildcard not found: {key}")
return None
```
**User Impact**: Wildcard remains unexpanded
---
### 7.2 File Read Error
**Scenario**: Cannot read file (permissions, encoding, etc.)
**Handling**:
```python
def load_txt_wildcard(file_path):
try:
with open(file_path, 'r', encoding="ISO-8859-1") as f:
return f.read().splitlines()
except Exception as e:
logging.error(f"Failed to load {file_path}: {e}")
return None
```
**User Impact**: Wildcard not loaded, error logged
---
### 7.3 Infinite Loop Protection
**Scenario**: Circular wildcard references
**Protection**:
```python
def process(text, seed=None):
max_iterations = 100
for i in range(max_iterations):
new_text = process_one_pass(text)
if new_text == text:
break # No changes, done
text = new_text
if i == max_iterations - 1:
logging.warning("Max iterations reached")
return text
```
**User Impact**: Processing stops after 100 iterations
---
## 8. Testing Strategy
### 8.1 Unit Tests
**Coverage**:
- `process()`: All syntax variations
- `replace_options()`: Weight, multi-select, nested
- `replace_wildcard()`: Direct, pattern, depth-agnostic
- `get_wildcard_value()`: Direct, fallback, caching
---
### 8.2 Integration Tests
**Scenarios**:
- Full cache mode activation
- On-demand mode activation
- Progressive loading tracking
- Depth-agnostic matching
- API endpoints
**Test Suite**: `tests/test_dragon_wildcard_expansion.sh`
---
### 8.3 Performance Tests
**Metrics**:
- Startup time (10GB collection)
- Memory usage (initial, after 100 accesses)
- First access latency
- Cached access latency
- Pattern matching latency
**Test Tool**: `/tmp/test_depth_agnostic.sh`
---
## 9. Security Considerations
### 9.1 Path Traversal
**Risk**: Malicious wildcard names could access files outside wildcard directory
**Mitigation**:
```python
def find_wildcard_file(key):
# Normalize and validate path
safe_key = os.path.normpath(key)
if '..' in safe_key or safe_key.startswith('/'):
logging.error(f"Invalid wildcard path: {key}")
return None
# Ensure result is within wildcard directory
file_path = os.path.join(wildcards_path, safe_key)
if not file_path.startswith(wildcards_path):
logging.error(f"Path traversal attempt: {key}")
return None
return file_path
```
---
### 9.2 Resource Exhaustion
**Risk**: Very large wildcards or infinite loops
**Mitigation**:
1. **Iteration Limit**: Max 100 expansions
2. **File Size Limit**: Reasonable file size checks
3. **Memory Monitoring**: Track loaded wildcard count
---
## 10. Future Enhancements
### 10.1 Planned Features
1. **LRU Cache**: Automatic eviction of least-used wildcards
2. **Background Preloading**: Preload frequently-used wildcards
3. **Persistent Cache**: Save loaded wildcards across restarts
4. **Usage Statistics**: Track wildcard access patterns
5. **Compression**: Compress infrequently-used wildcards
### 10.2 Performance Improvements
1. **Parallel Loading**: Load multiple wildcards concurrently
2. **Index Structure**: B-tree for faster lookups
3. **Memory Pooling**: Reduce allocation overhead
---
## 11. References
### 11.1 External Documentation
- [Product Requirements Document](WILDCARD_SYSTEM_PRD.md)
- [User Guide](WILDCARD_SYSTEM_OVERVIEW.md)
- [Testing Guide](WILDCARD_TESTING_GUIDE.md)
- [Tutorial](../../ComfyUI-extension-tutorials/ComfyUI-Impact-Pack/tutorial/ImpactWildcard.md)
### 11.2 Code References
- **Core Engine**: `modules/impact/wildcards.py`
- **API Server**: `modules/impact/impact_server.py`
- **UI Nodes**: `nodes.py` (ImpactWildcardProcessor, ImpactWildcardEncode)
---
**Document Approval**:
- Engineering Lead: ✅ Approved
- Architecture Review: ✅ Approved
- Security Review: ✅ Approved
**Last Review**: 2025-11-18

View File

@@ -0,0 +1,435 @@
# Wildcard System - Product Requirements Document
**Product**: ComfyUI Impact Pack Wildcard System
**Version**: 2.0 (Depth-Agnostic Matching)
**Status**: Released
**Last Updated**: 2025-11-18
---
## 1. Overview
### 1.1 Product Vision
The Wildcard System provides **dynamic text generation** for AI prompts, enabling users to create rich, varied prompts with minimal manual effort.
### 1.2 Target Users
- **AI Artists**: Creating varied prompts for image generation
- **Content Creators**: Generating diverse text content
- **Game Designers**: Dynamic NPC dialogue and procedural content
- **ComfyUI Users**: Workflow automation with dynamic text
---
## 2. Core Features
> **Note**: For detailed syntax examples and usage guides, see the [ImpactWildcard Tutorial](../../../ComfyUI-extension-tutorials/ComfyUI-Impact-Pack/tutorial/ImpactWildcard.md).
### 2.1 Wildcard Syntax
**Basic Wildcards**:
- `__wildcard_name__` - Simple text replacement (e.g., `__flower__` → random flower from flower.txt)
- `__category/subcategory__` - Hierarchical organization with subdirectories (e.g., `__obj/person__`)
- Transitive wildcards - Wildcards can reference other wildcards
- Case-insensitive matching - `__Jewel__` and `__jewel__` are identical
- `*` aggregation pattern (V4.15.1+) - Groups all items from path and subdirectories into one collection
**Quantifiers**:
- `N#__wildcard__` - Repeat wildcard N times
- Example: `5#__wildcards__` expands to `__wildcards__|__wildcards__|__wildcards__|__wildcards__|__wildcards__`
- Can be combined with multi-select: `{2$$, $$5#__wildcards__}`
**Comments**:
- Lines starting with `#` are treated as comments and removed
- Text following a comment is separated by single blank space from text before comment
- Example:
```
first {a|b|c} second # not a comment,
# this is a comment
trailing text
```
Becomes: `first a second # not a comment, trailing text`
**Pattern Matching**:
- `__*/wildcard__` - Depth-agnostic pattern matching at any directory level
- Automatic fallback when direct lookup fails
---
### 2.2 Dynamic Prompts
**Basic Selection**:
- `{option1|option2|option3}` - Random selection from options
- Unlimited nesting: `{a|{d|e|f}|c}` - Nested options are evaluated
- Example: `{blue apple|red {cherry|berry}|green melon}` → `blue apple`, `red cherry`, `red berry`, or `green melon`
- Complex nesting: `1{girl is holding {blue pencil|red __fruit__|colorful __flower__}|boy is riding __vehicle__}`
**Weighted Selection**:
- `{weight::option}` - Control selection probability
- **Syntax**: Weight comes FIRST, then `::`, then the option value
- **Correct**: `{10::common|1::rare}` → 10:1 ratio (≈91% vs ≈9%)
- **Incorrect**: `{common::10|rare::1}` → Will be treated as equal weights (50% vs 50%)
- Weights are normalized: `{5::red|3::green|2::blue}` → 50% red, 30% green, 20% blue
- Unweighted options default to weight 1: `{5::red|green|2::blue}` → 5:1:2 ratio
**Limitations**:
- Weights must be integers or simple decimals (e.g., `5`, `10`, `0.5`)
- Complex decimal weights may cause parsing issues due to multiselect pattern conflicts
- For decimal ratios, prefer integer equivalents: use `{5::a|3::b|2::c}` instead of `{0.5::a|0.3::b|0.2::c}`
**Multi-Select**:
- `{n$$opt1|opt2|opt3}` - Select exactly n items
- `{n1-n2$$opt1|opt2|opt3}` - Select between n1 and n2 items (excess ignored if range exceeds options)
- `{-n$$opt1|opt2|opt3}` - Select between 1 and n items
- **Custom separator**: `{n$$ separator $$opt1|opt2|opt3}`
- Example: `{2$$ and $$red|blue|green}` → "red and blue"
- Example: `{1-2$$ or $$apple|orange|banana}` → "apple" or "apple or orange"
---
### 2.3 ComfyUI Nodes
**ImpactWildcardProcessor**:
- **Purpose**: Browser-level wildcard processing for prompt generation
- **Dual Input Fields**:
- Upper field: Wildcard Prompt (accepts wildcard syntax)
- Lower field: Populated Prompt (displays generated result)
- **Mode Control**:
- **Populate**: Processes wildcards on queue prompt, populates result (read-only)
- **Fixed**: Ignores wildcard prompt, allows manual editing of populated prompt
- **Seed Input**:
- Supports seed-based deterministic generation
- Compatible seed inputs: `ImpactInt`, `Seed (rgthree)` only
- Limitation: Reads superficial input only, does not use execution results from other nodes
- **UI Indicator**:
- 🟢 Full Cache: All wildcards pre-loaded
- 🔵 On-Demand: Shows count of loaded wildcards
**ImpactWildcardEncode**:
- All features of ImpactWildcardProcessor
- **LoRA Loading**: `<lora:name:model_weight:clip_weight>` syntax
- If `clip_weight` omitted, uses same value as `model_weight`
- All loaded LoRAs applied to both `model` and `clip` outputs
- **LoRA Block Weight (LBW)** (requires Inspire Pack):
- Syntax: `<lora:name:model_weight:clip_weight:LBW=spec;>`
- Use `;` as separator within spec, recommended to end with `;`
- Specs without `A=` or `B=` → used in `Lora Loader (Block Weight)` node
- Specs with `A=` or `B=` → parameters for `A` and `B` in loader node
- Examples:
- `<lora:chunli:1.0:1.0:LBW=B11:0,0,0,0,0,0,0,0,0,0,A,0,0,0,0,0,0;A=0.;>`
- `<lora:chunli:1.0:1.0:LBW=0,0,0,0,0,0,0,0,0,0,A,B,0,0,0,0,0;A=0.5;B=0.2;>`
- `<lora:chunli:1.0:1.0:LBW=SD-MIDD;>`
- **BREAK Syntax**: Separately encode prompts and connect using `Conditioning (Concat)`
- **Output**: Returns processed conditioning with all LoRAs applied
---
### 2.4 Detailer Integration
Special syntax for Detailer Wildcard nodes (region-specific prompt application).
**Ordering Control** (place at very beginning of prompt):
- `[ASC]` - Ascending order by (x, y) coordinates (left takes precedence, then top)
- `[DSC]` - Descending order by (x, y) coordinates
- `[ASC-SIZE]` - Ascending order by area size
- `[DSC-SIZE]` - Descending order by area size
- `[RND]` - Random order
- Example: `[ASC]\n1girl, blue eyes, smile [SEP]\n1boy, brown eyes [SEP]`
**Area Control**:
- `[SEP]` - Separator for different prompts per detection area (SEG)
- `[SKIP]` - Skip detailing for current SEG
- `[STOP]` - Stop detailing, including current SEG
- `[CONCAT]` - Concatenate wildcard conditioning with positive conditioning (instead of replacing)
**Label-Based Application**:
- `[LAB]` - Apply prompts based on labels (each label appears once)
- `[ALL]` - Prefix that applies to all labels
- Example:
```
[LAB]
[ALL] laugh, detailed eyes
[Female] blue eyes
[Male] brown eyes
```
Female labels get: "laugh, detailed eyes, blue eyes"
Male labels get: "laugh, detailed eyes, brown eyes"
**Complete Example**:
```
[DSC-SIZE]
sun glasses[SEP]
[SKIP][SEP]
blue glasses[SEP]
[STOP]
```
Result: Faces sorted by size descending, largest gets "sun glasses", second largest skipped, third gets "blue glasses", rest not detailed.
---
### 2.5 File Formats
**TXT Files**:
- **Format**: One option per line (comma-separated on single line = one item)
- **Comments**: Lines starting with `#` are comments
- **Encoding**: UTF-8
- **Loading**: Supports on-demand loading (loaded only when used)
- **Subfolder Support**: Use path in wildcard name (e.g., `custom_wildcards/obj/person.txt` → `__obj/person__`)
- **Example** (flower.txt):
```
rose
orchid
iris
carnation
lily
```
**YAML Files** (V4.18.4+):
- **Format**: Nested hierarchical structure with multiple levels
- **Usage**: Keys become wildcard paths (e.g., `astronomy.Celestial-Bodies` → `__astronomy/Celestial-Bodies__`)
- **Loading**: Always pre-loaded at startup (keys exist in file content, not path)
- **Example**:
```yaml
astronomy:
Celestial-Bodies:
- Star
- Planet
surface-swap:
- swap the surfaces for
- replace the surfaces with
```
- **Performance Note**: For large collections with on-demand loading, prefer TXT file structure over YAML
**Wildcard Directories**:
- Default directories: `ComfyUI-Impact-Pack/wildcards/` and `ComfyUI-Impact-Pack/custom_wildcards/`
- Recommendation: Use `custom_wildcards/` to avoid conflicts during updates
- Custom path: Configure via `impact-pack.ini` → `custom_wildcards` setting
---
### 2.6 System Features
**Progressive On-Demand Loading** ⭐:
- **Automatic Mode Detection**: System chooses optimal loading strategy based on collection size
- **Full Cache Mode** (total size < 50MB):
- All wildcards loaded into memory at startup
- Instant access with no load delays
- UI Indicator: 🟢 `Select Wildcard 🟢 Full Cache`
- Startup log: `Using full cache mode.`
- **On-Demand Mode** (total size ≥ 50MB):
- Only metadata scanned at startup (< 1 minute for 10GB+)
- Actual wildcard data loaded progressively as accessed
- Low initial memory (< 100MB)
- UI Indicator: 🔵 `Select Wildcard 🔵 On-Demand: X loaded`
- Startup log: `Using on-demand loading mode (metadata scan only).`
- **Configuration**: Adjust threshold via `impact-pack.ini` → `wildcard_cache_limit_mb = 50`
- **File Type Behavior**:
- TXT files: Full on-demand loading support
- YAML files: Always pre-loaded (keys embedded in content)
- **Refresh Behavior**: Clears all cached data, re-scans directories, re-determines mode
**Depth-Agnostic Matching** ⭐:
- **Automatic Fallback**: When direct lookup fails, searches for pattern matches at any depth
- **Pattern Matching**: Finds keys that end with, start with, or contain the wildcard name
- **Multi-Source Combination**: Combines all matched wildcards into single selection pool
- **Zero Configuration**: Works automatically with any directory structure
- **Performance**: Results cached for subsequent access
**Wildcard Refresh API**:
- `GET /impact/wildcards/refresh` - Reload wildcards without restarting ComfyUI
- Clears all cached data (full cache and on-demand loaded)
- Re-scans wildcard directories
- Re-determines loading mode
**Other APIs**:
- `POST /impact/wildcards` - Process wildcard text with seed
- `GET /impact/wildcards/list` - List all available wildcards
- `GET /impact/wildcards/list/loaded` - Show currently loaded wildcards (on-demand mode)
**Deterministic Generation**:
- Seed-based random selection ensures reproducibility
- Same seed + same wildcard = same result
- Compatible with ImpactInt and Seed(rgthree) nodes
---
## 3. Requirements
### 3.1 Functional Requirements
**FR-1: Wildcard Processing**
- Support all documented syntax patterns
- Deterministic results with seed control
- Up to 100 levels of nested expansion
- Graceful error handling
**FR-2: Dynamic Prompts**
- Random, weighted, and multi-select
- Unlimited nesting depth
- Custom separators
**FR-3: Progressive Loading**
- Automatic mode detection
- On-demand loading for large collections
- Real-time tracking
**FR-4: Depth-Agnostic Matching**
- Automatic fallback pattern matching
- Combine all matched wildcards
- Support any directory structure
**FR-5: ComfyUI Integration**
- ImpactWildcardProcessor node
- ImpactWildcardEncode node with LoRA
- Detailer special syntax
---
### 3.2 Non-Functional Requirements
**NFR-1: Usability**
- Time to first success: < 5 minutes
- Zero configuration for basic use
- Clear error messages
**NFR-2: Reliability**
- 100% deterministic with same seed
- Graceful error handling
- No data loss on refresh
**NFR-3: Compatibility**
- Python 3.8+
- Windows, Linux, macOS
- Backward compatible with v1.x
**NFR-4: Scalability**
- Collections up to 100GB
- Up to 1M wildcard files
- Concurrent multi-user access
---
## 4. Configuration
**File**: `impact-pack.ini` (in ComfyUI-Impact-Pack directory)
```ini
[default]
# Custom wildcard directory (optional)
# Use this to specify additional wildcard directory path
custom_wildcards = /path/to/wildcards
# Cache size limit in MB (default: 50)
# Determines threshold for Full Cache vs On-Demand mode
wildcard_cache_limit_mb = 50
```
**Default Wildcard Directories**:
- `ComfyUI-Impact-Pack/wildcards/` - System wildcards (avoid modifying)
- `ComfyUI-Impact-Pack/custom_wildcards/` - User wildcards (recommended)
- Custom path via `custom_wildcards` setting (optional)
**Configuration Best Practices**:
- No configuration required for basic use
- Use `custom_wildcards/` to avoid conflicts during updates
- Adjust `wildcard_cache_limit_mb` based on system memory and collection size:
- Lower limit → More likely to use on-demand mode (slower first access, lower memory)
- Higher limit → More likely to use full cache mode (faster access, higher memory)
- For large collections (10GB+), consider organizing into subdirectories for better performance
---
## 5. User Workflows
### 5.1 Getting Started
**Goal**: First wildcard in < 5 minutes
1. Create file: `custom_wildcards/flower.txt`
2. Add content (one per line):
```
rose
orchid
iris
carnation
lily
```
3. Use in ImpactWildcardProcessor: `a beautiful __flower__`
4. Set mode to Populate and run queue prompt
5. Result: Random selection like "a beautiful rose"
### 5.2 Reusable Prompt Templates
**Goal**: Save frequently used prompts
1. Create `custom_wildcards/ppos.txt` with:
```
photorealistic:1.4, best quality:1.4
```
2. Use concise prompt: `__ppos__, beautiful nature`
3. Result: "photorealistic:1.4, best quality:1.4, beautiful nature"
### 5.3 Large Collections
**Goal**: Import 10GB+ seamlessly
1. Copy large wildcard collection to directory
2. Start ComfyUI (< 1 minute startup with on-demand mode)
3. Check UI indicator: 🔵 On-Demand mode active
4. Use wildcards immediately (loaded on first access)
5. Subsequent uses are cached for speed
### 5.4 LoRA + Wildcards
**Goal**: Dynamic character with LoRA
1. Create `custom_wildcards/characters.txt`:
```
<lora:char1:1.0:1.0> young girl with blue dress
<lora:char2:1.0:1.0> warrior with armor
<lora:char3:1.0:1.0> mage with robe
```
2. Use ImpactWildcardEncode node
3. Prompt: `__characters__, {day|night} scene, detailed face`
4. Result: Random character with LoRA loaded + random time of day
### 5.5 Multi-Face Detailing
**Goal**: Different prompts for multiple detected faces
1. Create Detailer Wildcard prompt:
```
[DSC-SIZE]
blue eyes, smile[SEP]
brown eyes, serious[SEP]
green eyes, laugh
```
2. Result: Largest face gets "blue eyes, smile", second gets "brown eyes, serious", third gets "green eyes, laugh"
---
## 6. References
### User Documentation
- **[ImpactWildcard Tutorial](../../../ComfyUI-extension-tutorials/ComfyUI-Impact-Pack/tutorial/ImpactWildcard.md)** - Complete feature documentation
### Technical Documentation
- **[Design Document](WILDCARD_SYSTEM_DESIGN.md)** - Architecture details
- **[Testing Guide](WILDCARD_TESTING_GUIDE.md)** - Test procedures
---
## Appendix: Glossary
- **Wildcard**: Reusable text snippet (`__name__`)
- **Dynamic Prompt**: Inline options (`{a|b|c}`)
- **Pattern Matching**: Finding wildcards by partial match
- **Depth-Agnostic**: Works with any directory structure
- **On-Demand Loading**: Load data when accessed
- **LoRA**: Low-Rank Adaptation models
- **Detailer**: Node for region-specific processing
---
**Last Updated**: 2025-11-18

View File

@@ -0,0 +1,381 @@
# Wildcard System Testing Guide
Complete testing guide for the ComfyUI Impact Pack wildcard system.
---
## 📋 Table of Contents
1. [Test Overview](#test-overview)
2. [Test Suites](#test-suites)
3. [Quick Start](#quick-start)
4. [Running Tests](#running-tests)
5. [Test Validation](#test-validation)
---
## Test Overview
### Test Statistics
- **Total Tests**: 86 tests across 7 suites
- **Coverage**: 100% of PRD core requirements
- **Pass Rate**: 100%
- **Test Types**: UTF-8, error handling, edge cases, nesting, on-demand, config, dynamic prompts
### Test Structure
```
tests/
├── Test Suites (7 suites, 86 tests)
│ ├── test_encoding.sh # 15 tests - UTF-8 multi-language support
│ ├── test_error_handling.sh # 10 tests - Error recovery and graceful handling
│ ├── test_edge_cases.sh # 20 tests - Boundary conditions and special cases
│ ├── test_deep_nesting.sh # 17 tests - 7-level transitive expansion + pattern matching
│ ├── test_ondemand_loading.sh # 8 tests - Progressive lazy loading with cache limits
│ ├── test_config_quotes.sh # 5 tests - Configuration path handling
│ └── test_dynamic_prompts_full.sh # 11 tests - Weighted/multiselect with statistical validation
├── Documentation
│ ├── README.md # Test suite overview
│ └── RUN_ALL_TESTS.md # Execution guide
├── Test Samples
│ └── wildcards/samples/ # Test wildcard files
│ ├── level1/.../level7/ # 7-level nesting structure
│ ├── *.txt # Various test wildcards
│ └── 아름다운색.txt # Korean UTF-8 sample
└── Utilities
└── restart_test_server.sh # Server management utility
```
---
## Test Suites
### 1. UTF-8 Encoding Tests (15 tests)
**File**: `test_encoding.sh`
**Port**: 8188
**Purpose**: Multi-language support validation
**Test Coverage**:
- Korean text (한글)
- Chinese text (中文)
- Arabic text (العربية)
- Emoji support (🐉🔥⚡)
- Special characters
- Mixed multi-language content
- Case-insensitive Korean matching
**Key Validations**:
- All non-ASCII characters preserved
- UTF-8 encoding consistency
- No character corruption
- Proper string comparison
---
### 2. Error Handling Tests (10 tests)
**File**: `test_error_handling.sh`
**Port**: 8189
**Purpose**: Graceful error recovery
**Test Coverage**:
- Non-existent wildcards
- Missing files
- Circular reference detection (direct and indirect)
- Malformed dynamic prompt syntax
- Deep nesting without crashes
- Invalid quantifiers
**Key Validations**:
- No server crashes
- Clear error messages
- Original text preserved on error
- Circular detection within 100 iterations
---
### 3. Edge Cases Tests (20 tests)
**File**: `test_edge_cases.sh`
**Port**: 8190
**Purpose**: Boundary conditions and special scenarios
**Test Coverage**:
- Empty lines and comments in wildcard files
- Very long lines (>1000 chars)
- Basic wildcard expansion
- Case-insensitive matching
- Quantifiers (1-10 repetitions)
- Pattern matching (`__*/name__`)
**Key Validations**:
- Empty lines filtered correctly
- Comments ignored properly
- Long text handling
- Quantifier accuracy
- Pattern matching at any depth
---
### 4. Deep Nesting Tests (17 tests)
**File**: `test_deep_nesting.sh`
**Port**: 8194
**Purpose**: 7-level transitive expansion and pattern matching
**Test Coverage**:
- Direct level access (Level 1-7)
- Transitive expansion through all levels
- Multiple wildcard nesting
- Mixed depth combinations
- Quantifiers with nesting
- Weighted selection with nesting
- Depth-agnostic pattern matching
**Key Validations**:
- All 7 levels fully expanded
- No unexpanded wildcards remain
- Pattern matching ignores directory depth
- Complex combinations work correctly
**Directory Structure**:
```
samples/level1/level2/level3/level4/level5/level6/level7/
```
---
### 5. On-Demand Loading Tests (8 tests)
**File**: `test_ondemand_loading.sh`
**Port**: 8191
**Purpose**: Progressive lazy loading with configurable cache limits
**Test Coverage**:
- Small cache (1MB) - On-demand mode
- Medium cache (10MB) - Hybrid mode
- Large cache (100MB) - Full cache mode
- Aggressive lazy (0.5MB)
- Various thresholds (5MB, 20MB, 50MB)
**Key Validations**:
- Correct loading mode selection
- Progressive loading functionality
- Cache limit enforcement
- No performance degradation
**Note**: Uses temporary samples in `/tmp/` with auto-cleanup
---
### 6. Config Quotes Tests (5 tests)
**File**: `test_config_quotes.sh`
**Port**: 8192
**Purpose**: Configuration path handling with quotes
**Test Coverage**:
- Paths with single quotes
- Paths with double quotes
- Paths with spaces (quoted)
- Mixed quote scenarios
- Unquoted baseline
**Key Validations**:
- Quotes stripped correctly
- Paths with spaces handled
- Wildcards loaded from quoted paths
---
### 7. Dynamic Prompts Tests (11 tests)
**File**: `test_dynamic_prompts_full.sh`
**Port**: 8193
**Purpose**: Statistical validation of weighted and multiselect features
**Test Coverage**:
- Multiselect (2-5 items) with custom separators
- Weighted selection (various ratios: 10:1, 1:1:1, 5:3:2)
- Nested dynamic prompts
- Basic random selection
- Seed variation validation
**Statistical Validation**:
- 100 iterations for weighted selection
- 20 iterations for multiselect
- Distribution verification (±15% tolerance)
- Duplicate detection
- Separator validation
**Key Validations**:
- Exact item count for multiselect
- No duplicates in multiselect
- Correct separators
- Statistical distribution matches weight ratios
- Nested prompt expansion
---
## Quick Start
### Run All Tests
```bash
cd tests/
bash test_encoding.sh && \
bash test_error_handling.sh && \
bash test_edge_cases.sh && \
bash test_deep_nesting.sh && \
bash test_ondemand_loading.sh && \
bash test_config_quotes.sh && \
bash test_dynamic_prompts_full.sh
```
### Run Individual Suite
```bash
cd tests/
bash test_encoding.sh
```
### Check Test Results
All tests output:
- ✅ PASS - Test succeeded with validation
- ❌ FAIL - Test failed (should not occur)
- ⚠️ WARNING - Partial success or non-critical issue
---
## Running Tests
### Prerequisites
- ComfyUI server must be installable
- Port availability (8188-8194)
- Network access to 127.0.0.1
- Python 3 with json module
### Automatic Server Management
All test suites automatically:
1. Kill any existing server on target port
2. Create temporary configuration file
3. Start ComfyUI server
4. Wait for server ready (up to 60s)
5. Execute tests
6. Clean up (kill server, remove config)
### Test Execution Flow
```
1. Setup
├─ Kill existing server on port
├─ Create impact-pack.ini config
└─ Start ComfyUI server
2. Wait for Ready
├─ Poll server every second
├─ Max 60 seconds timeout
└─ Log tail on failure
3. Execute Tests
├─ Call /impact/wildcards API
├─ Validate responses
└─ Check behavior
4. Cleanup
├─ Kill server process
└─ Remove config file
```
---
## Test Validation
### What Tests Validate
**Behavioral Validation** (Not just "no errors"):
- **Weighted Selection**: Statistical distribution matches weight ratios
- **Multiselect**: Exact count, no duplicates, correct separator
- **Nesting**: All levels fully expanded, no remaining wildcards
- **Pattern Matching**: Depth-agnostic matching works correctly
- **UTF-8**: Character preservation and proper encoding
- **Error Handling**: Graceful recovery with meaningful messages
### Success Criteria
- All 86 tests must pass (100% pass rate)
- No server crashes or hangs
- API responses within expected format
- Statistical distributions within ±15% tolerance
- No unexpanded wildcards in final output
### Validation Examples
**Weighted Selection**:
```bash
# Test 10:1 ratio with 100 iterations
# Expected: ~91% common, ~9% rare
# Actual: Count distribution within ±15%
```
**Multiselect**:
```bash
# Test {2$$, $$red|blue|green}
# Expected: Exactly 2 items, comma-space separator, no duplicates
# Validation: Count words, check separator, detect duplicates
```
**Pattern Matching**:
```bash
# Test __*/dragon__
# Expected: Matches dragon.txt, fantasy/dragon.txt, dragon/fire.txt
# Validation: No unexpanded wildcards remain
```
---
## Troubleshooting
### Common Issues
**Server Fails to Start**:
```bash
# Check log file
tail -20 /tmp/{test_name}_test.log
# Check port availability
lsof -i :8188
# Kill conflicting process
pkill -f "python.*main.py.*--port 8188"
```
**Tests Timeout**:
- Increase wait time in test script (default 60s)
- Check server performance and resources
- Verify network connectivity to 127.0.0.1
**Statistical Tests Fail**:
- Expected for very small sample sizes
- ±15% tolerance accounts for randomness
- Rerun test to verify consistency
**UTF-8 Issues**:
- Ensure terminal supports UTF-8
- Check file encoding: `file -i tests/wildcards/samples/*.txt`
- Verify locale: `locale | grep UTF-8`
---
## Test Maintenance
### Adding New Tests
1. Create new test function in appropriate suite
2. Follow existing test patterns (setup, execute, validate, cleanup)
3. Update test counts in README.md and SUMMARY.md
4. Update this guide with new test description
### Modifying Existing Tests
1. Preserve behavioral validation (not just "no errors")
2. Maintain statistical rigor for dynamic prompt tests
3. Update documentation if test purpose changes
4. Verify all 86 tests still pass after modification
### Test Philosophy
- **Tests validate behavior**, not just execution success
- **Statistical validation** for probabilistic features
- **Real-world scenarios** with production-like setup
- **Comprehensive coverage** of all PRD requirements