mirror of
https://git.datalinker.icu/ltdrdata/ComfyUI-Manager
synced 2025-12-10 22:54:30 +08:00
Remove package-level caching in cnr_utils and node_package modules to enable proper dynamic custom node installation and version switching without ComfyUI server restarts. Key Changes: - Remove @lru_cache decorators from version-sensitive functions - Remove cached_property from NodePackage for dynamic state updates - Add comprehensive test suite with parallel execution support - Implement version switching tests (CNR ↔ Nightly) - Add case sensitivity integration tests - Improve error handling and logging API Priority Rules (manager_core.py:1801): - Enabled-Priority: Show only enabled version when both exist - CNR-Priority: Show only CNR when both CNR and Nightly are disabled - Prevents duplicate package entries in /v2/customnode/installed API - Cross-match using cnr_id and aux_id for CNR ↔ Nightly detection Test Infrastructure: - 8 test files with 59 comprehensive test cases - Parallel test execution across 5 isolated environments - Automated test scripts with environment setup - Configurable timeout (60 minutes default) - Support for both master and dr-support-pip-cm branches Bug Fixes: - Fix COMFYUI_CUSTOM_NODES_PATH environment variable export - Resolve test fixture regression with module-level variables - Fix import timing issues in test configuration - Register pytest integration marker to eliminate warnings - Fix POSIX compliance in shell scripts (((var++)) → $((var + 1))) Documentation: - CNR_VERSION_MANAGEMENT_DESIGN.md v1.0 → v1.1 with API priority rules - Add test guides and execution documentation (TESTING_PROMPT.md) - Add security-enhanced installation guide - Create CLI migration guides and references - Document package version management 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
714 lines
24 KiB
Python
714 lines
24 KiB
Python
"""
|
||
Test cases for Nightly version downgrade and upgrade cycle.
|
||
|
||
Tests nightly package downgrade via git reset and subsequent upgrade via git pull.
|
||
This validates that update operations can recover from intentionally downgraded versions.
|
||
"""
|
||
|
||
import os
|
||
import subprocess
|
||
import time
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
|
||
|
||
# ============================================================================
|
||
# TEST CONFIGURATION - Easy to modify for different packages
|
||
# ============================================================================
|
||
|
||
# Test package configuration
|
||
TEST_PACKAGE_ID = "ComfyUI_SigmoidOffsetScheduler"
|
||
TEST_PACKAGE_CNR_ID = "comfyui_sigmoidoffsetscheduler"
|
||
|
||
# First commit SHA for reset tests
|
||
# This is the commit where untracked file conflicts occur after reset
|
||
# Update this if testing with a different package or commit history
|
||
FIRST_COMMIT_SHA = "b0eb1539f1de" # ComfyUI_SigmoidOffsetScheduler initial commit
|
||
|
||
# Alternative packages you can test with:
|
||
# Uncomment and modify as needed:
|
||
#
|
||
# TEST_PACKAGE_ID = "ComfyUI_Example_Package"
|
||
# TEST_PACKAGE_CNR_ID = "comfyui_example_package"
|
||
# FIRST_COMMIT_SHA = "abc1234567" # Your package's first commit
|
||
#
|
||
# To find your package's first commit:
|
||
# cd custom_nodes/YourPackage
|
||
# git rev-list --max-parents=0 HEAD
|
||
|
||
# ============================================================================
|
||
|
||
|
||
@pytest.fixture
|
||
def setup_nightly_package(api_client, custom_nodes_path):
|
||
"""Install Nightly version and ensure it has commit history."""
|
||
# Install Nightly version
|
||
response = api_client.queue_task(
|
||
kind="install",
|
||
ui_id="setup_nightly_downgrade",
|
||
params={
|
||
"id": TEST_PACKAGE_ID,
|
||
"version": "nightly",
|
||
"selected_version": "nightly",
|
||
},
|
||
)
|
||
assert response.status_code == 200
|
||
|
||
api_client.start_queue()
|
||
time.sleep(10)
|
||
|
||
# Verify Nightly installed
|
||
package_path = custom_nodes_path / TEST_PACKAGE_ID
|
||
assert package_path.exists(), "Nightly version should be installed"
|
||
|
||
git_dir = package_path / ".git"
|
||
assert git_dir.exists(), "Nightly package should have .git directory"
|
||
|
||
# Verify git repository has commits
|
||
result = subprocess.run(
|
||
["git", "rev-list", "--count", "HEAD"],
|
||
cwd=package_path,
|
||
capture_output=True,
|
||
text=True,
|
||
)
|
||
commit_count = int(result.stdout.strip())
|
||
assert commit_count > 0, "Git repository should have commit history"
|
||
|
||
yield package_path
|
||
|
||
# Cleanup
|
||
import shutil
|
||
if package_path.exists():
|
||
shutil.rmtree(package_path)
|
||
|
||
|
||
def get_current_commit(package_path: Path) -> str:
|
||
"""Get current git commit SHA."""
|
||
result = subprocess.run(
|
||
["git", "rev-parse", "HEAD"],
|
||
cwd=package_path,
|
||
capture_output=True,
|
||
text=True,
|
||
check=True,
|
||
)
|
||
return result.stdout.strip()
|
||
|
||
|
||
def get_commit_count(package_path: Path) -> int:
|
||
"""Get total commit count in git history."""
|
||
result = subprocess.run(
|
||
["git", "rev-list", "--count", "HEAD"],
|
||
cwd=package_path,
|
||
capture_output=True,
|
||
text=True,
|
||
check=True,
|
||
)
|
||
return int(result.stdout.strip())
|
||
|
||
|
||
def reset_to_previous_commit(package_path: Path, commits_back: int = 1) -> str:
|
||
"""
|
||
Reset git repository to previous commit(s).
|
||
|
||
Args:
|
||
package_path: Path to package directory
|
||
commits_back: Number of commits to go back (default: 1)
|
||
|
||
Returns:
|
||
New commit SHA after reset
|
||
"""
|
||
# Get current commit before reset
|
||
old_commit = get_current_commit(package_path)
|
||
|
||
# Reset to N commits back
|
||
reset_target = f"HEAD~{commits_back}"
|
||
result = subprocess.run(
|
||
["git", "reset", "--hard", reset_target],
|
||
cwd=package_path,
|
||
capture_output=True,
|
||
text=True,
|
||
check=True,
|
||
)
|
||
|
||
new_commit = get_current_commit(package_path)
|
||
|
||
# Verify commit actually changed
|
||
assert new_commit != old_commit, "Commit should change after reset"
|
||
|
||
return new_commit
|
||
|
||
|
||
@pytest.mark.priority_high
|
||
def test_nightly_downgrade_via_reset_then_upgrade(
|
||
api_client, custom_nodes_path, setup_nightly_package
|
||
):
|
||
"""
|
||
Test: Nightly downgrade via git reset, then upgrade via update API.
|
||
|
||
Workflow:
|
||
1. Install nightly (latest commit)
|
||
2. Manually downgrade via git reset HEAD~1
|
||
3. Trigger update via API (git pull)
|
||
4. Verify package upgraded back to latest
|
||
|
||
Verifies:
|
||
- Update can recover from manually downgraded nightly packages
|
||
- git pull correctly fetches and merges newer commits
|
||
- Package state remains valid throughout cycle
|
||
"""
|
||
package_path = setup_nightly_package
|
||
git_dir = package_path / ".git"
|
||
|
||
# Step 1: Get initial state (latest commit)
|
||
initial_commit = get_current_commit(package_path)
|
||
initial_count = get_commit_count(package_path)
|
||
|
||
print(f"\n[Initial State]")
|
||
print(f" Commit: {initial_commit[:8]}")
|
||
print(f" Total commits: {initial_count}")
|
||
|
||
# Verify we have enough history to downgrade
|
||
assert initial_count >= 2, "Need at least 2 commits to test downgrade"
|
||
|
||
# Step 2: Downgrade by resetting to previous commit
|
||
print(f"\n[Downgrading via git reset]")
|
||
downgraded_commit = reset_to_previous_commit(package_path, commits_back=1)
|
||
downgraded_count = get_commit_count(package_path)
|
||
|
||
print(f" Commit: {downgraded_commit[:8]}")
|
||
print(f" Total commits: {downgraded_count}")
|
||
|
||
# Verify downgrade succeeded
|
||
assert downgraded_commit != initial_commit, "Commit should change after downgrade"
|
||
assert downgraded_count == initial_count - 1, "Commit count should decrease by 1"
|
||
|
||
# Verify package still functional
|
||
assert git_dir.exists(), ".git directory should still exist after reset"
|
||
init_file = package_path / "__init__.py"
|
||
assert init_file.exists(), "Package should still be functional after reset"
|
||
|
||
# Step 3: Trigger update via API (should pull latest commit)
|
||
print(f"\n[Upgrading via update API]")
|
||
response = api_client.queue_task(
|
||
kind="update",
|
||
ui_id="test_nightly_upgrade_after_reset",
|
||
params={
|
||
"node_name": TEST_PACKAGE_ID,
|
||
"node_ver": "nightly",
|
||
},
|
||
)
|
||
assert response.status_code == 200, f"Failed to queue update task: {response.text}"
|
||
|
||
# Start queue and wait
|
||
response = api_client.start_queue()
|
||
assert response.status_code in [200, 201], f"Failed to start queue: {response.text}"
|
||
time.sleep(10)
|
||
|
||
# Step 4: Verify upgrade succeeded
|
||
upgraded_commit = get_current_commit(package_path)
|
||
upgraded_count = get_commit_count(package_path)
|
||
|
||
print(f" Commit: {upgraded_commit[:8]}")
|
||
print(f" Total commits: {upgraded_count}")
|
||
|
||
# Verify we're back to latest
|
||
assert upgraded_commit == initial_commit, \
|
||
f"Should return to initial commit. Expected {initial_commit[:8]}, got {upgraded_commit[:8]}"
|
||
assert upgraded_count == initial_count, \
|
||
f"Should return to initial commit count. Expected {initial_count}, got {upgraded_count}"
|
||
|
||
# Verify package integrity maintained
|
||
assert git_dir.exists(), ".git directory should be preserved after update"
|
||
assert init_file.exists(), "Package should be functional after update"
|
||
|
||
# Verify package is still nightly (no .tracking file)
|
||
tracking_file = package_path / ".tracking"
|
||
assert not tracking_file.exists(), "Nightly package should not have .tracking file"
|
||
|
||
print(f"\n[Test Summary]")
|
||
print(f" ✅ Downgrade: {initial_commit[:8]} → {downgraded_commit[:8]}")
|
||
print(f" ✅ Upgrade: {downgraded_commit[:8]} → {upgraded_commit[:8]}")
|
||
print(f" ✅ Recovered to initial state")
|
||
|
||
|
||
@pytest.mark.priority_high
|
||
def test_nightly_downgrade_multiple_commits_then_upgrade(
|
||
api_client, custom_nodes_path, setup_nightly_package
|
||
):
|
||
"""
|
||
Test: Nightly downgrade by multiple commits, then upgrade.
|
||
|
||
Workflow:
|
||
1. Install nightly (latest)
|
||
2. Reset to 3 commits back (if available)
|
||
3. Trigger update
|
||
4. Verify full upgrade to latest
|
||
|
||
Verifies:
|
||
- Update can handle larger commit gaps
|
||
- git pull correctly fast-forwards through multiple commits
|
||
"""
|
||
package_path = setup_nightly_package
|
||
|
||
# Get initial state
|
||
initial_commit = get_current_commit(package_path)
|
||
initial_count = get_commit_count(package_path)
|
||
|
||
print(f"\n[Initial State]")
|
||
print(f" Commit: {initial_commit[:8]}")
|
||
print(f" Total commits: {initial_count}")
|
||
|
||
# Determine how many commits to go back (max 3, or less if not enough history)
|
||
commits_to_reset = min(3, initial_count - 1)
|
||
|
||
if commits_to_reset < 1:
|
||
pytest.skip("Not enough commit history to test multi-commit downgrade")
|
||
|
||
print(f" Will reset {commits_to_reset} commit(s) back")
|
||
|
||
# Downgrade by multiple commits
|
||
print(f"\n[Downgrading by {commits_to_reset} commits]")
|
||
downgraded_commit = reset_to_previous_commit(package_path, commits_back=commits_to_reset)
|
||
downgraded_count = get_commit_count(package_path)
|
||
|
||
print(f" Commit: {downgraded_commit[:8]}")
|
||
print(f" Total commits: {downgraded_count}")
|
||
|
||
# Verify downgrade
|
||
assert downgraded_count == initial_count - commits_to_reset, \
|
||
f"Should have {commits_to_reset} fewer commits"
|
||
|
||
# Trigger update
|
||
print(f"\n[Upgrading via update API]")
|
||
response = api_client.queue_task(
|
||
kind="update",
|
||
ui_id="test_nightly_multi_commit_upgrade",
|
||
params={
|
||
"node_name": TEST_PACKAGE_ID,
|
||
"node_ver": "nightly",
|
||
},
|
||
)
|
||
assert response.status_code == 200
|
||
|
||
api_client.start_queue()
|
||
time.sleep(10)
|
||
|
||
# Verify full upgrade
|
||
upgraded_commit = get_current_commit(package_path)
|
||
upgraded_count = get_commit_count(package_path)
|
||
|
||
print(f" Commit: {upgraded_commit[:8]}")
|
||
print(f" Total commits: {upgraded_count}")
|
||
|
||
assert upgraded_commit == initial_commit, "Should return to initial commit"
|
||
assert upgraded_count == initial_count, "Should restore full commit history"
|
||
|
||
print(f"\n[Test Summary]")
|
||
print(f" ✅ Downgraded {commits_to_reset} commit(s)")
|
||
print(f" ✅ Upgraded back to latest")
|
||
print(f" ✅ Commit gap: {commits_to_reset} commits")
|
||
|
||
|
||
@pytest.mark.priority_medium
|
||
def test_nightly_verify_git_pull_behavior(
|
||
api_client, custom_nodes_path, setup_nightly_package
|
||
):
|
||
"""
|
||
Test: Verify git pull behavior when already at latest.
|
||
|
||
Workflow:
|
||
1. Install nightly (latest)
|
||
2. Trigger update (already at latest)
|
||
3. Verify no errors, commit unchanged
|
||
|
||
Verifies:
|
||
- Update operation is idempotent
|
||
- No errors when already up-to-date
|
||
- Package integrity maintained
|
||
"""
|
||
package_path = setup_nightly_package
|
||
|
||
# Get initial commit
|
||
initial_commit = get_current_commit(package_path)
|
||
|
||
print(f"\n[Initial State]")
|
||
print(f" Commit: {initial_commit[:8]}")
|
||
|
||
# Trigger update when already at latest
|
||
print(f"\n[Updating when already at latest]")
|
||
response = api_client.queue_task(
|
||
kind="update",
|
||
ui_id="test_nightly_already_latest",
|
||
params={
|
||
"node_name": TEST_PACKAGE_ID,
|
||
"node_ver": "nightly",
|
||
},
|
||
)
|
||
assert response.status_code == 200
|
||
|
||
api_client.start_queue()
|
||
time.sleep(8)
|
||
|
||
# Verify commit unchanged
|
||
final_commit = get_current_commit(package_path)
|
||
|
||
print(f" Commit: {final_commit[:8]}")
|
||
|
||
assert final_commit == initial_commit, \
|
||
"Commit should remain unchanged when already at latest"
|
||
|
||
# Verify package integrity
|
||
git_dir = package_path / ".git"
|
||
init_file = package_path / "__init__.py"
|
||
|
||
assert git_dir.exists(), ".git directory should be preserved"
|
||
assert init_file.exists(), "Package should remain functional"
|
||
|
||
print(f"\n[Test Summary]")
|
||
print(f" ✅ Update when already latest: no errors")
|
||
print(f" ✅ Commit unchanged: {initial_commit[:8]}")
|
||
print(f" ✅ Package integrity maintained")
|
||
|
||
|
||
@pytest.mark.priority_high
|
||
def test_nightly_reset_to_first_commit_with_unstaged_files(
|
||
api_client, custom_nodes_path, setup_nightly_package
|
||
):
|
||
"""
|
||
Test: Reset to first commit (creates unstaged files), then upgrade.
|
||
|
||
Critical Scenario:
|
||
- First commit: b0eb1539f1de (minimal files)
|
||
- Later commits: Added many files
|
||
- Reset to first commit → many files become untracked
|
||
- These files will conflict with git pull
|
||
|
||
Real-world case:
|
||
User resets to initial commit for debugging, then wants to update back.
|
||
The files added in later commits remain in working tree as untracked files,
|
||
causing git pull to fail with "would be overwritten" error.
|
||
|
||
Scenario:
|
||
1. Install nightly (latest)
|
||
2. Reset to first commit: git reset --hard b0eb1539f1de
|
||
3. Files added after first commit become untracked/unstaged
|
||
4. Trigger update (git pull should handle file conflicts)
|
||
5. Verify upgrade handles this critical edge case
|
||
|
||
Verifies:
|
||
- Update detects unstaged files that conflict with incoming changes
|
||
- Update either: stashes files, or reports clear error, or uses --force
|
||
- Package state remains valid (not corrupted)
|
||
- .git directory preserved
|
||
"""
|
||
package_path = setup_nightly_package
|
||
git_dir = package_path / ".git"
|
||
|
||
# Step 1: Get initial state
|
||
initial_commit = get_current_commit(package_path)
|
||
initial_count = get_commit_count(package_path)
|
||
|
||
print(f"\n[Initial State - Latest Commit]")
|
||
print(f" Commit: {initial_commit[:8]}")
|
||
print(f" Total commits: {initial_count}")
|
||
|
||
# Get list of tracked files at latest commit
|
||
result = subprocess.run(
|
||
["git", "ls-files"],
|
||
cwd=package_path,
|
||
capture_output=True,
|
||
text=True,
|
||
check=True,
|
||
)
|
||
files_at_latest = set(result.stdout.strip().split('\n'))
|
||
print(f" Files at latest: {len(files_at_latest)}")
|
||
|
||
# Verify we have enough history to reset to first commit
|
||
assert initial_count >= 2, "Need at least 2 commits to test reset to first"
|
||
|
||
# Step 2: Find first commit SHA
|
||
result = subprocess.run(
|
||
["git", "rev-list", "--max-parents=0", "HEAD"],
|
||
cwd=package_path,
|
||
capture_output=True,
|
||
text=True,
|
||
check=True,
|
||
)
|
||
first_commit = result.stdout.strip()
|
||
|
||
print(f"\n[First Commit Found]")
|
||
print(f" SHA: {first_commit[:8]}")
|
||
|
||
# Check if first commit matches configured commit
|
||
if first_commit.startswith(FIRST_COMMIT_SHA[:8]):
|
||
print(f" ✅ Matches configured first commit: {FIRST_COMMIT_SHA}")
|
||
else:
|
||
print(f" ℹ️ First commit: {first_commit[:12]}")
|
||
print(f" ⚠️ Expected: {FIRST_COMMIT_SHA[:12]}")
|
||
print(f" 💡 Update FIRST_COMMIT_SHA in test configuration if needed")
|
||
|
||
# Step 3: Reset to first commit
|
||
print(f"\n[Resetting to first commit]")
|
||
result = subprocess.run(
|
||
["git", "reset", "--hard", first_commit],
|
||
cwd=package_path,
|
||
capture_output=True,
|
||
text=True,
|
||
check=True,
|
||
)
|
||
|
||
downgraded_commit = get_current_commit(package_path)
|
||
downgraded_count = get_commit_count(package_path)
|
||
|
||
print(f" Current commit: {downgraded_commit[:8]}")
|
||
print(f" Total commits: {downgraded_count}")
|
||
assert downgraded_count == 1, "Should be at first commit (1 commit in history)"
|
||
|
||
# Get files at first commit
|
||
result = subprocess.run(
|
||
["git", "ls-files"],
|
||
cwd=package_path,
|
||
capture_output=True,
|
||
text=True,
|
||
check=True,
|
||
)
|
||
files_at_first = set(result.stdout.strip().split('\n'))
|
||
print(f" Files at first commit: {len(files_at_first)}")
|
||
|
||
# Files added after first commit (these will be untracked after reset)
|
||
new_files_in_later_commits = files_at_latest - files_at_first
|
||
|
||
print(f"\n[Files Added After First Commit]")
|
||
print(f" Count: {len(new_files_in_later_commits)}")
|
||
if new_files_in_later_commits:
|
||
# These files still exist in working tree but are now untracked
|
||
print(f" Sample files (now untracked):")
|
||
for file in list(new_files_in_later_commits)[:5]:
|
||
file_path = package_path / file
|
||
if file_path.exists():
|
||
print(f" ✓ {file} (exists as untracked)")
|
||
else:
|
||
print(f" ✗ {file} (was deleted by reset)")
|
||
|
||
# Check git status - should show untracked files
|
||
result = subprocess.run(
|
||
["git", "status", "--porcelain"],
|
||
cwd=package_path,
|
||
capture_output=True,
|
||
text=True,
|
||
)
|
||
status_output = result.stdout.strip()
|
||
|
||
if status_output:
|
||
untracked_count = len([line for line in status_output.split('\n') if line.startswith('??')])
|
||
print(f"\n[Untracked Files After Reset]")
|
||
print(f" Count: {untracked_count}")
|
||
print(f" First few:\n{status_output[:300]}")
|
||
else:
|
||
print(f"\n[No Untracked Files - reset --hard cleaned everything]")
|
||
|
||
# Step 4: Trigger update via API
|
||
print(f"\n[Triggering Update to Latest]")
|
||
print(f" Target: {initial_commit[:8]} (latest)")
|
||
print(f" Current: {downgraded_commit[:8]} (first commit)")
|
||
|
||
response = api_client.queue_task(
|
||
kind="update",
|
||
ui_id="test_nightly_upgrade_from_first_commit",
|
||
params={
|
||
"node_name": TEST_PACKAGE_ID,
|
||
"node_ver": "nightly",
|
||
},
|
||
)
|
||
assert response.status_code == 200, f"Failed to queue update task: {response.text}"
|
||
|
||
response = api_client.start_queue()
|
||
assert response.status_code in [200, 201], f"Failed to start queue: {response.text}"
|
||
time.sleep(15) # Longer wait for large update
|
||
|
||
# Step 5: Verify upgrade result
|
||
upgraded_commit = get_current_commit(package_path)
|
||
upgraded_count = get_commit_count(package_path)
|
||
|
||
print(f"\n[After Update Attempt]")
|
||
print(f" Commit: {upgraded_commit[:8]}")
|
||
print(f" Total commits: {upgraded_count}")
|
||
|
||
# Step 6: Check task history to see if update failed with proper error
|
||
history_response = api_client.get_queue_history()
|
||
assert history_response.status_code == 200, "Should get queue history"
|
||
|
||
history_data = history_response.json()
|
||
update_task = history_data.get("history", {}).get("test_nightly_upgrade_from_first_commit")
|
||
|
||
if update_task:
|
||
task_status = update_task.get("status", {})
|
||
status_str = task_status.get("status_str", "unknown")
|
||
messages = task_status.get("messages", [])
|
||
result_text = update_task.get("result", "")
|
||
|
||
print(f"\n[Update Task Result]")
|
||
print(f" Status: {status_str}")
|
||
print(f" Result: {result_text}")
|
||
if messages:
|
||
print(f" Messages: {messages}")
|
||
|
||
# Check upgrade result
|
||
if upgraded_commit == initial_commit:
|
||
# Case A or B: Update succeeded
|
||
print(f"\n ✅ Successfully upgraded to latest from first commit!")
|
||
print(f" Commit gap: {initial_count - 1} commits")
|
||
print(f" Implementation handles untracked files correctly")
|
||
assert upgraded_count == initial_count, "Should restore full commit history"
|
||
|
||
if update_task and status_str == "success":
|
||
print(f" ✅ Task status correctly reports success")
|
||
|
||
else:
|
||
# Case C: Update failed - must be properly reported
|
||
print(f"\n ⚠️ Update did not reach latest commit")
|
||
print(f" Expected: {initial_commit[:8]}")
|
||
print(f" Got: {upgraded_commit[:8]}")
|
||
print(f" Commit stayed at: first commit")
|
||
|
||
# CRITICAL: If update failed, task status MUST report failure
|
||
if update_task:
|
||
if status_str in ["failed", "error"]:
|
||
print(f" ✅ Task correctly reports failure: {status_str}")
|
||
print(f" This is acceptable - untracked files prevented update")
|
||
elif status_str == "success":
|
||
pytest.fail(
|
||
f"CRITICAL: Update failed (commit unchanged) but task reports success!\n"
|
||
f" Expected commit: {initial_commit[:8]}\n"
|
||
f" Actual commit: {upgraded_commit[:8]}\n"
|
||
f" Task status: {status_str}\n"
|
||
f" This is a bug - update must report failure when it fails"
|
||
)
|
||
else:
|
||
print(f" ⚠️ Unexpected task status: {status_str}")
|
||
else:
|
||
print(f" ⚠️ Update task not found in history")
|
||
|
||
# Verify package integrity (critical - must pass even if update failed)
|
||
assert git_dir.exists(), ".git directory should be preserved"
|
||
init_file = package_path / "__init__.py"
|
||
assert init_file.exists(), "Package should remain functional after failed update"
|
||
|
||
# Check final working tree status
|
||
result = subprocess.run(
|
||
["git", "status", "--porcelain"],
|
||
cwd=package_path,
|
||
capture_output=True,
|
||
text=True,
|
||
)
|
||
final_status = result.stdout.strip()
|
||
|
||
print(f"\n[Final Git Status]")
|
||
if final_status:
|
||
print(f" Has unstaged/untracked changes:")
|
||
print(f"{final_status[:300]}")
|
||
else:
|
||
print(f" ✅ Working tree clean")
|
||
|
||
print(f"\n[Test Summary]")
|
||
print(f" Initial commits: {initial_count}")
|
||
print(f" Reset to: first commit (1 commit)")
|
||
print(f" Final commits: {upgraded_count}")
|
||
print(f" Files added in later commits: {len(new_files_in_later_commits)}")
|
||
print(f" ✅ Package integrity maintained")
|
||
print(f" ✅ Git repository remains valid")
|
||
|
||
|
||
@pytest.mark.priority_high
|
||
def test_nightly_soft_reset_with_modified_files_then_upgrade(
|
||
api_client, custom_nodes_path, setup_nightly_package
|
||
):
|
||
"""
|
||
Test: Nightly soft reset (preserves changes) then upgrade.
|
||
|
||
Scenario:
|
||
1. Install nightly (latest)
|
||
2. Soft reset to previous commit (git reset --soft HEAD~1)
|
||
3. This leaves changes staged that match latest commit
|
||
4. Trigger update
|
||
5. Verify update handles staged changes correctly
|
||
|
||
This tests git reset --soft which is less destructive but creates
|
||
a different conflict scenario (staged vs unstaged).
|
||
|
||
Verifies:
|
||
- Update handles staged changes appropriately
|
||
- Package can recover from soft reset state
|
||
"""
|
||
package_path = setup_nightly_package
|
||
|
||
# Get initial state
|
||
initial_commit = get_current_commit(package_path)
|
||
initial_count = get_commit_count(package_path)
|
||
|
||
print(f"\n[Initial State]")
|
||
print(f" Commit: {initial_commit[:8]}")
|
||
|
||
assert initial_count >= 2, "Need at least 2 commits"
|
||
|
||
# Soft reset to previous commit (keeps changes staged)
|
||
print(f"\n[Soft reset to previous commit]")
|
||
result = subprocess.run(
|
||
["git", "reset", "--soft", "HEAD~1"],
|
||
cwd=package_path,
|
||
capture_output=True,
|
||
text=True,
|
||
check=True,
|
||
)
|
||
|
||
downgraded_commit = get_current_commit(package_path)
|
||
print(f" Commit: {downgraded_commit[:8]}")
|
||
|
||
# Verify changes are staged
|
||
result = subprocess.run(
|
||
["git", "status", "--porcelain"],
|
||
cwd=package_path,
|
||
capture_output=True,
|
||
text=True,
|
||
)
|
||
status_output = result.stdout.strip()
|
||
print(f" Staged changes:\n{status_output[:200]}...")
|
||
assert len(status_output) > 0, "Should have staged changes after soft reset"
|
||
|
||
# Trigger update
|
||
print(f"\n[Triggering update with staged changes]")
|
||
response = api_client.queue_task(
|
||
kind="update",
|
||
ui_id="test_nightly_upgrade_after_soft_reset",
|
||
params={
|
||
"node_name": TEST_PACKAGE_ID,
|
||
"node_ver": "nightly",
|
||
},
|
||
)
|
||
assert response.status_code == 200
|
||
|
||
api_client.start_queue()
|
||
time.sleep(12)
|
||
|
||
# Verify state after update
|
||
upgraded_commit = get_current_commit(package_path)
|
||
|
||
print(f"\n[After Update]")
|
||
print(f" Commit: {upgraded_commit[:8]}")
|
||
|
||
# Package should remain functional regardless of final commit state
|
||
git_dir = package_path / ".git"
|
||
init_file = package_path / "__init__.py"
|
||
|
||
assert git_dir.exists(), ".git directory should be preserved"
|
||
assert init_file.exists(), "Package should remain functional"
|
||
|
||
print(f"\n[Test Summary]")
|
||
print(f" ✅ Update completed after soft reset")
|
||
print(f" ✅ Package integrity maintained")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
pytest.main([__file__, "-v", "-s"])
|