Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 41 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,12 @@ jobs:
with:
extra_args: --all-files

tests:
tests-unit:
runs-on: ubuntu-latest
strategy:
matrix:
go-version: ['1.24.x', '1.25.x']
env:
# Needed for the quic-go test
GOEXPERIMENT: ${{ matrix.go-version == '1.24.x' && 'synctest' || '' }}
steps:
- uses: actions/checkout@v4
Expand All @@ -35,14 +34,48 @@ jobs:
with:
go-version: ${{ matrix.go-version }}
- uses: taiki-e/install-action@cargo-nextest

- uses: moonrepo/setup-rust@v1
- run: |
cd go-runner
cargo nextest run --all
- name: Run unit and discovery tests
run: cargo nextest run --lib -E 'not test(~integration_tests)'
working-directory: go-runner
env:
CODSPEED_GO_PKG_VERSION: ${{ github.head_ref || github.ref_name }}

tests-integration:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
go-version: ['1.24.x', '1.25.x']
shard:
- name: hugo
filter: "test(~hugo)"
- name: caddy
filter: "test(~caddy)"
- name: large
filter: "test(~fzf) | test(~opentelemetry) | test(~golang_benchmarks)"
- name: medium
filter: "test(~zerolog) | test(~zap) | test(~fuego) | test(~cli_runtime) | test(~quic)"
- name: examples
filter: "test(~example)"
env:
GOEXPERIMENT: ${{ matrix.go-version == '1.24.x' && 'synctest' || '' }}
name: integration (${{ matrix.go-version }}, ${{ matrix.shard.name }})
steps:
- uses: actions/checkout@v4
with:
lfs: true
submodules: true
- uses: actions/setup-go@v6
with:
go-version: ${{ matrix.go-version }}
- uses: taiki-e/install-action@cargo-nextest
- uses: moonrepo/setup-rust@v1
- name: Run integration tests (${{ matrix.shard.name }})
run: cargo nextest run -E '${{ matrix.shard.filter }}'
working-directory: go-runner
env:
CODSPEED_GO_PKG_VERSION: ${{ github.head_ref || github.ref_name }}

verify-fork-scripts:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -118,7 +151,8 @@ jobs:
if: always()
needs:
- lint
- tests
- tests-unit
- tests-integration
- verify-fork-scripts
- compat-integration-test-walltime
- go-runner-benchmarks
Expand Down
36 changes: 36 additions & 0 deletions example/compat/logr_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package example

import (
"fmt"
"testing"

"github.com/go-logr/logr/testr"
)

func TestLogrTestr(t *testing.T) {
logger := testr.NewWithOptions(t, testr.Options{Verbosity: 2})

logger.Info("starting test", "test", "logr")
logger.V(0).Info("V(0).info")
logger.V(1).Info("V(1).info")
logger.Error(fmt.Errorf("test error"), "error")

childLogger := logger.WithValues("request_id", "12345")
childLogger.Info("child logger message")

loggerWithName := childLogger.WithName("child")
loggerWithName.Info("named child logger")
}

func BenchmarkLogrTestr(b *testing.B) {
t := &testing.T{}
logger := testr.New(t)

for b.Loop() {
// Log operations within the benchmark
logger.Info("benchmark message")
logger.V(1).Info("verbose benchmark")
childLogger := logger.WithValues("id", "test")
childLogger.Info("child message")
}
}
55 changes: 55 additions & 0 deletions example/compat/stdr_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package example

import (
"errors"
"log"
"testing"

"github.com/go-logr/stdr"
)

func TestStdr(t *testing.T) {
logger := stdr.New(log.Default())

logger.Info("info message", "key", "value")
logger.Error(errors.New("test error"), "error message")

namedLogger := logger.WithName("myapp")
namedLogger.Info("named logger message")

childLogger := namedLogger.WithValues("request_id", "12345")
childLogger.Info("child logger message")

childLogger.V(1).Info("verbose message")
}

func TestStdrWithOptions(t *testing.T) {
options := stdr.Options{
LogCaller: stdr.Error,
Depth: 1,
}

logger := stdr.NewWithOptions(log.Default(), options)

logger.Info("info with options")
logger.Error(errors.New("err"), "error with options")
}

func BenchmarkStdr(b *testing.B) {
logger := stdr.New(log.Default())

for b.Loop() {
logger.Info("benchmark message", "iteration", b.N)
childLogger := logger.WithValues("id", "test")
childLogger.Info("child message")
}
}

func BenchmarkStdrWithName(b *testing.B) {
logger := stdr.New(log.Default()).WithName("bench")

for b.Loop() {
logger.Info("named benchmark", "count", b.N)
logger.V(1).Info("verbose benchmark")
}
}
4 changes: 3 additions & 1 deletion example/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ go 1.24.3

