Skip to content

Commit 3b90659

Browse files
committed
wip: refactored the data model
1 parent 118e1a4 commit 3b90659

6 files changed

+925
-1549
lines changed

go-runner/src/builder/discovery.rs

Lines changed: 145 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Finds all the benchmarks and packages in a given Go project.
22
33
use std::{
4-
collections::HashMap,
4+
ops::Deref,
55
path::{Path, PathBuf},
66
process::Command,
77
};
@@ -11,7 +11,7 @@ use crate::prelude::*;
1111
use serde::{Deserialize, Serialize};
1212

1313
#[derive(Debug, Clone, Serialize, Deserialize)]
14-
pub struct Module {
14+
pub struct GoModule {
1515
/// The module path (e.g., "local.dev/example-complex").
1616
#[serde(rename = "Path")]
1717
pub path: String,
@@ -58,7 +58,7 @@ pub struct GoPackage {
5858
pub compiled_go_files: Option<Vec<String>>,
5959

6060
#[serde(rename = "Module")]
61-
pub module: Module,
61+
pub module: GoModule,
6262
}
6363

6464
impl GoPackage {
@@ -70,15 +70,7 @@ impl GoPackage {
7070
serde_json::from_str(&format!("[{output}]")).context("Failed to parse Go list output")
7171
}
7272

73-
pub fn name(&self) -> &str {
74-
&self.name
75-
}
76-
77-
pub fn test_go_files(&self) -> Option<&Vec<String>> {
78-
self.test_go_files.as_ref()
79-
}
80-
81-
pub fn benchmarks(&self) -> anyhow::Result<Vec<BenchmarkData>> {
73+
fn benchmarks(&self) -> anyhow::Result<Vec<GoBenchmark>> {
8274
let Some(test_go_files) = &self.test_go_files else {
8375
bail!("No test files found for package: {}", self.name);
8476
};
@@ -100,25 +92,25 @@ impl GoPackage {
10092
};
10193

10294
// We don't support files with benchmarks that also contains github.com/frankban/quicktest
103-
if file
104-
.imports
105-
.iter()
106-
.any(|import| import.path.value.contains("github.com/frankban/quicktest"))
107-
{
108-
warn!("Skipping file with quicktest import: {file_path:?}");
109-
continue;
110-
}
111-
112-
// We don't support testify yet
113-
// FIXME: We shouldn't skip packages where benchmarks and tests are separated -> opentelemetry-go
114-
if file
115-
.imports
116-
.iter()
117-
.any(|import| import.path.value.contains("github.com/stretchr/testify"))
118-
{
119-
warn!("Skipping file with testify import: {file_path:?}");
120-
continue;
121-
}
95+
// if file
96+
// .imports
97+
// .iter()
98+
// .any(|import| import.path.value.contains("github.com/frankban/quicktest"))
99+
// {
100+
// warn!("Skipping file with quicktest import: {file_path:?}");
101+
// continue;
102+
// }
103+
104+
// // We don't support testify yet
105+
// // FIXME: We shouldn't skip packages where benchmarks and tests are separated -> opentelemetry-go
106+
// if file
107+
// .imports
108+
// .iter()
109+
// .any(|import| import.path.value.contains("github.com/stretchr/testify"))
110+
// {
111+
// warn!("Skipping file with testify import: {file_path:?}");
112+
// continue;
113+
// }
122114

123115
// We can't import packages that are declared as `main`
124116
if file.pkg_name.name == "main" {
@@ -159,8 +151,26 @@ impl GoPackage {
159151

160152
let valid_benchmarks =
161153
verifier::FuncVisitor::verify_source_code(&content, &found_benchmarks)?;
154+
if valid_benchmarks.len() != found_benchmarks.len() {
155+
warn!(
156+
"Only {} out of {} are valid, skipping file",
157+
valid_benchmarks.len(),
158+
found_benchmarks.len()
159+
);
160+
warn!("Valid benchmarks: {valid_benchmarks:?}");
161+
warn!(
162+
"Invalid benchmarks: {:?}",
163+
found_benchmarks
164+
.iter()
165+
.filter(|f| !valid_benchmarks.contains(f))
166+
.collect::<Vec<_>>()
167+
);
168+
169+
continue;
170+
}
171+
162172
for func in valid_benchmarks {
163-
benchmarks.push(BenchmarkData::new(
173+
benchmarks.push(GoBenchmark::new(
164174
package_import_path.clone(),
165175
func,
166176
root_relative_file_path.to_path_buf(),
@@ -173,7 +183,7 @@ impl GoPackage {
173183
}
174184

175185
#[derive(Debug, Clone, Serialize, Deserialize)]
176-
pub struct BenchmarkData {
186+
pub struct GoBenchmark {
177187
/// The name of the benchmark (e.g. `BenchmarkFoo`).
178188
pub name: String,
179189

@@ -190,7 +200,7 @@ pub struct BenchmarkData {
190200
pub file_path: PathBuf,
191201
}
192202

193-
impl BenchmarkData {
203+
impl GoBenchmark {
194204
pub fn new(package_import_path: String, name: String, file_path: PathBuf) -> Self {
195205
#[cfg(test)]
196206
let import_alias = {
@@ -213,143 +223,129 @@ impl BenchmarkData {
213223
}
214224
}
215225

216-
fn run_go_list(go_project_path: &Path) -> anyhow::Result<Vec<GoPackage>> {
217-
// Execute 'go list -test -compiled -json ./...' to get package information
218-
let output = Command::new("go")
219-
.args(["list", "-test", "-compiled", "-json", "./..."])
220-
.current_dir(go_project_path)
221-
.output()?;
222-
223-
if !output.status.success() {
224-
bail!(
225-
"Failed to execute 'go list': {}",
226-
String::from_utf8_lossy(&output.stderr)
227-
);
226+
/// Represents a package with its benchmarks.
227+
#[derive(Debug, Clone, Serialize)]
228+
pub struct BenchmarkPackage {
229+
raw_package: GoPackage,
230+
pub benchmarks: Vec<GoBenchmark>,
231+
}
232+
233+
impl BenchmarkPackage {
234+
fn new(package: GoPackage, benchmarks: Vec<GoBenchmark>) -> Self {
235+
Self {
236+
raw_package: package,
237+
benchmarks,
238+
}
228239
}
229240

230-
// Wrap it in '[{output}]' and parse it with serde_json
231-
let output_str = String::from_utf8(output.stdout)?;
232-
trace!("Go list output: {output_str}");
241+
pub fn from_project(go_project_path: &Path) -> anyhow::Result<Vec<BenchmarkPackage>> {
242+
let raw_packages = Self::run_go_list(go_project_path)?;
243+
let has_test_files =
244+
|files: &Vec<String>| files.iter().any(|name| name.ends_with("_test.go"));
245+
let has_test_imports = |imports: &Vec<String>| {
246+
imports.iter().any(|import| {
247+
// import "testing"
248+
import.contains("testing")
249+
})
250+
};
233251

234-
GoPackage::from_go_list_output(&output_str)
235-
}
252+
let mut packages = Vec::new();
253+
for package in raw_packages {
254+
// Skip packages without test files
255+
let has_tests = package
256+
.test_go_files
257+
.as_ref()
258+
.map(has_test_files)
259+
.unwrap_or_default();
260+
if !has_tests {
261+
debug!("Skipping package without test files: {}", package.name);
262+
continue;
263+
}
236264

237-
pub fn run(go_project_path: &Path) -> anyhow::Result<HashMap<String, GoPackage>> {
238-
let packages = run_go_list(go_project_path)?;
265+
// Skip packages without test imports
266+
let has_test_imports = package
267+
.test_imports
268+
.as_ref()
269+
.map(has_test_imports)
270+
.unwrap_or_default();
271+
if !has_test_imports {
272+
debug!("Skipping package without test imports: {}", package.name);
273+
continue;
274+
}
239275

240-
discover_benchmarks(packages)
241-
}
276+
// Only include test executables, since we want to generate them manually.
277+
// Example format: `local.dev/example-complex [local.dev/example-complex.test]`
278+
if !package.import_path.ends_with(".test]") {
279+
debug!("Skipping package without test executable: {}", package.name);
280+
continue;
281+
}
242282

243-
fn discover_benchmarks(packages: Vec<GoPackage>) -> anyhow::Result<HashMap<String, GoPackage>> {
244-
let has_test_files = |files: &Vec<String>| files.iter().any(|name| name.ends_with("_test.go"));
245-
let has_test_imports = |imports: &Vec<String>| {
246-
imports.iter().any(|import| {
247-
// import "testing"
248-
import.contains("testing")
249-
})
250-
};
251-
252-
let mut benchmark_packages: HashMap<String, GoPackage> = HashMap::new();
253-
for package in packages {
254-
// Skip packages without test files
255-
let has_tests = package
256-
.test_go_files
257-
.as_ref()
258-
.map(has_test_files)
259-
.unwrap_or_default();
260-
if !has_tests {
261-
debug!("Skipping package without test files: {}", package.name);
262-
continue;
263-
}
283+
// Skip packages that don't have benchmarks
284+
let benchmarks = match package.benchmarks() {
285+
Ok(benchmarks) => benchmarks,
286+
Err(e) => {
287+
warn!(
288+
"Failed to get benchmarks for package {}: {}",
289+
package.name, e
290+
);
291+
continue;
292+
}
293+
};
294+
if benchmarks.is_empty() {
295+
debug!("Skipping package without benchmarks: {}", package.name);
296+
continue;
297+
}
264298

265-
// Skip packages without test imports
266-
let has_test_imports = package
267-
.test_imports
268-
.as_ref()
269-
.map(has_test_imports)
270-
.unwrap_or_default();
271-
if !has_test_imports {
272-
debug!("Skipping package without test imports: {}", package.name);
273-
continue;
299+
packages.push(BenchmarkPackage::new(package, benchmarks));
274300
}
275301

276-
// Only include test executables, since we want to generate them manually.
277-
// Example format: `local.dev/example-complex [local.dev/example-complex.test]`
278-
if !package.import_path.ends_with(".test]") {
279-
debug!("Skipping package without test executable: {}", package.name);
280-
continue;
281-
}
302+
Ok(packages)
303+
}
282304

283-
// Skip packages that don't have benchmarks
284-
let benchmarks = match package.benchmarks() {
285-
Ok(benchmarks) => benchmarks,
286-
Err(e) => {
287-
warn!(
288-
"Failed to get benchmarks for package {}: {}",
289-
package.name, e
290-
);
291-
continue;
292-
}
293-
};
294-
if benchmarks.is_empty() {
295-
debug!("Skipping package without benchmarks: {}", package.name);
296-
continue;
305+
fn run_go_list(go_project_path: &Path) -> anyhow::Result<Vec<GoPackage>> {
306+
// Execute 'go list -test -compiled -json ./...' to get package information
307+
let output = Command::new("go")
308+
.args(["list", "-test", "-compiled", "-json", "./..."])
309+
.current_dir(go_project_path)
310+
.output()?;
311+
312+
if !output.status.success() {
313+
bail!(
314+
"Failed to execute 'go list': {}",
315+
String::from_utf8_lossy(&output.stderr)
316+
);
297317
}
298318

299-
assert!(!benchmark_packages.contains_key(&package.import_path));
300-
benchmark_packages.insert(package.import_path.clone(), package);
301-
// // FIXME: Assert that we didn't already add this package
302-
// benchmark_packages
303-
// .entry(package.import_path.clone())
304-
// .insert_entry(package);
319+
// Wrap it in '[{output}]' and parse it with serde_json
320+
let output_str = String::from_utf8(output.stdout)?;
321+
trace!("Go list output: {output_str}");
322+
323+
GoPackage::from_go_list_output(&output_str)
305324
}
325+
}
306326

307-
Ok(benchmark_packages)
327+
impl Deref for BenchmarkPackage {
328+
type Target = GoPackage;
329+
330+
fn deref(&self) -> &Self::Target {
331+
&self.raw_package
332+
}
308333
}
309334

310335
#[cfg(test)]
311336
mod tests {
312337
use super::*;
313338

314-
#[test]
315-
fn test_parse_go_list_output() {
316-
let packages = run_go_list(Path::new("testdata/projects/golang-benchmarks")).unwrap();
317-
318-
insta::with_settings!({sort_maps => true}, {
319-
insta::assert_json_snapshot!(packages, {
320-
".**[\"Dir\"]" => "[package_dir]",
321-
".**[\"Module\"][\"Dir\"]" => "[module_dir]",
322-
".**[\"Module\"][\"GoMod\"]" => "[go_mod_path]"
323-
});
324-
});
325-
}
326-
327339
#[test]
328340
fn test_discover_benchmarks() {
329-
let packages = run_go_list(Path::new("testdata/projects/golang-benchmarks")).unwrap();
330-
let packages = discover_benchmarks(packages).unwrap();
331-
332-
insta::with_settings!({sort_maps => true}, {
333-
insta::assert_json_snapshot!(packages, {
334-
".**[\"Dir\"]" => "[package_dir]",
335-
".**[\"Module\"][\"Dir\"]" => "[module_dir]",
336-
".**[\"Module\"][\"GoMod\"]" => "[go_mod_path]"
337-
});
341+
let packages =
342+
BenchmarkPackage::from_project(Path::new("testdata/projects/golang-benchmarks"))
343+
.unwrap();
344+
345+
insta::assert_json_snapshot!(packages, {
346+
".**[\"Dir\"]" => "[package_dir]",
347+
".**[\"Module\"][\"Dir\"]" => "[module_dir]",
348+
".**[\"Module\"][\"GoMod\"]" => "[go_mod_path]"
338349
});
339350
}
340-
341-
#[test]
342-
fn test_benchmarks_for_package() {
343-
let packages = run_go_list(Path::new("testdata/projects/golang-benchmarks")).unwrap();
344-
let benchmark_packages = discover_benchmarks(packages).unwrap();
345-
346-
let package = benchmark_packages
347-
.get("github.com/SimonWaldherr/golang-benchmarks/base64 [github.com/SimonWaldherr/golang-benchmarks/base64.test]")
348-
.unwrap();
349-
350-
let mut benches = package.benchmarks().unwrap();
351-
benches.sort_by_cached_key(|b| b.name.clone());
352-
353-
insta::assert_json_snapshot!(benches);
354-
}
355351
}

0 commit comments

Comments
 (0)