From 01b60f8bca7b0b37e234093b863fa079ec269398 Mon Sep 17 00:00:00 2001 From: Zedong Date: Sun, 1 Nov 2026 23:18:18 +0800 Subject: [PATCH] fix(trace): keep JSON export metadata reusable --- openevolve/utils/trace_export_utils.py | 4 +- tests/test_trace_export_json.py | 56 ++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 tests/test_trace_export_json.py diff --git a/openevolve/utils/trace_export_utils.py b/openevolve/utils/trace_export_utils.py index 27d442cdcf..218ce4323d 100644 --- a/openevolve/utils/trace_export_utils.py +++ b/openevolve/utils/trace_export_utils.py @@ -54,7 +54,7 @@ def export_traces_json( Args: traces: List of trace objects with to_dict() method output_path: Path to save the JSON file - metadata: Optional metadata to include in the output + metadata: Optional metadata to include in the output, without modifying the caller's dict """ output_path = Path(output_path) output_path.parent.mkdir(parents=True, exist_ok=True) @@ -68,7 +68,7 @@ def export_traces_json( trace_dicts.append(trace) # Build output structure - output_data = {"metadata": metadata or {}, "traces": trace_dicts} + output_data = {"metadata": dict(metadata or {}), "traces": trace_dicts} # Add default metadata output_data["metadata"].setdefault("total_traces", len(trace_dicts)) diff --git a/tests/test_trace_export_json.py b/tests/test_trace_export_json.py new file mode 100644 index 0000000000..0423b2ba11 --- /dev/null +++ b/tests/test_trace_export_json.py @@ -0,0 +1,56 @@ +"""Regression tests for JSON trace export metadata ownership.""" + +import json +import tempfile +import unittest +from pathlib import Path +from unittest.mock import patch + +from openevolve.utils.trace_export_utils import export_traces_json + + +class TestJsonExportMetadata(unittest.TestCase): + def test_reusing_metadata_exports_fresh_statistics(self): + metadata = {"run": "example"} + with tempfile.TemporaryDirectory() as directory: + first_path = Path(directory) / "first.json" + second_path = Path(directory) / "second.json" + with patch("openevolve.utils.trace_export_utils.time") as clock: + clock.time.side_effect = [1000.0, 2000.0] + export_traces_json([{"id": "one"}], first_path, metadata) + export_traces_json([{"id": "one"}, {"id": "two"}], second_path, metadata) + + first = json.loads(first_path.read_text()) + second = json.loads(second_path.read_text()) + + self.assertEqual(first["metadata"]["total_traces"], len(first["traces"])) + self.assertEqual(first["metadata"]["exported_at"], 1000.0) + self.assertEqual(second["metadata"]["total_traces"], len(second["traces"])) + self.assertEqual(second["metadata"]["exported_at"], 2000.0) + self.assertEqual(metadata, {"run": "example"}) + + def test_explicit_statistics_remain_overridable(self): + metadata = {"run": "example", "total_traces": 42, "exported_at": 123.0} + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / "traces.json" + export_traces_json([{"id": "one"}], path, metadata) + exported = json.loads(path.read_text()) + + self.assertEqual(exported["metadata"], metadata) + self.assertEqual(exported["traces"], [{"id": "one"}]) + + def test_missing_and_empty_metadata_get_default_statistics(self): + for metadata in (None, {}): + with self.subTest(metadata=metadata), tempfile.TemporaryDirectory() as directory: + path = Path(directory) / "traces.json" + with patch("openevolve.utils.trace_export_utils.time") as clock: + clock.time.return_value = 1000.0 + export_traces_json([], path, metadata) + exported = json.loads(path.read_text()) + + self.assertEqual(exported["metadata"], {"total_traces": 0, "exported_at": 1000.0}) + self.assertEqual(exported["traces"], []) + + +if __name__ == "__main__": + unittest.main()