ComfyUI-Manager/tests/common/pip_util/TEST_SCENARIOS.md
Dr.Lt.Data 2866193baf ● feat: Draft pip package policy management system (not yet integrated)
Add comprehensive pip dependency conflict resolution framework as draft implementation. This is self-contained and does not affect existing
ComfyUI Manager functionality.

Key components:
- pip_util.py with PipBatch class for policy-driven package management
- Lazy-loaded policy system supporting base + user overrides
- Multi-stage policy execution (uninstall → apply_first_match → apply_all_matches → restore)
- Conditional policies based on platform, installed packages, and ComfyUI version
- Comprehensive test suite covering edge cases, workflows, and platform scenarios
- Design and implementation documentation

Policy capabilities (draft):
- Package replacement (e.g., PIL → Pillow, opencv-python → opencv-contrib-python)
- Version pinning to prevent dependency conflicts
- Dependency protection during installations
- Platform-specific handling (Linux/Windows, GPU detection)
- Pre-removal and post-restoration workflows

Testing infrastructure:
- Pytest-based test suite with isolated environments
- Dependency analysis tools for conflict detection
- Coverage for policy priority, edge cases, and environment recovery

Status: Draft implementation complete, integration with manager workflows pending.
2025-10-04 08:55:59 +09:00

574 lines
13 KiB
Markdown

