Skip to content

Commit 040af31

Browse files
committed
Version the report payload with bmf_version
A report payload can now say what format it is written in. `bmf_version` is a top level key of the payload, next to `results`, and it accepts exactly 0 and 1. An absent key is version 0, so every payload that ingests today ingests unchanged. Any other value is refused with a message that names the accepted versions. At version 1 the `json` adapter node tries its `json_v1` leaf first and falls back to `json_v0`; at version 0 it tries them in today's order. That reorders the attempts rather than filtering the payload, so version 1 refuses no v0 shape and version 0 still ingests a v1 shape through the second attempt. `magic` inherits the preference through the node, an explicitly named leaf is unaffected because it is already an exact statement, and no non-JSON adapter reads the key. An empty payload is now foldable at any version. It is the one payload both leaves claim, so at version 1 it parses as v1, and since fold is all or nothing across the array it would otherwise disable fold for every v0 iteration beside it, changing the number of metric rows a report writes. The fold refusal exists because a pooled statistic cannot be recomputed from per iteration values, and a payload that reported nothing has none, so it has nothing to refuse over. `BmfVersion` is a validated newtype in `bencher_valid` that replaces the enum of the same name in `bencher_adapter`. The payload's declared version and the version a leaf parsed are two values of one type, and they have to be able to differ: a payload that declares version 1 may still hold v0 results. `/v0/run` carries the key because `JsonNewRun` gains the field and the conversion into the report payload forwards it. A job based run does not: its results are the runner's own output rather than the submitted payload. The v1 thresholds shape and the project gate are later layers, and the CLI gains no flag here.
1 parent 9faab9d commit 040af31

38 files changed

Lines changed: 1227 additions & 126 deletions

File tree

lib/api_projects/tests/bmf_version.rs

Lines changed: 531 additions & 0 deletions
Large diffs are not rendered by default.

