speech-text-data-aligners / tests /unit /core /test_validation.py
pltobing's picture
chore(repo): finalize licensing and documentation
51e84d2
Raw History Blame Contribute Delete
4.77 kB
# License: Apache-2.0 License
# Created by: Patrick Lumbantobing, VertoX-AI
# Copyright (c) 2026 VertoX-AI. All rights reserved.
#
# This work is licensed under the Apache-2.0 License.
# To view a copy of this license, visit
# https://www.apache.org/licenses/LICENSE-2.0
from __future__ import annotations
import math
from pathlib import Path
import pytest
from speech_text_data_aligners.core.errors import AlignmentValidationError
from speech_text_data_aligners.core.models import (
AlignmentProvenance,
AlignmentRequest,
AlignmentResult,
LexicalWord,
WordAlignment,
)
from speech_text_data_aligners.core.normalization import lexicalize
from speech_text_data_aligners.core.validation import (
find_path_aliases,
validate_alignment_result,
)
def make_pair() -> tuple[AlignmentRequest, AlignmentResult]:
request = AlignmentRequest("r", Path("a.wav"), "One two", 2.0)
words = lexicalize(request.transcript)
result = AlignmentResult(
"r",
(WordAlignment(words[0], 0.0, 0.5), WordAlignment(words[1], 0.5, 1.0)),
AlignmentProvenance("fake"),
runtime_s=0.1,
)
return request, result
def test_validation_accepts_equality_and_single_word() -> None:
request, result = make_pair()
assert validate_alignment_result(request, result) is result
single_request = AlignmentRequest("s", Path("s.wav"), "Solo", 1.0)
word = lexicalize("Solo")[0]
single_result = AlignmentResult(
"s", (WordAlignment(word, 0.0, 1.0, 0.0),), AlignmentProvenance("fake")
)
assert validate_alignment_result(single_request, single_result) is single_result
def test_validation_rejects_identity_duration_runtime_and_count() -> None:
request, result = make_pair()
variants = (
(request, AlignmentResult("x", result.words, result.provenance)),
(AlignmentRequest("r", Path("a"), "One two", 0.0), result),
(AlignmentRequest("r", Path("a"), "One two", math.inf), result),
(request, AlignmentResult("r", result.words, result.provenance, runtime_s=-1)),
(
request,
AlignmentResult("r", result.words, result.provenance, runtime_s=math.nan),
),
(request, AlignmentResult("r", result.words[:1], result.provenance)),
)
for candidate_request, candidate_result in variants:
with pytest.raises(AlignmentValidationError):
validate_alignment_result(candidate_request, candidate_result)
def test_validation_rejects_word_mapping_and_all_timestamp_errors() -> None:
request, result = make_pair()
first, second = result.words
wrong_word = LexicalWord(9, "wrong", 0, 5)
variants = (
(WordAlignment(wrong_word, 0.0, 0.2), second),
(WordAlignment(first.lexical_word, math.nan, 0.2), second),
(WordAlignment(first.lexical_word, -0.1, 0.2), second),
(WordAlignment(first.lexical_word, 0.3, 0.2), second),
(first, WordAlignment(second.lexical_word, 0.5, 2.1)),
(
WordAlignment(first.lexical_word, 0.0, 0.7),
WordAlignment(second.lexical_word, 0.6, 1.0),
),
(WordAlignment(first.lexical_word, 0.0, 0.5, math.inf), second),
)
for words in variants:
candidate = AlignmentResult("r", words, result.provenance)
with pytest.raises(AlignmentValidationError):
validate_alignment_result(request, candidate)
def test_path_alias_detection_handles_prospective_normalized_and_existing_aliases(
tmp_path: Path,
) -> None:
source = tmp_path / "source.csv"
source.write_text("source", encoding="utf-8")
hard_link = tmp_path / "hard-link.csv"
try:
hard_link.hardlink_to(source)
except OSError as error:
pytest.skip(f"hard links are unavailable: {error}")
prospective = tmp_path / "future" / ".." / "source.csv"
distinct = tmp_path / "new.csv"
assert find_path_aliases(
{
"source": source,
"normalized": prospective,
"hard link": hard_link,
"distinct": distinct,
}
) == (
("source", "normalized"),
("source", "hard link"),
("normalized", "hard link"),
)
def test_path_alias_detection_falls_back_when_resolution_is_unavailable(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
original_resolve = Path.resolve
def fail_selected(path: Path, *, strict: bool = False) -> Path:
if path.name == "unresolvable":
raise OSError("resolution unavailable")
return original_resolve(path, strict=strict)
monkeypatch.setattr(Path, "resolve", fail_selected)
path = tmp_path / "unresolvable"
assert find_path_aliases({"left": path, "right": path}) == (("left", "right"),)