# pip_util Test Scenarios - Test Data Specification
This document precisely defines all test scenarios, packages, versions, and expected behaviors used in the pip_util test suite.
## Table of Contents
1. [Test Scenario 1: Dependency Version Protection](#scenario-1-dependency-version-protection)
2. [Test Scenario 2: Complex Dependency Chain](#scenario-2-complex-dependency-chain)
3. [Test Scenario 3: Package Deletion and Restore](#scenario-3-package-deletion-and-restore)
4. [Test Scenario 4: Version Change and Restore](#scenario-4-version-change-and-restore)
5. [Test Scenario 5: Full Workflow Integration](#scenario-5-full-workflow-integration)
6. [Test Scenario 6: Pin Failure Retry](#scenario-6-pin-failure-retry)
---
## Scenario 1: Dependency Version Protection
**File**: `test_dependency_protection.py::test_dependency_version_protection_with_pin`
**Purpose**: Verify that `pin_dependencies` policy prevents dependency upgrades during package installation.
### Initial Environment State
```python
installed_packages = {
"urllib3": "1.26.15", # OLD stable version
"certifi": "2023.7.22", # OLD version
"charset-normalizer": "3.2.0" # OLD version
}
```
### Policy Configuration
```json
{
"requests": {
"apply_all_matches": [
{
"type": "pin_dependencies",
"pinned_packages": ["urllib3", "certifi", "charset-normalizer"],
"on_failure": "retry_without_pin"
}
]
}
}
```
### Action
```python
batch.install("requests")
```
### Expected pip Command
```bash
pip install requests urllib3==1.26.15 certifi==2023.7.22 charset-normalizer==3.2.0
```
### Expected Final State
```python
installed_packages = {
"urllib3": "1.26.15", # PROTECTED - stayed at old version
"certifi": "2023.7.22", # PROTECTED - stayed at old version
"charset-normalizer": "3.2.0", # PROTECTED - stayed at old version
"requests": "2.31.0" # NEWLY installed
}
```
### Without Pin (What Would Happen)
```python
# If pin_dependencies was NOT used:
installed_packages = {
"urllib3": "2.1.0", # UPGRADED to 2.x (breaking change)
"certifi": "2024.2.2", # UPGRADED to latest
"charset-normalizer": "3.3.2", # UPGRADED to latest
"requests": "2.31.0"
}
```
**Key Point**: Pin prevents `urllib3` from upgrading to 2.x, which has breaking API changes.
---
## Scenario 2: Complex Dependency Chain
**File**: `test_dependency_protection.py::test_dependency_chain_with_click_colorama`
**Purpose**: Verify that `force_version` + `pin_dependencies` work together correctly.
### Initial Environment State
```python
installed_packages = {
"colorama": "0.4.6" # Existing dependency
}
```
### Policy Configuration
```json
{
"click": {
"apply_first_match": [
{
"condition": {
"type": "installed",
"package": "colorama",
"spec": "<0.5.0"
},
"type": "force_version",
"version": "8.1.3",
"reason": "click 8.1.3 compatible with colorama <0.5"
}
],
"apply_all_matches": [
{
"type": "pin_dependencies",
"pinned_packages": ["colorama"]
}
]
}
}
```
### Condition Evaluation
```python
# Check: colorama installed AND version < 0.5.0?
colorama_installed = True
colorama_version = "0.4.6" # 0.4.6 < 0.5.0 → True
# Result: Condition satisfied → apply force_version
```
### Action
```python
batch.install("click")
```
### Expected pip Command
```bash
pip install click==8.1.3 colorama==0.4.6
```
### Expected Final State
```python
installed_packages = {
"colorama": "0.4.6", # PINNED - version protected
"click": "8.1.3" # FORCED to specific version
}
```
**Key Point**:
- `force_version` forces click to install version 8.1.3
- `pin_dependencies` ensures colorama stays at 0.4.6
---
## Scenario 3: Package Deletion and Restore
**File**: `test_environment_recovery.py::test_package_deletion_and_restore`
**Purpose**: Verify that deleted packages can be restored to required versions.
### Initial Environment State
```python
installed_packages = {
"six": "1.16.0", # Critical package
"attrs": "23.1.0",
"packaging": "23.1"
}
```
### Policy Configuration
```json
{
"six": {
"restore": [
{
"target": "six",
"version": "1.16.0",
"reason": "six must be maintained at 1.16.0 for compatibility"
}
]
}
}
```
### Action Sequence
**Step 1**: Install package that removes six
```python
batch.install("python-dateutil")
```
**Step 1 Result**: six is DELETED
```python
installed_packages = {
# "six": "1.16.0", # ❌ DELETED by python-dateutil
"attrs": "23.1.0",
"packaging": "23.1",
"python-dateutil": "2.8.2" # ✅ NEW
}
```
**Step 2**: Restore deleted packages
```python
batch.ensure_installed()
```
**Step 2 Result**: six is RESTORED
```python
installed_packages = {
"six": "1.16.0", # ✅ RESTORED to required version
"attrs": "23.1.0",
"packaging": "23.1",
"python-dateutil": "2.8.2"
}
```
### Expected pip Commands
```bash
# Step 1: Install
pip install python-dateutil
# Step 2: Restore
pip install six==1.16.0
```
**Key Point**: `restore` policy automatically reinstalls deleted packages.
---
## Scenario 4: Version Change and Restore
**File**: `test_environment_recovery.py::test_version_change_and_restore`
**Purpose**: Verify that packages with changed versions can be restored to required versions.
### Initial Environment State
```python
installed_packages = {
"urllib3": "1.26.15", # OLD version (required)
"certifi": "2023.7.22"
}
```
### Policy Configuration
```json
{
"urllib3": {
"restore": [
{
"condition": {
"type": "installed",
"spec": "!=1.26.15"
},
"target": "urllib3",
"version": "1.26.15",
"reason": "urllib3 must be 1.26.15 for compatibility"
}
]
}
}
```
### Action Sequence
**Step 1**: Install package that upgrades urllib3
```python
batch.install("requests")
```
**Step 1 Result**: urllib3 is UPGRADED
```python
installed_packages = {
"urllib3": "2.1.0", # ❌ UPGRADED from 1.26.15 to 2.1.0
"certifi": "2023.7.22",
"requests": "2.31.0" # ✅ NEW
}
```
**Step 2**: Check restore condition
```python
# Condition: urllib3 installed AND version != 1.26.15?
urllib3_version = "2.1.0"
condition_met = (urllib3_version != "1.26.15") # True
# Result: Restore urllib3 to 1.26.15
```
**Step 2**: Restore to required version
```python
batch.ensure_installed()
```
**Step 2 Result**: urllib3 is DOWNGRADED
```python
installed_packages = {
"urllib3": "1.26.15", # ✅ RESTORED to required version
"certifi": "2023.7.22",
"requests": "2.31.0"
}
```
### Expected pip Commands
```bash
# Step 1: Install (causes upgrade)
pip install requests
# Step 2: Restore (downgrade)
pip install urllib3==1.26.15
```
**Key Point**: `restore` with condition can revert unwanted version changes.
---
## Scenario 5: Full Workflow Integration
**File**: `test_full_workflow_integration.py::test_uninstall_install_restore_workflow`
**Purpose**: Verify complete workflow: uninstall → install → restore.
### Initial Environment State
```python
installed_packages = {
"old-package": "1.0.0", # To be removed
"critical-package": "1.2.3", # To be restored
"urllib3": "1.26.15",
"certifi": "2023.7.22"
}
```
### Policy Configuration
```json
{
"old-package": {
"uninstall": [
{
"target": "old-package"
}
]
},
"requests": {
"apply_all_matches": [
{
"type": "pin_dependencies",
"pinned_packages": ["urllib3", "certifi"]
}
]
},
"critical-package": {
"restore": [
{
"target": "critical-package",
"version": "1.2.3"
}
]
}
}
```
### Action Sequence
**Step 1**: Remove old packages
```python
removed = batch.ensure_not_installed()
```
**Step 1 Result**:
```python
installed_packages = {
# "old-package": "1.0.0", # ❌ REMOVED
"critical-package": "1.2.3",
"urllib3": "1.26.15",
"certifi": "2023.7.22"
}
removed = ["old-package"]
```
**Step 2**: Install new package with pins
```python
batch.install("requests")
```
**Step 2 Result**:
```python
installed_packages = {
"critical-package": "1.2.3",
"urllib3": "1.26.15", # PINNED - no upgrade
"certifi": "2023.7.22", # PINNED - no upgrade
"requests": "2.31.0" # NEW
}
```
**Step 3**: Restore required packages
```python
restored = batch.ensure_installed()
```
**Step 3 Result**:
```python
installed_packages = {
"critical-package": "1.2.3", # Still present
"urllib3": "1.26.15",
"certifi": "2023.7.22",
"requests": "2.31.0"
}
restored = [] # Nothing to restore (all present)
```
### Expected pip Commands
```bash
# Step 1: Uninstall
pip uninstall -y old-package
# Step 2: Install with pins
pip install requests urllib3==1.26.15 certifi==2023.7.22
# Step 3: (No command - all packages present)
```
**Key Point**: Complete workflow demonstrates policy coordination.
---
## Scenario 6: Pin Failure Retry
**File**: `test_pin_failure_retry.py::test_pin_failure_retry_without_pin_succeeds`
**Purpose**: Verify automatic retry without pins when installation with pins fails.
### Initial Environment State
```python
installed_packages = {
"urllib3": "1.26.15",
"certifi": "2023.7.22"
}
```
### Policy Configuration
```json
{
"requests": {
"apply_all_matches": [
{
"type": "pin_dependencies",
"pinned_packages": ["urllib3", "certifi"],
"on_failure": "retry_without_pin"
}
]
}
}
```
### Action
```python
batch.install("requests")
```
### Attempt 1: Install WITH pins (FAILS)
```bash
# Command:
pip install requests urllib3==1.26.15 certifi==2023.7.22
# Result: FAILURE (dependency conflict)
# Error: "Package conflict: requests requires urllib3>=2.0"
```
### Attempt 2: Retry WITHOUT pins (SUCCEEDS)
```bash
# Command:
pip install requests
# Result: SUCCESS
```
**Final State**:
```python
installed_packages = {
"urllib3": "2.1.0", # UPGRADED (pins removed)
"certifi": "2024.2.2", # UPGRADED (pins removed)
"requests": "2.31.0" # INSTALLED
}
```
### Expected Behavior
1. **First attempt**: Install with pinned versions
2. **On failure**: Log warning about conflict
3. **Retry**: Install without pins
4. **Success**: Package installed, dependencies upgraded
**Key Point**: `retry_without_pin` provides automatic fallback for compatibility issues.
---
## Scenario 6b: Pin Failure with Hard Fail
**File**: `test_pin_failure_retry.py::test_pin_failure_with_fail_raises_exception`
**Purpose**: Verify that `on_failure: fail` raises exception instead of retrying.
### Initial Environment State
```python
installed_packages = {
"urllib3": "1.26.15",
"certifi": "2023.7.22"
}
```
### Policy Configuration
```json
{
"requests": {
"apply_all_matches": [
{
"type": "pin_dependencies",
"pinned_packages": ["urllib3", "certifi"],
"on_failure": "fail"
}
]
}
}
```
### Action
```python
batch.install("requests")
```
### Attempt 1: Install WITH pins (FAILS)
```bash
# Command:
pip install requests urllib3==1.26.15 certifi==2023.7.22
# Result: FAILURE (dependency conflict)
# Error: "Package conflict: requests requires urllib3>=2.0"
```
### Expected Behavior
1. **First attempt**: Install with pinned versions
2. **On failure**: Raise `subprocess.CalledProcessError`
3. **No retry**: Exception propagates to caller
4. **No changes**: Environment unchanged
**Key Point**: `on_failure: fail` ensures strict version requirements.
---
## Summary Table: All Test Packages
| Package | Initial Version | Action | Final Version | Role |
|---------|----------------|--------|---------------|------|
| **urllib3** | 1.26.15 | Pin | 1.26.15 | Protected dependency |
| **certifi** | 2023.7.22 | Pin | 2023.7.22 | Protected dependency |
| **charset-normalizer** | 3.2.0 | Pin | 3.2.0 | Protected dependency |
| **requests** | (not installed) | Install | 2.31.0 | New package |
| **colorama** | 0.4.6 | Pin | 0.4.6 | Protected dependency |
| **click** | (not installed) | Force version | 8.1.3 | New package with forced version |
| **six** | 1.16.0 | Delete→Restore | 1.16.0 | Deleted then restored |
| **python-dateutil** | (not installed) | Install | 2.8.2 | Package that deletes six |
| **attrs** | 23.1.0 | No change | 23.1.0 | Bystander package |
| **packaging** | 23.1 | No change | 23.1 | Bystander package |
## Policy Types Summary
| Policy Type | Purpose | Example |
|-------------|---------|---------|
| **pin_dependencies** | Prevent dependency upgrades | Keep urllib3 at 1.26.15 |
| **force_version** | Force specific package version | Install click==8.1.3 |
| **restore** | Reinstall deleted/changed packages | Restore six to 1.16.0 |
| **uninstall** | Remove obsolete packages | Remove old-package |
| **on_failure** | Handle installation failures | retry_without_pin or fail |
## Test Data Design Principles
1. **Lightweight Packages**: All packages are <200KB for fast testing
2. **Real Dependencies**: Use actual PyPI package relationships
3. **Version Realism**: Use real version numbers from PyPI
4. **Clear Scenarios**: Each test demonstrates one clear behavior
5. **Reproducible**: Mock ensures consistent behavior across environments