cd6d8615e6
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 1s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 30s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 30s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m40s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 1m38s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 2m5s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m30s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m50s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 3m30s
AI Code Review / AI Code Review (pull_request) Successful in 6m41s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m34s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 9s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 1m37s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 20m16s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 5s
新增 5 个测试覆盖 int seed 入参分支(reproducible/与 Random 对象 等价/None 默认),满足 diff coverage ≥60% 要求。
142 lines
5.1 KiB
Python
142 lines
5.1 KiB
Python
"""像素级扰动滤镜单元测试(Issue #1765)。
|
|
|
|
覆盖:
|
|
- generate_pixel_perturbation:生成像素级扰动参数
|
|
- 滤镜组合:2-3 种滤镜随机组合
|
|
- 参数范围:肉眼不可见但帧级可检测
|
|
- FFmpeg 滤镜语法生成
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import random
|
|
|
|
import pytest
|
|
|
|
from packages.domain.variant_plan_selector import generate_pixel_perturbation
|
|
|
|
|
|
class TestGeneratePixelPerturbation:
|
|
"""generate_pixel_perturbation 测试。"""
|
|
|
|
def test_returns_dict(self):
|
|
"""返回 dict。"""
|
|
result = generate_pixel_perturbation()
|
|
assert isinstance(result, dict)
|
|
|
|
def test_has_filters_key(self):
|
|
"""包含 filters 键。"""
|
|
result = generate_pixel_perturbation()
|
|
assert "filters" in result
|
|
|
|
def test_filters_count_2_or_3(self):
|
|
"""选 2-3 种滤镜。"""
|
|
for _ in range(50):
|
|
result = generate_pixel_perturbation()
|
|
assert len(result["filters"]) in [2, 3]
|
|
|
|
def test_filters_from_valid_options(self):
|
|
"""滤镜来自有效选项。"""
|
|
valid_options = {"noise", "unsharp", "curves", "color_balance"}
|
|
for _ in range(50):
|
|
result = generate_pixel_perturbation()
|
|
for f in result["filters"]:
|
|
assert f in valid_options
|
|
|
|
def test_noise_parameters(self):
|
|
"""noise 滤镜有正确参数范围。"""
|
|
for _ in range(20):
|
|
result = generate_pixel_perturbation()
|
|
if "noise" in result["filters"]:
|
|
strength = result.get("noise_strength", 0)
|
|
assert 0.01 <= strength <= 0.02
|
|
|
|
def test_unsharp_parameters(self):
|
|
"""unsharp 滤镜有正确参数范围。"""
|
|
for _ in range(20):
|
|
result = generate_pixel_perturbation()
|
|
if "unsharp" in result["filters"]:
|
|
amount = result.get("unsharp_amount", 0)
|
|
assert -0.5 <= amount <= 0.5
|
|
|
|
def test_curves_parameters(self):
|
|
"""curves 滤镜有正确参数范围。"""
|
|
for _ in range(20):
|
|
result = generate_pixel_perturbation()
|
|
if "curves" in result["filters"]:
|
|
contrast = result.get("curves_contrast", 1.0)
|
|
assert 0.95 <= contrast <= 1.05
|
|
|
|
def test_color_balance_parameters(self):
|
|
"""color_balance 滤镜有正确参数范围。"""
|
|
valid_colors = [-5, -3, 0, 3, 5]
|
|
for _ in range(20):
|
|
result = generate_pixel_perturbation()
|
|
if "color_balance" in result["filters"]:
|
|
assert result.get("color_r") in valid_colors
|
|
assert result.get("color_g") in valid_colors
|
|
assert result.get("color_b") in valid_colors
|
|
|
|
def test_same_seed_same_result(self):
|
|
"""相同 seed 返回相同结果。"""
|
|
rng1 = random.Random(42)
|
|
rng2 = random.Random(42)
|
|
result1 = generate_pixel_perturbation(rng1)
|
|
result2 = generate_pixel_perturbation(rng2)
|
|
assert result1 == result2
|
|
|
|
def test_different_seeds_may_differ(self):
|
|
"""不同 seed 可能返回不同结果。"""
|
|
results = set()
|
|
for seed in range(20):
|
|
rng = random.Random(seed)
|
|
result = generate_pixel_perturbation(rng)
|
|
results.add(tuple(result["filters"]))
|
|
# 20 个 seed 至少看到 3 种不同组合
|
|
assert len(results) >= 3
|
|
|
|
|
|
class TestPixelPerturbationAcceptance:
|
|
"""Issue #1765 验收测试。"""
|
|
|
|
def test_batch_3_variants_have_different_filters(self):
|
|
"""批量 3 个变体有不同的滤镜组合。"""
|
|
results = []
|
|
for seed in [100, 200, 300]:
|
|
rng = random.Random(seed)
|
|
result = generate_pixel_perturbation(rng)
|
|
results.append(tuple(result["filters"]))
|
|
|
|
# 至少 2 种不同组合
|
|
unique = len(set(results))
|
|
assert unique >= 2, f"Expected >= 2 unique filter combos, got {unique}: {results}"
|
|
|
|
|
|
class TestIntSeedSupport:
|
|
"""int seed 入参支持(与 get_rhythm_template(seed) 接口一致)。"""
|
|
|
|
def test_int_seed_returns_dict(self):
|
|
"""int seed 正常返回 dict。"""
|
|
result = generate_pixel_perturbation(42)
|
|
assert isinstance(result, dict)
|
|
assert "filters" in result
|
|
|
|
def test_int_seed_reproducible(self):
|
|
"""相同 int seed 结果一致。"""
|
|
assert generate_pixel_perturbation(42) == generate_pixel_perturbation(42)
|
|
|
|
def test_int_seed_differs_across_seeds(self):
|
|
"""不同 int seed 大概率不同(遍历确认至少 2 种组合)。"""
|
|
results = {tuple(generate_pixel_perturbation(s)["filters"]) for s in range(30)}
|
|
assert len(results) >= 2
|
|
|
|
def test_int_seed_matches_random_obj(self):
|
|
"""int seed 与等价 random.Random(seed) 结果一致。"""
|
|
assert generate_pixel_perturbation(7) == generate_pixel_perturbation(random.Random(7))
|
|
|
|
def test_none_seed_works(self):
|
|
"""None 入参(默认随机)正常返回。"""
|
|
result = generate_pixel_perturbation(None)
|
|
assert isinstance(result, dict)
|
|
assert len(result["filters"]) in [2, 3]
|