Files
wehub-resource-sync 5a558eb09e
Backend Tests / discover-tests (push) Waiting to run
Backend Tests / ${{ matrix.name }} (push) Blocked by required conditions
Build and Publish SDK / build-and-publish (push) Waiting to run
Build Opik Docker Images / set-version (push) Waiting to run
Build Opik Docker Images / build-backend (push) Blocked by required conditions
Build Opik Docker Images / build-sandbox-executor-python (push) Blocked by required conditions
Build Opik Docker Images / build-python-backend (push) Blocked by required conditions
Build Opik Docker Images / build-frontend (push) Blocked by required conditions
Build Opik Docker Images / create-git-tag (push) Blocked by required conditions
ClickHouse Migration Cluster Check / validate-clickhouse-migrations (push) Waiting to run
Docs - Publish / run (push) Waiting to run
E2E Tests - Post Merge (v2) / 🧪 E2E v2 Tests (${{ github.event.inputs.tier || 't1' }}) (push) Waiting to run
E2E Tests - Post Merge (v2) / 📢 Slack Notification (push) Blocked by required conditions
Frontend Unit Tests / Test on Node 20 (push) Waiting to run
Guardrails E2E Tests / Select Python version matrix (push) Waiting to run
Guardrails E2E Tests / Guardrails E2E Tests ${{matrix.python_version}} (push) Blocked by required conditions
Guardrails E2E Tests / 📢 Slack Notification (push) Blocked by required conditions
Guardrails Backend Unit Tests / Guardrails Backend Unit Tests (push) Waiting to run
Guardrails Backend Unit Tests / 📢 Slack Notification (push) Blocked by required conditions
Lint Opik Helm Chart / lint-helm-chart (Helm v3.21.0) (push) Waiting to run
Lint Opik Helm Chart / lint-helm-chart (Helm v4.2.0) (push) Waiting to run
Lint Opik Helm Chart / unittest-helm-chart (push) Waiting to run
Lint Opik Helm Chart / render-equality (push) Waiting to run
Opik Optimizer - Unit Tests / Opik Optimizer Unit Tests Python ${{matrix.python_version}} (push) Waiting to run
Python BE E2E Tests / Python BE E2E (push) Waiting to run
Python Backend Tests / run-python-backend-tests (push) Waiting to run
Python SDK Unit Tests / Python SDK Unit Tests ${{matrix.python_version}} (push) Waiting to run
Release Drafter / update_release_draft (push) Waiting to run
SDK E2E Libraries Integration Tests / Check Secrets (push) Waiting to run
SDK E2E Libraries Integration Tests / Missed OpenAI API Key Warning (push) Blocked by required conditions
SDK E2E Libraries Integration Tests / build-opik (push) Blocked by required conditions
SDK E2E Libraries Integration Tests / E2E Lib Integration Python ${{matrix.python_version}} (push) Blocked by required conditions
TypeScript SDK Integration Build & Publish / build-and-publish (opik-gemini) (push) Waiting to run
TypeScript SDK Integration Build & Publish / build-and-publish (opik-langchain) (push) Waiting to run
TypeScript SDK Integration Build & Publish / build-and-publish (opik-openai) (push) Waiting to run
TypeScript SDK Integration Build & Publish / build-and-publish (opik-otel) (push) Waiting to run
TypeScript SDK Integration Build & Publish / build-and-publish (opik-vercel) (push) Waiting to run
TypeScript SDK Build & Publish / build-and-publish (push) Waiting to run
TypeScript SDK Unit Tests / Test on Node ${{ matrix.node-version }} (push) Waiting to run
TypeScript SDK Compatibility V1.x E2E Tests / Select Node version matrix (push) Has been cancelled
TypeScript SDK Compatibility V1.x E2E Tests / TypeScript SDK Compatibility V1.x E2E Tests Node ${{matrix.node_version}} (push) Has been cancelled
TypeScript SDK E2E Tests / TypeScript SDK E2E Tests Node ${{matrix.node_version}} (push) Has been cancelled
Opik Optimizer - E2E Tests / build-opik (push) Has been cancelled
TypeScript SDK Compatibility V1.x E2E Tests / build-opik (push) Has been cancelled
Python SDK E2E Tests / Select Python version matrix (push) Has been cancelled
Python SDK E2E Tests / Python SDK E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Python SDK E2E Tests / build-opik (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / Select Python version matrix (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / Python SDK Compatibility V1.x E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / build-opik (push) Has been cancelled
TypeScript SDK E2E Tests / Select Node version matrix (push) Has been cancelled
TypeScript SDK E2E Tests / build-opik (push) Has been cancelled
Opik Optimizer - E2E Tests / Opik Optimizer E2E Tests Python ${{matrix.python_version}} (push) Has been cancelled
Opik Optimizer - E2E Tests / Opik Optimizer Integration Smoke Tests (push) Has been cancelled
🐙 Code Quality / detect (push) Has been cancelled
🐙 Code Quality / lint (${{ matrix.leg.name }}) (push) Has been cancelled
🐙 Code Quality / summary (push) Has been cancelled
TypeScript SDK Library Integration Tests / Check Secrets (push) Has been cancelled
TypeScript SDK Library Integration Tests / opik-vercel (Vercel AI SDK / eve) (push) Has been cancelled
SDK Library Integration Tests Runner / Check Secrets (push) Has been cancelled
SDK Library Integration Tests Runner / Missed OpenAI API Key Warning (push) Has been cancelled
SDK Library Integration Tests Runner / Build (push) Has been cancelled
SDK Library Integration Tests Runner / openai_tests (push) Has been cancelled
SDK Library Integration Tests Runner / langchain_tests (push) Has been cancelled
SDK Library Integration Tests Runner / langchain_legacy_tests (push) Has been cancelled
SDK Library Integration Tests Runner / llama_index_tests (push) Has been cancelled
SDK Library Integration Tests Runner / anthropic_tests (push) Has been cancelled
SDK Library Integration Tests Runner / mistral_tests (push) Has been cancelled
SDK Library Integration Tests Runner / groq_tests (push) Has been cancelled
SDK Library Integration Tests Runner / aisuite_tests (push) Has been cancelled
SDK Library Integration Tests Runner / haystack_tests (push) Has been cancelled
SDK Library Integration Tests Runner / dspy_tests (push) Has been cancelled
SDK Library Integration Tests Runner / crewai_v0_tests (push) Has been cancelled
SDK Library Integration Tests Runner / crewai_v1_tests (push) Has been cancelled
SDK Library Integration Tests Runner / genai_tests (push) Has been cancelled
SDK Library Integration Tests Runner / adk_tests (push) Has been cancelled
SDK Library Integration Tests Runner / adk_legacy_1_3_0_tests (push) Has been cancelled
SDK Library Integration Tests Runner / evaluation_metrics_tests (push) Has been cancelled
SDK Library Integration Tests Runner / bedrock_tests (push) Has been cancelled
SDK Library Integration Tests Runner / litellm_tests (push) Has been cancelled
SDK Library Integration Tests Runner / harbor_tests (push) Has been cancelled
SDK Library Integration Tests Runner / Slack Notification (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:25:44 +08:00

359 lines
13 KiB
Python

"""Tests for the migration manifest used by opik import for resumable migrations."""
import sqlite3
from pathlib import Path
import pytest
from opik.cli.migration_manifest import MigrationManifest, MANIFEST_FILENAME
@pytest.fixture
def tmp_base(tmp_path: Path) -> Path:
return tmp_path
class TestMigrationManifestLifecycle:
def test_manifest__fresh_instance__status_is_not_started(
self, tmp_base: Path
) -> None:
manifest = MigrationManifest(tmp_base)
assert manifest.status == "not_started"
assert not manifest.is_in_progress
assert not manifest.is_completed
def test_start__fresh_manifest__status_becomes_in_progress_and_file_written(
self, tmp_base: Path
) -> None:
manifest = MigrationManifest(tmp_base)
manifest.start()
assert manifest.is_in_progress
assert (tmp_base / MANIFEST_FILENAME).exists()
def test_complete__after_start__status_becomes_completed(
self, tmp_base: Path
) -> None:
manifest = MigrationManifest(tmp_base)
manifest.start()
manifest.complete()
assert manifest.is_completed
assert not manifest.is_in_progress
def test_reset__after_start_with_data__all_state_cleared(
self, tmp_base: Path
) -> None:
manifest = MigrationManifest(tmp_base)
manifest.start()
trace_file = tmp_base / "projects" / "p1" / "trace_abc.json"
trace_file.parent.mkdir(parents=True)
trace_file.touch()
manifest.mark_file_completed(trace_file)
manifest.add_trace_mapping("src-1", "dest-1")
manifest.reset()
assert manifest.status == "not_started"
assert manifest.completed_count() == 0
assert manifest.get_trace_id_map() == {}
def test_exists__before_any_save__returns_false(self, tmp_base: Path) -> None:
assert not MigrationManifest.exists(tmp_base)
def test_exists__after_start__returns_true(self, tmp_base: Path) -> None:
MigrationManifest(tmp_base).start()
assert MigrationManifest.exists(tmp_base)
def test_start__called_twice__started_at_preserved(self, tmp_base: Path) -> None:
manifest = MigrationManifest(tmp_base)
manifest.start()
conn = sqlite3.connect(str(tmp_base / MANIFEST_FILENAME))
first_started_at = conn.execute(
"SELECT value FROM status WHERE key = 'started_at'"
).fetchone()[0]
conn.close()
manifest.start() # second call must not overwrite started_at
conn = sqlite3.connect(str(tmp_base / MANIFEST_FILENAME))
second_started_at = conn.execute(
"SELECT value FROM status WHERE key = 'started_at'"
).fetchone()[0]
conn.close()
assert second_started_at == first_started_at
class TestFileTracking:
def _make_file(self, base: Path, relative: str) -> Path:
p = base / relative
p.parent.mkdir(parents=True, exist_ok=True)
p.touch()
return p
def test_is_file_completed__fresh_manifest__returns_false(
self, tmp_base: Path
) -> None:
manifest = MigrationManifest(tmp_base)
f = self._make_file(tmp_base, "datasets/dataset_foo.json")
assert not manifest.is_file_completed(f)
def test_mark_file_completed__new_file__recorded_and_count_incremented(
self, tmp_base: Path
) -> None:
manifest = MigrationManifest(tmp_base)
f = self._make_file(tmp_base, "datasets/dataset_foo.json")
manifest.mark_file_completed(f)
assert manifest.is_file_completed(f)
assert manifest.completed_count() == 1
def test_mark_file_completed__same_file_twice__count_remains_one(
self, tmp_base: Path
) -> None:
manifest = MigrationManifest(tmp_base)
f = self._make_file(tmp_base, "datasets/dataset_foo.json")
manifest.mark_file_completed(f)
manifest.mark_file_completed(f)
assert manifest.completed_count() == 1
def test_mark_file_failed__new_file__recorded_with_error(
self, tmp_base: Path
) -> None:
manifest = MigrationManifest(tmp_base)
f = self._make_file(tmp_base, "projects/p1/trace_abc.json")
manifest.mark_file_failed(f, "timeout")
assert manifest.failed_count() == 1
assert manifest.get_failed_files()[manifest.relative_path(f)] == "timeout"
def test_mark_file_completed__previously_failed_file__removed_from_failed(
self, tmp_base: Path
) -> None:
manifest = MigrationManifest(tmp_base)
f = self._make_file(tmp_base, "projects/p1/trace_abc.json")
manifest.mark_file_failed(f, "timeout")
manifest.mark_file_completed(f)
assert manifest.failed_count() == 0
assert manifest.is_file_completed(f)
def test_relative_path__nested_file__returns_path_relative_to_base(
self, tmp_base: Path
) -> None:
manifest = MigrationManifest(tmp_base)
f = self._make_file(tmp_base, "projects/my-project/trace_xyz.json")
assert manifest.relative_path(f) == str(
Path("projects") / "my-project" / "trace_xyz.json"
)
class TestTraceIdMapping:
def test_add_trace_mapping__single_entry__retrievable_via_get_map(
self, tmp_base: Path
) -> None:
manifest = MigrationManifest(tmp_base)
manifest.add_trace_mapping("src-111", "dest-222")
manifest.save()
assert manifest.get_trace_id_map() == {"src-111": "dest-222"}
def test_trace_id_map__save_then_new_instance__mapping_survives(
self, tmp_base: Path
) -> None:
m1 = MigrationManifest(tmp_base)
m1.start()
m1.add_trace_mapping("src-aaa", "dest-bbb")
m1.save()
m2 = MigrationManifest(tmp_base)
assert m2.get_trace_id_map() == {"src-aaa": "dest-bbb"}
def test_get_trace_id_map__mutate_returned_dict__original_unaffected(
self, tmp_base: Path
) -> None:
manifest = MigrationManifest(tmp_base)
manifest.add_trace_mapping("src-1", "dest-1")
manifest.save()
copy = manifest.get_trace_id_map()
copy["src-2"] = "dest-2" # mutate the copy
assert "src-2" not in manifest.get_trace_id_map()
def test_add_trace_mapping__same_src_id_twice__last_dest_wins(
self, tmp_base: Path
) -> None:
manifest = MigrationManifest(tmp_base)
manifest.add_trace_mapping("src-1", "dest-old")
manifest.add_trace_mapping("src-1", "dest-new")
manifest.save()
assert manifest.get_trace_id_map()["src-1"] == "dest-new"
class TestDatabaseIntegrity:
def test_manifest_file__after_start_and_save__is_valid_sqlite_with_expected_tables(
self, tmp_base: Path
) -> None:
manifest = MigrationManifest(tmp_base)
manifest.start()
manifest.add_trace_mapping("a", "b")
manifest.save()
conn = sqlite3.connect(str(tmp_base / MANIFEST_FILENAME))
tables = {
row[0]
for row in conn.execute(
"SELECT name FROM sqlite_master WHERE type='table'"
).fetchall()
}
conn.close()
assert {"status", "completed_files", "failed_files", "trace_id_map"} <= tables
def test_save__after_start__no_tmp_file_created(self, tmp_base: Path) -> None:
manifest = MigrationManifest(tmp_base)
manifest.start()
assert not (tmp_base / "migration_manifest.tmp").exists()
def test_mark_file_completed__duplicate_write__count_remains_one(
self, tmp_base: Path
) -> None:
"""INSERT OR IGNORE means re-flushing the same path never duplicates rows."""
manifest = MigrationManifest(tmp_base, batch_size=1)
f = tmp_base / "projects" / "p" / "trace_x.json"
f.parent.mkdir(parents=True)
f.touch()
manifest.mark_file_completed(f)
manifest.mark_file_completed(f)
assert manifest.completed_count() == 1
class TestBatchingBehavior:
"""Document and verify the crash trade-off introduced by batched writes."""
def _make_trace(self, base: Path, name: str) -> Path:
p = base / "projects" / "proj" / name
p.parent.mkdir(parents=True, exist_ok=True)
p.touch()
return p
def test_mark_file_completed__within_batch__visible_via_api_before_flush(
self, tmp_base: Path
) -> None:
"""Buffered completions are visible through the public API (which flushes
before querying) even before the batch threshold is reached."""
manifest = MigrationManifest(tmp_base, batch_size=50)
f = self._make_trace(tmp_base, "trace_001.json")
manifest.mark_file_completed(f)
# completed_count() flushes first, so the buffered write IS visible.
assert manifest.completed_count() == 1
assert manifest.is_file_completed(f)
def test_mark_file_completed__crash_before_flush__unflushed_data_lost(
self, tmp_base: Path
) -> None:
"""Simulates a crash (new instance, no save()) with batch_size > pending count.
This is the documented trade-off: up to batch_size-1 completions may be
absent from the manifest after a crash. Those files will simply be
re-imported on resume.
"""
f = self._make_trace(tmp_base, "trace_001.json")
m1 = MigrationManifest(tmp_base, batch_size=50)
m1.start() # always flushes immediately
m1.mark_file_completed(f) # buffered — NOT yet on disk
# Simulate crash: m1 is abandoned without save() or complete().
# __del__ is NOT called here because m1 is still in scope when m2 is created.
m2 = MigrationManifest(tmp_base, batch_size=50)
# The completion is not on disk — resume will re-process this file.
assert not m2.is_file_completed(f)
assert m2.completed_count() == 0
def test_mark_file_completed__batch_size_one__each_write_immediately_durable(
self, tmp_base: Path
) -> None:
"""With batch_size=1 every completion is immediately committed to disk."""
f = self._make_trace(tmp_base, "trace_001.json")
m1 = MigrationManifest(tmp_base, batch_size=1)
m1.start()
m1.mark_file_completed(f) # auto-flushes (batch_size=1)
# New instance reads from disk — must see the completion.
m2 = MigrationManifest(tmp_base, batch_size=1)
assert m2.is_file_completed(f)
assert m2.completed_count() == 1
def test_mark_file_completed__batch_threshold_reached__auto_flushed_to_disk(
self, tmp_base: Path
) -> None:
"""When pending count hits batch_size the buffer is auto-flushed."""
batch_size = 3
files = [self._make_trace(tmp_base, f"trace_{i:03d}.json") for i in range(3)]
m1 = MigrationManifest(tmp_base, batch_size=batch_size)
m1.start()
for f in files:
m1.mark_file_completed(f) # third call triggers auto-flush
# New instance must see all three completions.
m2 = MigrationManifest(tmp_base, batch_size=batch_size)
assert m2.completed_count() == 3
for f in files:
assert m2.is_file_completed(f)
class TestResumeScenario:
"""Simulate a real interrupted + resumed migration."""
def test_resume__interrupted_import__completed_files_skipped_and_id_map_available(
self, tmp_base: Path
) -> None:
"""After an interrupted run the second run skips already-flushed files.
batch_size=1 so every mark_file_completed is immediately durable —
this models the boundary between two process invocations where only
flushed data survives.
"""
trace_files = []
for i in range(3):
p = tmp_base / "projects" / "proj" / f"trace_{i:03d}.json"
p.parent.mkdir(parents=True, exist_ok=True)
p.touch()
trace_files.append(p)
m1 = MigrationManifest(tmp_base, batch_size=1)
m1.start()
m1.add_trace_mapping("src-0", "dest-0")
m1.mark_file_completed(trace_files[0])
m1.add_trace_mapping("src-1", "dest-1")
m1.mark_file_completed(trace_files[1])
# Process crashes before trace_files[2] — no complete() call.
m2 = MigrationManifest(tmp_base)
assert m2.is_in_progress
assert m2.completed_count() == 2
assert m2.is_file_completed(trace_files[0])
assert m2.is_file_completed(trace_files[1])
assert not m2.is_file_completed(trace_files[2])
id_map = m2.get_trace_id_map()
assert id_map["src-0"] == "dest-0"
assert id_map["src-1"] == "dest-1"
assert "src-2" not in id_map
def test_reset__after_completed_files__all_state_cleared(
self, tmp_base: Path
) -> None:
trace_file = tmp_base / "projects" / "p" / "trace_abc.json"
trace_file.parent.mkdir(parents=True)
trace_file.touch()
m1 = MigrationManifest(tmp_base)
m1.start()
m1.mark_file_completed(trace_file)
m2 = MigrationManifest(tmp_base)
m2.reset()
assert m2.completed_count() == 0
assert not m2.is_file_completed(trace_file)
assert m2.status == "not_started"