Skip to content

Commit 0b54e45

Browse files
committed
add --merge-json feature
1 parent 2101560 commit 0b54e45

5 files changed

Lines changed: 149 additions & 14 deletions

File tree

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
zip_safe=False,
2222
package_dir={'': 'src'},
2323
packages=find_packages(where='src'),
24-
install_requires=["requests>=2", "pytest>=7"],
24+
install_requires=["requests>=2", "pytest>=7", "filelock>=3"],
2525
extras_require={
2626
"dev": [
2727
"mock>=4",

src/buildkite_test_collector/collector/payload.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class TestHistory:
7777
start_at: Optional[Instant] = None
7878
end_at: Optional[Instant] = None
7979
duration: Optional[timedelta] = None
80-
children: Tuple['TestSpan'] = ()
80+
children: list['TestSpan'] = ()
8181

8282
def is_finished(self) -> bool:
8383
"""Is there an end_at time present?"""
@@ -91,7 +91,7 @@ def as_json(self, started_at: Instant) -> JsonDict:
9191
"""Convert this trace into a Dict for eventual serialisation into JSON"""
9292
attrs = {
9393
"section": "top",
94-
"children": tuple(map(lambda span: span.as_json(started_at), self.children))
94+
"children": list(map(lambda span: span.as_json(started_at), self.children))
9595
}
9696

9797
if self.start_at is not None:

src/buildkite_test_collector/pytest_plugin/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def pytest_unconfigure(config):
5959
# Note that when xdist is used, this JSON output file will NOT contain tags.
6060
jsonpath = config.option.jsonpath
6161
if jsonpath:
62-
plugin.save_payload_as_json(jsonpath)
62+
plugin.save_payload_as_json(jsonpath, merge=config.option.mergejson)
6363

6464
del config._buildkite
6565
config.pluginmanager.unregister(plugin)
@@ -75,3 +75,10 @@ def pytest_addoption(parser):
7575
metavar="path",
7676
help='save json file at given path'
7777
)
78+
group.addoption(
79+
'--merge-json',
80+
default=False,
81+
action='store_true',
82+
dest="mergejson",
83+
help='merge json output with existing file, if it exists'
84+
)

src/buildkite_test_collector/pytest_plugin/buildkite_plugin.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
"""Buildkite test collector plugin for Pytest"""
22
import json
3+
import os
34
from uuid import uuid4
45

6+
from filelock import Timeout, FileLock
7+
58
from ..collector.payload import TestData
69
from .logger import logger
710

@@ -108,7 +111,22 @@ def finalize_test(self, nodeid):
108111
return True
109112
return False
110113

111-
def save_payload_as_json(self, path):
112-
""" Save payload into a json file """
113-
with open(path, "w", encoding="utf-8") as f:
114-
json.dump(self.payload.as_json()["data"], f)
114+
def save_payload_as_json(self, path, merge=False):
115+
"""Save payload into a json file, merging with existing data if merge is True"""
116+
lock = FileLock(f"{path}.lock")
117+
with lock:
118+
data = list(self.payload.as_json()["data"])
119+
if merge and os.path.exists(path):
120+
try:
121+
with open(path, "r", encoding="utf-8") as f:
122+
existing_data = json.load(f)
123+
except json.JSONDecodeError:
124+
existing_data = []
125+
# Merge existing data with current payload
126+
merged_data = existing_data + data
127+
else:
128+
# If file does not exist or merge is False, use current payload
129+
merged_data = data
130+
131+
with open(path, "w", encoding="utf-8") as f:
132+
json.dump(merged_data, f)
Lines changed: 116 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
from buildkite_test_collector.pytest_plugin import BuildkitePlugin
1+
import json
2+
23
from buildkite_test_collector.collector.payload import Payload
3-
from pathlib import Path
4+
from buildkite_test_collector.pytest_plugin import BuildkitePlugin
45

5-
import json
66

77
def test_runtest_logstart_with_unstarted_payload(fake_env):
88
payload = Payload.init(fake_env)
@@ -15,14 +15,124 @@ def test_runtest_logstart_with_unstarted_payload(fake_env):
1515
assert plugin.payload.started_at is not None
1616

1717

18-
def test_save_json_payload(fake_env, tmp_path, successful_test):
18+
def test_save_json_payload_without_merge(fake_env, tmp_path, successful_test):
19+
payload = Payload.init(fake_env)
20+
payload = Payload.started(payload)
21+
payload = payload.push_test_data(successful_test)
22+
23+
plugin = BuildkitePlugin(payload)
24+
25+
path = tmp_path / "result.json"
26+
27+
# Create an existing file with some data
28+
existing_data = [{"existing": "data"}]
29+
path.write_text(json.dumps(existing_data))
30+
31+
# Save without merge option
32+
plugin.save_payload_as_json(path, merge=False)
33+
34+
# Check if the data was not merged
35+
expected_data = [successful_test.as_json(payload.started_at)]
36+
assert json.loads(path.read_text()) == expected_data
37+
38+
39+
def test_save_json_payload_with_merge(fake_env, tmp_path, successful_test):
1940
payload = Payload.init(fake_env)
2041
payload = Payload.started(payload)
2142
payload = payload.push_test_data(successful_test)
2243

2344
plugin = BuildkitePlugin(payload)
2445

2546
path = tmp_path / "result.json"
26-
plugin.save_payload_as_json(path)
2747

28-
assert path.read_text() == json.dumps([successful_test.as_json(payload.started_at)])
48+
# Create an existing file with some data
49+
existing_data = [{"existing": "data"}]
50+
path.write_text(json.dumps(existing_data))
51+
52+
# Save with merge option
53+
plugin.save_payload_as_json(path, merge=True)
54+
55+
# Check if the data was merged
56+
expected_data = existing_data + [successful_test.as_json(payload.started_at)]
57+
assert json.loads(path.read_text()) == expected_data
58+
59+
60+
def test_save_json_payload_with_non_existent_file(fake_env, tmp_path, successful_test):
61+
payload = Payload.init(fake_env)
62+
payload = Payload.started(payload)
63+
payload = payload.push_test_data(successful_test)
64+
65+
plugin = BuildkitePlugin(payload)
66+
67+
path = tmp_path / "non_existent.json"
68+
69+
# Ensure the file does not exist
70+
assert not path.exists()
71+
72+
# Save with merge option
73+
plugin.save_payload_as_json(path, merge=True)
74+
75+
# Check if the data was saved correctly
76+
expected_data = [successful_test.as_json(payload.started_at)]
77+
assert json.loads(path.read_text()) == expected_data
78+
79+
80+
def test_save_json_payload_with_empty_file(fake_env, tmp_path, successful_test):
81+
payload = Payload.init(fake_env)
82+
payload = Payload.started(payload)
83+
payload = payload.push_test_data(successful_test)
84+
85+
plugin = BuildkitePlugin(payload)
86+
87+
path = tmp_path / "empty.json"
88+
89+
# Create an empty file
90+
path.write_text("")
91+
92+
# Save with merge option
93+
plugin.save_payload_as_json(path, merge=True)
94+
95+
# Check if the data was saved correctly
96+
expected_data = [successful_test.as_json(payload.started_at)]
97+
assert json.loads(path.read_text()) == expected_data
98+
99+
100+
def test_save_json_payload_with_invalid_file(fake_env, tmp_path, successful_test):
101+
payload = Payload.init(fake_env)
102+
payload = Payload.started(payload)
103+
payload = payload.push_test_data(successful_test)
104+
105+
plugin = BuildkitePlugin(payload)
106+
107+
path = tmp_path / "invalid.json"
108+
109+
# Create a file with invalid JSON
110+
path.write_text("{invalid: json}")
111+
112+
# Save with merge option
113+
plugin.save_payload_as_json(path, merge=True)
114+
115+
# Check if the data was saved correctly
116+
expected_data = [successful_test.as_json(payload.started_at)]
117+
assert json.loads(path.read_text()) == expected_data
118+
119+
120+
def test_save_json_payload_with_large_data(fake_env, tmp_path, successful_test):
121+
payload = Payload.init(fake_env)
122+
payload = Payload.started(payload)
123+
payload = payload.push_test_data(successful_test)
124+
125+
plugin = BuildkitePlugin(payload)
126+
127+
path = tmp_path / "large_data.json"
128+
129+
# Create an existing file with a large amount of data
130+
existing_data = [{"test": f"data_{i}"} for i in range(1000)]
131+
path.write_text(json.dumps(existing_data))
132+
133+
# Save with merge option
134+
plugin.save_payload_as_json(path, merge=True)
135+
136+
# Check if the data was merged correctly
137+
expected_data = existing_data + [successful_test.as_json(payload.started_at)]
138+
assert json.loads(path.read_text()) == expected_data

0 commit comments

Comments
 (0)