Files
xiaoxia-saas/tests/unit/test_dedup_pure.py
T
CI Bot bbf4108e2f
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 21s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 54s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 2m1s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 2m4s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 2m10s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 2m27s
CI/CD Pipeline / Validate - Code Quality (pull_request) Failing after 3m52s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 7m39s
Preview Deploy / Deploy Preview Environment (pull_request) Failing after 43s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m22s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m59s
AI Code Review / AI Code Review (pull_request) Successful in 5m48s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 2m35s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 2m37s
CI/CD Pipeline / Production Browser E2E (pull_request) Failing after 1276h3m5s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 1276h3m31s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 1276h3m33s
CI/CD Pipeline / Deploy Production (pull_request) Failing after 1276h3m37s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 1276h6m49s
CI/CD Pipeline / Build Production Worker Image (pull_request) Failing after 1276h6m51s
CI/CD Pipeline / Build Production Web Image (pull_request) Failing after 1276h6m53s
CI/CD Pipeline / Build Production API Image (pull_request) Failing after 1276h6m55s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 1276h6m57s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 1276h20m42s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 1276h20m46s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 1276h36m8s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 1276h53m17s
style: fix black formatting for wave70 dedup tests
2026-07-25 11:05:53 +08:00

165 lines
5.8 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""去重纯算法测试 — hamming_distance + histogram_similarity + VideoFingerprint."""
from __future__ import annotations
import sys
from unittest.mock import MagicMock
import numpy as np
import pytest
# 模块级mock cv2(dedup模块import时需要)
sys.modules["cv2"] = MagicMock()
from video_processing.dedup import ( # noqa: E402
VideoDeduplicator,
VideoFingerprint,
hamming_distance,
)
class TestHammingDistance:
"""hamming_distance 汉明距离计算测试."""
def test_identical_hashes_zero(self):
"""相同哈希距离为0."""
assert hamming_distance("ff", "ff") == 0
assert hamming_distance("00", "00") == 0
def test_all_different(self):
"""全不同的8bit哈希距离为8."""
assert hamming_distance("00", "ff") == 8
def test_single_bit_diff(self):
"""1个bit不同."""
# 0x01 = 00000001, 0x00 = 00000000 → 1 bit不同
assert hamming_distance("01", "00") == 1
def test_four_bits_diff(self):
"""4个bit不同."""
# 0x0F = 00001111, 0xF0 = 11110000 → 8 bits都不同
assert hamming_distance("0f", "f0") == 8
def test_longer_hashes(self):
"""更长的哈希(如64-bit pHash)."""
# 两个完全不同的64-bit哈希
assert hamming_distance("0000000000000000", "ffffffffffffffff") == 64
def test_partial_difference(self):
"""部分bit不同."""
# a = 1010, 5 = 0101 → 4 bits不同(每个hex digit)
assert hamming_distance("aa", "55") == 8
def test_case_insensitive(self):
"""十六进制不区分大小写."""
assert hamming_distance("FF", "ff") == 0
assert hamming_distance("AbC123", "aBc123") == 0
def test_different_length_hashes(self):
"""不同长度的哈希(短的前补零)."""
# "ff" = 0xff = 255, "0ff" = 0x0ff = 255
# int("ff", 16) = 255, int("0ff", 16) = 255
assert hamming_distance("ff", "0ff") == 0
class TestVideoFingerprint:
"""VideoFingerprint 数据结构测试."""
def test_to_dict_contains_all_fields(self):
"""to_dict返回完整字典."""
fp = VideoFingerprint(
md5="abc123",
keyframe_phashes=["hash1", "hash2"],
color_histograms=[[0.1, 0.2], [0.3, 0.4]],
duration=30.5,
resolution=(1920, 1080),
)
d = fp.to_dict()
assert d["md5"] == "abc123"
assert d["keyframe_phashes"] == ["hash1", "hash2"]
assert d["duration"] == 30.5
assert d["resolution"] == [1920, 1080]
assert "color_histograms" in d
def test_empty_phashes(self):
"""空关键帧列表."""
fp = VideoFingerprint(
md5="test",
keyframe_phashes=[],
color_histograms=[],
duration=0.0,
resolution=(0, 0),
)
d = fp.to_dict()
assert d["keyframe_phashes"] == []
assert d["color_histograms"] == []
class TestAverageHistogramSimilarity:
"""_average_histogram_similarity 直方图相似度测试."""
def test_identical_histograms(self):
"""完全相同的直方图相似度为1.0."""
hist = [[0.5, 0.5, 0.0], [0.3, 0.4, 0.3]]
sim = VideoDeduplicator._average_histogram_similarity(hist, hist)
assert sim == pytest.approx(1.0)
def test_empty_first_list(self):
"""第一组为空返回0."""
sim = VideoDeduplicator._average_histogram_similarity([], [[0.5, 0.5]])
assert sim == 0.0
def test_empty_second_list(self):
"""第二组为空返回0."""
sim = VideoDeduplicator._average_histogram_similarity([[0.5, 0.5]], [])
assert sim == 0.0
def test_both_empty(self):
"""两组都为空返回0."""
sim = VideoDeduplicator._average_histogram_similarity([], [])
assert sim == 0.0
def test_orthogonal_histograms(self):
"""正交直方图相似度为0."""
# [1, 0] 和 [0, 1] 正交
sim = VideoDeduplicator._average_histogram_similarity([[1.0, 0.0]], [[0.0, 1.0]])
assert sim == pytest.approx(0.0)
def test_partial_similarity(self):
"""部分相似."""
# [1, 1] 和 [1, 0] 的余弦相似度 = 1/√2 ≈ 0.707
sim = VideoDeduplicator._average_histogram_similarity([[1.0, 1.0]], [[1.0, 0.0]])
assert sim == pytest.approx(1.0 / (2**0.5), rel=0.01)
def test_multiple_frames_best_match(self):
"""多帧时取最佳匹配."""
# 第一帧完全不同,第二帧完全相同 → 平均 best = (0 + 1) / 2 = 0.5
sim = VideoDeduplicator._average_histogram_similarity(
[[1.0, 0.0], [0.0, 1.0]],
[[0.0, 1.0]], # 只有一帧,和第一帧0相似,和第二帧1相似
)
# 第一帧最佳匹配=0,第二帧最佳匹配=1,平均=0.5
assert sim == pytest.approx(0.5)
def test_zero_norm_histogram_skipped(self):
"""零范数直方图被跳过."""
sim = VideoDeduplicator._average_histogram_similarity([[0.0, 0.0]], [[1.0, 1.0]])
# 第一组的零范数被跳过,similarities为空,返回0
assert sim == 0.0
def test_different_length_histograms(self):
"""不同长度的直方图取最小长度对齐."""
sim = VideoDeduplicator._average_histogram_similarity(
[[1.0, 1.0, 0.0, 0.0]], # 4维
[[1.0, 1.0]], # 2维
)
# 对齐到前2维,都是[1,1],相似度1.0
assert sim == pytest.approx(1.0)
def test_similarity_in_zero_one_range(self):
"""相似度在[0, 1]范围内."""
hist_a = [np.random.rand(96).tolist() for _ in range(5)]
hist_b = [np.random.rand(96).tolist() for _ in range(5)]
sim = VideoDeduplicator._average_histogram_similarity(hist_a, hist_b)
assert 0.0 <= sim <= 1.0