Skip to content

Commit 8b0e51e

Browse files
committed
resolve merge conflicts
2 parents 0c6f944 + c033fce commit 8b0e51e

File tree

2 files changed

+65
-62
lines changed

2 files changed

+65
-62
lines changed

sentience/cloud_tracing.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@ def __init__(
9999
# Use persistent cache directory instead of temp file
100100
# This ensures traces survive process crashes
101101
cache_dir = Path.home() / ".sentience" / "traces" / "pending"
102-
TraceFileManager.ensure_directory(cache_dir)
102+
# Create directory if it doesn't exist (ensure_directory is for file paths, not dirs)
103+
cache_dir.mkdir(parents=True, exist_ok=True)
103104

104105
# Persistent file (survives process crash)
105106
self._path = cache_dir / f"{run_id}.jsonl"
@@ -146,7 +147,16 @@ def close(
146147

147148
self._closed = True
148149

149-
# Close file first
150+
# Flush and sync file to disk before closing to ensure all data is written
151+
# This is critical on CI systems where file system operations may be slower
152+
self._trace_file.flush()
153+
try:
154+
# Force OS to write buffered data to disk
155+
os.fsync(self._trace_file.fileno())
156+
except (OSError, AttributeError):
157+
# Some file handles don't support fsync (e.g., StringIO in tests)
158+
# This is fine - flush() is usually sufficient
159+
pass
150160
self._trace_file.close()
151161

152162
# Ensure file exists and has content before proceeding

tests/test_cloud_tracing.py

Lines changed: 53 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,21 @@
2020
class TestCloudTraceSink:
2121
"""Test CloudTraceSink functionality."""
2222

23+
@pytest.fixture(autouse=True)
24+
def mock_home_dir(self):
25+
"""
26+
Automatically patch Path.home() to use a temporary directory for all tests.
27+
This isolates file operations and prevents FileNotFoundError on CI runners.
28+
"""
29+
with tempfile.TemporaryDirectory() as tmp_dir:
30+
mock_home = Path(tmp_dir)
31+
32+
# Patch Path.home in the cloud_tracing module
33+
with patch("sentience.cloud_tracing.Path.home", return_value=mock_home):
34+
# Also patch it in the current test module if used directly
35+
with patch("pathlib.Path.home", return_value=mock_home):
36+
yield mock_home
37+
2338
def test_cloud_trace_sink_upload_success(self):
2439
"""Test CloudTraceSink successfully uploads trace to cloud."""
2540
upload_url = "https://sentience.nyc3.digitaloceanspaces.com/user123/run456/trace.jsonl.gz"
@@ -138,10 +153,7 @@ def test_cloud_trace_sink_network_error_graceful_degradation(self, capsys):
138153
sink = CloudTraceSink(upload_url, run_id=run_id)
139154
sink.emit({"v": 1, "type": "test", "seq": 1})
140155

141-
# Ensure file is written before close
142-
sink._trace_file.flush()
143-
sink._trace_file.close()
144-
156+
# Close triggers upload (which will fail due to network error)
145157
# Should not raise, just print warning
146158
sink.close()
147159

@@ -151,10 +163,7 @@ def test_cloud_trace_sink_network_error_graceful_degradation(self, capsys):
151163
# Verify file was preserved
152164
cache_dir = Path.home() / ".sentience" / "traces" / "pending"
153165
trace_path = cache_dir / f"{run_id}.jsonl"
154-
# File should exist if emit was called (even if close fails)
155-
if trace_path.exists():
156-
# Cleanup
157-
os.remove(trace_path)
166+
assert trace_path.exists(), "Trace file should be preserved on network error"
158167

159168
def test_cloud_trace_sink_multiple_close_safe(self):
160169
"""Test CloudTraceSink.close() is idempotent."""
@@ -391,61 +400,45 @@ class TestTracerFactory:
391400

392401
def test_create_tracer_pro_tier_success(self, capsys):
393402
"""Test create_tracer returns CloudTraceSink for Pro tier."""
394-
with patch("sentience.tracer_factory.requests.post") as mock_post:
395-
with patch("sentience.cloud_tracing.requests.put") as mock_put:
396-
# Mock API response
397-
mock_response = Mock()
398-
mock_response.status_code = 200
399-
mock_response.json.return_value = {
400-
"upload_url": "https://sentience.nyc3.digitaloceanspaces.com/upload"
401-
}
402-
mock_post.return_value = mock_response
403-
404-
# Mock upload response
405-
mock_put.return_value = Mock(status_code=200)
406-
407-
run_id = f"test-run-{uuid.uuid4().hex[:8]}"
408-
tracer = create_tracer(
409-
api_key="sk_pro_test123",
410-
run_id=run_id,
411-
upload_trace=True,
412-
goal="Test goal for trace name",
413-
agent_type="SentienceAgent",
414-
llm_model="gpt-4-turbo",
415-
start_url="https://example.com",
416-
)
417-
418-
# Verify Pro tier message
419-
captured = capsys.readouterr()
420-
assert "☁️ [Sentience] Cloud tracing enabled (Pro tier)" in captured.out
403+
# Patch orphaned trace recovery to avoid extra API calls
404+
with patch("sentience.tracer_factory._recover_orphaned_traces"):
405+
with patch("sentience.tracer_factory.requests.post") as mock_post:
406+
with patch("sentience.cloud_tracing.requests.put") as mock_put:
407+
# Mock API response
408+
mock_response = Mock()
409+
mock_response.status_code = 200
410+
mock_response.json.return_value = {
411+
"upload_url": "https://sentience.nyc3.digitaloceanspaces.com/upload"
412+
}
413+
mock_post.return_value = mock_response
421414

422-
# Verify tracer works
423-
assert tracer.run_id == run_id
424-
# Check if sink is CloudTraceSink (it should be)
425-
assert isinstance(
426-
tracer.sink, CloudTraceSink
427-
), f"Expected CloudTraceSink, got {type(tracer.sink)}"
428-
assert tracer.sink.run_id == run_id # Verify run_id is passed
415+
# Mock upload response
416+
mock_put.return_value = Mock(status_code=200)
429417

430-
# Verify the init API was called
431-
assert mock_post.called
432-
assert mock_post.call_count == 1
418+
run_id = f"test-run-{uuid.uuid4().hex[:8]}"
419+
tracer = create_tracer(
420+
api_key="sk_pro_test123", run_id=run_id, upload_trace=True
421+
)
433422

434-
# Verify metadata was sent correctly
435-
call_args = mock_post.call_args
436-
request_payload = call_args[1]["json"]
437-
assert "run_id" in request_payload
438-
assert request_payload["run_id"] == run_id
439-
assert "metadata" in request_payload
440-
metadata = request_payload["metadata"]
441-
assert metadata["goal"] == "Test goal for trace name"
442-
assert metadata["agent_type"] == "SentienceAgent"
443-
assert metadata["llm_model"] == "gpt-4-turbo"
444-
assert metadata["start_url"] == "https://example.com"
445-
446-
# Cleanup - emit at least one event so file exists before close
447-
tracer.emit("test", {"v": 1, "seq": 1})
448-
tracer.close()
423+
# Verify Pro tier message
424+
captured = capsys.readouterr()
425+
assert "☁️ [Sentience] Cloud tracing enabled (Pro tier)" in captured.out
426+
427+
# Verify tracer works
428+
assert tracer.run_id == run_id
429+
# Check if sink is CloudTraceSink (it should be)
430+
assert isinstance(
431+
tracer.sink, CloudTraceSink
432+
), f"Expected CloudTraceSink, got {type(tracer.sink)}"
433+
assert tracer.sink.run_id == run_id # Verify run_id is passed
434+
435+
# Verify the init API was called (only once, since orphaned recovery is patched)
436+
assert mock_post.called
437+
assert mock_post.call_count == 1
438+
439+
# Cleanup - emit at least one event so file exists before close
440+
tracer.emit("test", {"v": 1, "seq": 1})
441+
tracer.close()
449442

450443
def test_create_tracer_free_tier_fallback(self, capsys):
451444
"""Test create_tracer falls back to local for free tier."""

0 commit comments

Comments
 (0)