lib/api_run/tests/run.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,103 @@ async fn run_post_unauthenticated() {
131131

132132
// --- Job creation integration tests (Plus only) ---
133133

134+
/// POST /v0/run - the run payload carries `bmf_version` through to the adapter.
135+
///
136+
/// `/v0/run` takes its own payload and converts it into a report payload, so the
137+
/// key reaches ingest only because `JsonNewRun` carries it and the conversion
138+
/// forwards it. A BMF v1 payload declared as version 1 ingests through the default
139+
/// Magic adapter, which reaches the JSON leaves through the `json` node.
140+
#[tokio::test]
141+
async fn run_post_bmf_version_1() {
142+
let server = TestServer::new().await;
143+
let user = server.signup("Test User", "runbmf@example.com").await;
144+
let org = server.create_org(&user, "Run Bmf Org").await;
145+
let project = server.create_project(&user, &org, "Run Bmf Project").await;
146+
147+
let project_slug: &str = project.slug.as_ref();
148+
let body = serde_json::json!({
149+
"project": project_slug,
150+
"branch": "main",
151+
"testbed": "localhost",
152+
"start_time": "2024-01-01T00:00:00Z",
153+
"end_time": "2024-01-01T00:01:00Z",
154+
"results": [bmf_v1_results().to_string()],
155+
"bmf_version": 1,
156+
});
157+
158+
let resp = server
159+
.client
160+
.post(server.api_url("/v0/run"))
161+
.header(
162+
bencher_json::AUTHORIZATION,
163+
bencher_json::bearer_header(&user.token),
164+
)
165+
.json(&body)
166+
.send()
167+
.await
168+
.expect("Request failed");
169+
170+
assert_eq!(resp.status(), StatusCode::CREATED);
171+
let report: JsonReport = resp.json().await.expect("Failed to parse response");
172+
// The grid point is the proof the v1 shape was read: a v0 payload has no
173+
// parameters to report.
174+
let results = report.results.expect("Report results");
175+
let iteration = results.first().expect("Report iteration");
176+
let result = iteration.first().expect("Report result");
177+
assert!(!result.parameter.set.is_empty());
178+
}
179+
180+
/// POST /v0/run - an unknown `bmf_version` is rejected before anything is created.
181+
#[tokio::test]
182+
async fn run_post_unknown_bmf_version() {
183+
let server = TestServer::new().await;
184+
let user = server.signup("Test User", "runbmfbad@example.com").await;
185+
let org = server.create_org(&user, "Run Bad Bmf Org").await;
186+
let project = server
187+
.create_project(&user, &org, "Run Bad Bmf Project")
188+
.await;
189+
190+
let project_slug: &str = project.slug.as_ref();
191+
let body = serde_json::json!({
192+
"project": project_slug,
193+
"branch": "main",
194+
"testbed": "localhost",
195+
"start_time": "2024-01-01T00:00:00Z",
196+
"end_time": "2024-01-01T00:01:00Z",
197+
"results": [bmf_results().to_string()],
198+
"bmf_version": 2,
199+
});
200+
201+
let resp = server
202+
.client
203+
.post(server.api_url("/v0/run"))
204+
.header(
205+
bencher_json::AUTHORIZATION,
206+
bencher_json::bearer_header(&user.token),
207+
)
208+
.json(&body)
209+
.send()
210+
.await
211+
.expect("Request failed");
212+
213+
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
214+
let body = resp.text().await.expect("Failed to read the response");
215+
assert!(
216+
body.contains("0 or 1"),
217+
"expected the rejection to name the accepted versions: {body}"
218+
);
219+
}
220+
221+
/// A BMF v1 payload: a benchmark maps to an array of grid points.
222+
fn bmf_v1_results() -> serde_json::Value {
223+
serde_json::json!({
224+
"benchmark_name": [{
225+
"parameters": { "size_mb": 16 },
226+
"measures": { "latency": { "value": 100.0 } },
227+
}]
228+
})
229+
}
230+
134231
fn bmf_results() -> serde_json::Value {
135232
serde_json::json!({
136233
"benchmark_name": {

lib/bencher_adapter/src/adapters/c_sharp/dot_net.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ impl DotNet {
172172

173173
#[cfg(test)]
174174
pub(crate) mod test_c_sharp_dot_net {
175+
use bencher_json::BmfVersion;
175176
use bencher_json::project::report::JsonAverage;
176177
use ordered_float::OrderedFloat;
177178
use pretty_assertions::assert_eq;
@@ -209,9 +210,7 @@ pub(crate) mod test_c_sharp_dot_net {
209210

210211
let results = opt_convert_file_path::<AdapterCSharpDotNet>(
211212
&file_path,
212-
Settings {
213-
average: Some(JsonAverage::Mean),
214-
},
213+
Settings::new(Some(JsonAverage::Mean), BmfVersion::default()),
215214
)
216215
.unwrap();
217216

@@ -233,9 +232,7 @@ pub(crate) mod test_c_sharp_dot_net {
233232

234233
let results = opt_convert_file_path::<AdapterCSharpDotNet>(
235234
&file_path,
236-
Settings {
237-
average: Some(JsonAverage::Mean),
238-
},
235+
Settings::new(Some(JsonAverage::Mean), BmfVersion::default()),
239236
)
240237
.unwrap();
241238
validate_adapter_c_sharp_dot_net(&results);

lib/bencher_adapter/src/adapters/cpp/catch2.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ fn parse_catch2_duration(input: &str) -> IResult<&str, OrderedFloat<f64>> {
196196

197197
#[cfg(test)]
198198
pub(crate) mod test_cpp_catch2 {
199+
use bencher_json::BmfVersion;
199200
use bencher_json::project::report::JsonAverage;
200201
use pretty_assertions::assert_eq;
201202

@@ -247,9 +248,7 @@ pub(crate) mod test_cpp_catch2 {
247248
let file_path = "./tool_output/cpp/catch2/four.txt";
248249
let results = opt_convert_file_path::<AdapterCppCatch2>(
249250
file_path,
250-
Settings {
251-
average: Some(JsonAverage::Mean),
252-
},
251+
Settings::new(Some(JsonAverage::Mean), BmfVersion::default()),
253252
)
254253
.unwrap();
255254
validate_adapter_cpp_catch2(&results);
@@ -258,9 +257,7 @@ pub(crate) mod test_cpp_catch2 {
258257
None,
259258
opt_convert_file_path::<AdapterCppCatch2>(
260259
file_path,
261-
Settings {
262-
average: Some(JsonAverage::Median)
263-
}
260+
Settings::new(Some(JsonAverage::Median), BmfVersion::default())
264261
)
265262
);
266263
}

lib/bencher_adapter/src/adapters/cpp/google.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ impl TryFrom<Google> for Option<AdapterResults> {
7070

7171
#[cfg(test)]
7272
pub(crate) mod test_cpp_google {
73+
use bencher_json::BmfVersion;
7374
use bencher_json::project::report::JsonAverage;
7475
use pretty_assertions::assert_eq;
7576

@@ -90,9 +91,7 @@ pub(crate) mod test_cpp_google {
9091
let file_path = "./tool_output/cpp/google/two.txt";
9192
let results = opt_convert_file_path::<AdapterCppGoogle>(
9293
file_path,
93-
Settings {
94-
average: Some(JsonAverage::Mean),
95-
},
94+
Settings::new(Some(JsonAverage::Mean), BmfVersion::default()),
9695
)
9796
.unwrap();
9897
validate_adapter_cpp_google(&results);
@@ -101,9 +100,7 @@ pub(crate) mod test_cpp_google {
101100
None,
102101
opt_convert_file_path::<AdapterCppGoogle>(
103102
file_path,
104-
Settings {
105-
average: Some(JsonAverage::Median)
106-
}
103+
Settings::new(Some(JsonAverage::Median), BmfVersion::default())
107104
)
108105
);
109106
}

lib/bencher_adapter/src/adapters/dart/benchmark_harness.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ fn parse_dart_line(input: &str) -> IResult<&str, (BenchmarkName, JsonNewMetric)>
7070

7171
#[cfg(test)]
7272
pub(crate) mod test_dart_benchmark_harness {
73+
use bencher_json::BmfVersion;
7374
use bencher_json::project::report::JsonAverage;
7475
use pretty_assertions::assert_eq;
7576

@@ -92,9 +93,7 @@ pub(crate) mod test_dart_benchmark_harness {
9293
None,
9394
opt_convert_file_path::<AdapterDartBenchmarkHarness>(
9495
file_path,
95-
Settings {
96-
average: Some(JsonAverage::Median)
97-
}
96+
Settings::new(Some(JsonAverage::Median), BmfVersion::default())
9897
)
9998
);
10099
}

lib/bencher_adapter/src/adapters/go/bench.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ fn parse_go_bench(input: &str) -> IResult<&str, JsonNewMetric> {
7777

7878
#[cfg(test)]
7979
pub(crate) mod test_go_bench {
80+
use bencher_json::BmfVersion;
8081
use bencher_json::{JsonNewMetric, project::report::JsonAverage};
8182
use pretty_assertions::assert_eq;
8283

@@ -192,9 +193,7 @@ pub(crate) mod test_go_bench {
192193
let file_path = "./tool_output/go/bench/five.txt";
193194
let results = opt_convert_file_path::<AdapterGoBench>(
194195
file_path,
195-
Settings {
196-
average: Some(JsonAverage::Mean),
197-
},
196+
Settings::new(Some(JsonAverage::Mean), BmfVersion::default()),
198197
)
199198
.unwrap();
200199
validate_adapter_go_bench(&results);
@@ -203,9 +202,7 @@ pub(crate) mod test_go_bench {
203202
None,
204203
opt_convert_file_path::<AdapterGoBench>(
205204
file_path,
206-
Settings {
207-
average: Some(JsonAverage::Median)
208-
}
205+
Settings::new(Some(JsonAverage::Median), BmfVersion::default())
209206
)
210207
);
211208
}

lib/bencher_adapter/src/adapters/java/jmh.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ impl TryFrom<Jmh> for Option<AdapterResults> {
109109

110110
#[cfg(test)]
111111
pub(crate) mod test_java_jmh {
112+
use bencher_json::BmfVersion;
112113
use bencher_json::project::report::JsonAverage;
113114
use pretty_assertions::assert_eq;
114115

@@ -163,9 +164,7 @@ pub(crate) mod test_java_jmh {
163164
let file_path = "./tool_output/java/jmh/six.json";
164165
let results = opt_convert_file_path::<AdapterJavaJmh>(
165166
file_path,
166-
Settings {
167-
average: Some(JsonAverage::Mean),
168-
},
167+
Settings::new(Some(JsonAverage::Mean), BmfVersion::default()),
169168
)
170169
.unwrap();
171170
validate_adapter_java_jmh(&results);
@@ -174,9 +173,7 @@ pub(crate) mod test_java_jmh {
174173
None,
175174
opt_convert_file_path::<AdapterJavaJmh>(
176175
file_path,
177-
Settings {
178-
average: Some(JsonAverage::Median)
179-
}
176+
Settings::new(Some(JsonAverage::Median), BmfVersion::default())
180177
)
181178
);
182179
}

lib/bencher_adapter/src/adapters/js/benchmark.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ fn parse_benchmark_time(input: &str) -> IResult<&str, JsonNewMetric> {
8888

8989
#[cfg(test)]
9090
pub(crate) mod test_js_benchmark {
91+
use bencher_json::BmfVersion;
9192
use bencher_json::project::report::JsonAverage;
9293
use pretty_assertions::assert_eq;
9394

@@ -110,17 +111,13 @@ pub(crate) mod test_js_benchmark {
110111
None,
111112
opt_convert_file_path::<AdapterJsBenchmark>(
112113
file_path,
113-
Settings {
114-
average: Some(JsonAverage::Mean)
115-
}
114+
Settings::new(Some(JsonAverage::Mean), BmfVersion::default())
116115
)
117116
);
118117

119118
let results = opt_convert_file_path::<AdapterJsBenchmark>(
120119
file_path,
121-
Settings {
122-
average: Some(JsonAverage::Median),
123-
},
120+
Settings::new(Some(JsonAverage::Median), BmfVersion::default()),
124121
)
125122
.unwrap();
126123
validate_adapter_js_benchmark(&results);

lib/bencher_adapter/src/adapters/js/time.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ fn parse_time_time(input: &str) -> IResult<&str, JsonNewMetric> {
8282

8383
#[cfg(test)]
8484
pub(crate) mod test_js_time {
85+
use bencher_json::BmfVersion;
8586
use bencher_json::project::report::JsonAverage;
8687
use pretty_assertions::assert_eq;
8788

@@ -104,19 +105,15 @@ pub(crate) mod test_js_time {
104105
None,
105106
opt_convert_file_path::<AdapterJsTime>(
106107
file_path,
107-
Settings {
108-
average: Some(JsonAverage::Mean)
109-
}
108+
Settings::new(Some(JsonAverage::Mean), BmfVersion::default())
110109
)
111110
);
112111

113112
assert_eq!(
114113
None,
115114
opt_convert_file_path::<AdapterJsTime>(
116115
file_path,
117-
Settings {
118-
average: Some(JsonAverage::Median)
119-
}
116+
Settings::new(Some(JsonAverage::Median), BmfVersion::default())
120117
)
121118
);
122119
}

0 commit comments

Comments
 (0)