require (
github.com/frankban/quicktest v1.14.6
github.com/go-logr/logr v1.4.3
github.com/go-logr/stdr v1.2.2
github.com/stretchr/testify v1.11.1
github.com/thejerf/slogassert v0.3.4
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
Expand Down
8 changes: 7 additions & 1 deletion example/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
Expand Down
80 changes: 78 additions & 2 deletions go-runner/src/builder/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,25 +248,85 @@ impl GoBenchmark {
pub struct BenchmarkPackage {
raw_package: GoPackage,
pub benchmarks: Vec<GoBenchmark>,

/// Internal test files that should be renamed alongside external test files.
/// For external test packages (package foo_test), this contains files from
/// the companion internal test package (package foo in *_test.go files).
/// These files define helper functions that external tests depend on.
///
/// This field will only be set/used for external tests.
#[serde(skip_serializing_if = "Vec::is_empty")]
internal_test_files: Vec<String>,
}

impl BenchmarkPackage {
fn new(package: GoPackage, benchmarks: Vec<GoBenchmark>) -> Self {
fn new(
package: GoPackage,
benchmarks: Vec<GoBenchmark>,
internal_test_files: Vec<String>,
) -> Self {
Self {
raw_package: package,
benchmarks,
internal_test_files,
}
}

/// Returns the internal test files that should be renamed for this package.
pub fn internal_test_files(&self) -> &[String] {
&self.internal_test_files
}

pub fn from_project(
go_project_path: &Path,
packages: &[String],
) -> anyhow::Result<Vec<BenchmarkPackage>> {
use std::collections::HashMap;

let mut raw_packages = Self::run_go_list(go_project_path, packages)?;

// Sort packages by import path to ensure deterministic order
raw_packages.sort_by(|a, b| a.import_path.cmp(&b.import_path));

// Pre-pass: Find internal test helper files from ForTest packages that have no benchmarks.
// These are internal test files (package foo) that external tests (package foo_test) may depend on.
// Key: (dir, for_test) -> internal test files (TestGoFiles)
let mut helper_tests: HashMap<(PathBuf, String), Vec<String>> = HashMap::new();
for pkg in &raw_packages {
// Skip external test packages - we only want internal test packages
if pkg.is_external_test_package() {
continue;
}

// Must have ForTest field (indicates this is a test package)
let Some(for_test) = &pkg.for_test else {
continue;
};

// Must be a test executable
if !pkg.import_path.ends_with(".test]") {
continue;
}

// Must have internal test files (TestGoFiles)
let Some(test_go_files) = &pkg.test_go_files else {
continue;
};
if test_go_files.is_empty() {
continue;
}

// This is a helper-only internal test package - store its TestGoFiles
debug!(
"Found internal test helper files for {}: {:?}",
for_test, test_go_files
);
helper_tests
.entry((pkg.dir.clone(), for_test.clone()))
.or_default()
.extend(test_go_files.clone());
}

let mut packages = Vec::new();
for package in raw_packages {
// Filter 1: Must have test files
Expand Down Expand Up @@ -318,7 +378,20 @@ impl BenchmarkPackage {
continue;
}

packages.push(BenchmarkPackage::new(package, benchmarks));
// For external test packages, attach any internal helper test files
let internal_helpers = if package.is_external_test_package() {
if let Some(for_test) = &package.for_test {
helper_tests
.get(&(package.dir.clone(), for_test.clone()))
.cloned()
.unwrap_or_default()
} else {
Vec::new()
}
} else {
Vec::new()
};
packages.push(BenchmarkPackage::new(package, benchmarks, internal_helpers));
}

Ok(packages)
Expand Down Expand Up @@ -378,6 +451,9 @@ mod tests {
#[case::example_with_main("example-with-main")]
#[case::example_with_dot_go_folder("example-with-dot-go-folder")]
#[case::example_with_test_package("example-with-test-package")]
#[case::example_with_excluded_names("example-with-excluded-names")]
#[case::example_external_test_unexported_access("example-external-test-unexported-access")]
#[case::example_with_testify("example-with-testify")]
#[test_log::test]
fn test_discover_benchmarks(#[case] project_name: &str) {
let project_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
Expand Down
38 changes: 38 additions & 0 deletions go-runner/src/builder/patcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,34 @@ impl Patcher {
"\"github.com/CodSpeedHQ/codspeed-go/pkg/quicktest\"",
);

// Replace logr + subpackages
for logr_pkg in &["testr", "funcr", "slogr", "benchmark", "testing"] {
find_replace_range(
&format!("github.com/go-logr/logr/{}", logr_pkg),
&format!("\"github.com/CodSpeedHQ/codspeed-go/pkg/logr/{logr_pkg}\""),
);
}
find_replace_range(
"github.com/go-logr/logr",
"\"github.com/CodSpeedHQ/codspeed-go/pkg/logr\"",
);
find_replace_range(
"github.com/go-logr/stdr",
"\"github.com/CodSpeedHQ/codspeed-go/pkg/stdr\"",
);

// Replace testify + subpackages
for testify_pkg in &["assert", "require", "mock", "suite", "http"] {
find_replace_range(
&format!("github.com/stretchr/testify/{}", testify_pkg),
&format!("\"github.com/CodSpeedHQ/codspeed-go/pkg/testify/{testify_pkg}\""),
);
}
find_replace_range(
"github.com/stretchr/testify",
"\"github.com/CodSpeedHQ/codspeed-go/pkg/testify\"",
);

// Apply replacements in reverse order to avoid shifting positions
for (range, replacement) in replacements
.into_iter()
Expand Down Expand Up @@ -465,6 +493,15 @@ import (
"testing/iotest"
"testing/synctest"
)
"#;

const IMPORT_TESTIFY: &str = r#"package main
import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
"#;

#[rstest]
Expand All @@ -484,6 +521,7 @@ import (
)]
#[case("package_main", PACKAGE_MAIN)]
#[case("many_testing_imports", MANY_TESTING_IMPORTS)]
#[case("import_testify", IMPORT_TESTIFY)]
fn test_patch_go_source(#[case] test_name: &str, #[case] source: &str) {
let mut result = source.to_string();

Expand Down
Loading
Loading