From f5ab9cd05d08851790c78aa0a43a4566100a87dc Mon Sep 17 00:00:00 2001 From: Kiril Keranov <114745615+kiril-keranov@users.noreply.github.com> Date: Tue, 24 Feb 2026 18:23:39 +0200 Subject: [PATCH 1/8] Refactor Context struct to use interfaces --- src/java/common/context.go | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/java/common/context.go b/src/java/common/context.go index da735415d..b9c8b2d3e 100644 --- a/src/java/common/context.go +++ b/src/java/common/context.go @@ -4,20 +4,46 @@ import ( "encoding/json" "fmt" "github.com/cloudfoundry/libbuildpack" + "io" "os" "path/filepath" "strconv" "strings" ) +type Command interface { + Execute(string, io.Writer, io.Writer, string, ...string) error +} + +type Stager interface { + LinkDirectoryInDepDir(string, string) error + BuildDir() string + DepDir() string + DepsIdx() string + WriteConfigYml(interface{}) error + WriteEnvFile(string, string) error + WriteProfileD(string, string) error +} + +type Manifest interface { + AllDependencyVersions(string) []string + DefaultVersion(string) (libbuildpack.Dependency, error) + GetEntry(libbuildpack.Dependency) (*libbuildpack.ManifestEntry, error) +} + +type Installer interface { + InstallDependency(libbuildpack.Dependency, string) error + InstallDependencyWithStrip(libbuildpack.Dependency, string, int) error +} + // Context holds shared dependencies for buildpack components // Used by containers, frameworks, and JREs to access buildpack infrastructure type Context struct { - Stager *libbuildpack.Stager - Manifest *libbuildpack.Manifest - Installer *libbuildpack.Installer + Stager Stager + Manifest Manifest + Installer Installer Log *libbuildpack.Logger - Command *libbuildpack.Command + Command Command } // DetermineJavaVersion determines the major Java version from a Java installation From 965ad5ea0009d4edb4b34bcb940cdabefbe9541d Mon Sep 17 00:00:00 2001 From: Kiril Keranov Date: Wed, 25 Feb 2026 16:45:21 +0200 Subject: [PATCH 2/8] Add mocks;Adjust supply tests;adjust metrics test --- go.mod | 3 +- src/internal/mocks/mocks.go | 294 ++++++++++++++++++ src/java/finalize/finalize.go | 8 +- src/java/frameworks/cf_metrics_exporter.go | 3 - .../frameworks/cf_metrics_exporter_test.go | 2 + src/java/supply/supply.go | 20 +- src/java/supply/supply_test.go | 180 ++++++++--- 7 files changed, 457 insertions(+), 53 deletions(-) create mode 100644 src/internal/mocks/mocks.go diff --git a/go.mod b/go.mod index f25e80a78..4b2bc4c26 100644 --- a/go.mod +++ b/go.mod @@ -6,9 +6,11 @@ require ( github.com/Dynatrace/libbuildpack-dynatrace v1.8.0 github.com/cloudfoundry/libbuildpack v0.0.0-20251203175254-7be530ec9fef github.com/cloudfoundry/switchblade v0.9.4 + github.com/golang/mock v1.6.0 github.com/onsi/ginkgo/v2 v2.27.2 github.com/onsi/gomega v1.38.2 github.com/sclevine/spec v1.4.0 + go.yaml.in/yaml/v3 v3.0.4 gopkg.in/yaml.v2 v2.4.0 ) @@ -42,7 +44,6 @@ require ( go.opentelemetry.io/otel v1.32.0 // indirect go.opentelemetry.io/otel/metric v1.32.0 // indirect go.opentelemetry.io/otel/trace v1.32.0 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/mod v0.30.0 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/sync v0.18.0 // indirect diff --git a/src/internal/mocks/mocks.go b/src/internal/mocks/mocks.go new file mode 100644 index 000000000..7174c7238 --- /dev/null +++ b/src/internal/mocks/mocks.go @@ -0,0 +1,294 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: src/java/common/context.go + +// Package mocks is a generated GoMock package. +package mocks + +import ( + io "io" + reflect "reflect" + + libbuildpack "github.com/cloudfoundry/libbuildpack" + gomock "github.com/golang/mock/gomock" +) + +// MockCommand is a mock of Command interface. +type MockCommand struct { + ctrl *gomock.Controller + recorder *MockCommandMockRecorder +} + +// MockCommandMockRecorder is the mock recorder for MockCommand. +type MockCommandMockRecorder struct { + mock *MockCommand +} + +// NewMockCommand creates a new mock instance. +func NewMockCommand(ctrl *gomock.Controller) *MockCommand { + mock := &MockCommand{ctrl: ctrl} + mock.recorder = &MockCommandMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockCommand) EXPECT() *MockCommandMockRecorder { + return m.recorder +} + +// Execute mocks base method. +func (m *MockCommand) Execute(arg0 string, arg1, arg2 io.Writer, arg3 string, arg4 ...string) error { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1, arg2, arg3} + for _, a := range arg4 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Execute", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Execute indicates an expected call of Execute. +func (mr *MockCommandMockRecorder) Execute(arg0, arg1, arg2, arg3 interface{}, arg4 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1, arg2, arg3}, arg4...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Execute", reflect.TypeOf((*MockCommand)(nil).Execute), varargs...) +} + +// MockStager is a mock of Stager interface. +type MockStager struct { + ctrl *gomock.Controller + recorder *MockStagerMockRecorder +} + +// MockStagerMockRecorder is the mock recorder for MockStager. +type MockStagerMockRecorder struct { + mock *MockStager +} + +// NewMockStager creates a new mock instance. +func NewMockStager(ctrl *gomock.Controller) *MockStager { + mock := &MockStager{ctrl: ctrl} + mock.recorder = &MockStagerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockStager) EXPECT() *MockStagerMockRecorder { + return m.recorder +} + +// BuildDir mocks base method. +func (m *MockStager) BuildDir() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BuildDir") + ret0, _ := ret[0].(string) + return ret0 +} + +// BuildDir indicates an expected call of BuildDir. +func (mr *MockStagerMockRecorder) BuildDir() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildDir", reflect.TypeOf((*MockStager)(nil).BuildDir)) +} + +// DepDir mocks base method. +func (m *MockStager) DepDir() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DepDir") + ret0, _ := ret[0].(string) + return ret0 +} + +// DepDir indicates an expected call of DepDir. +func (mr *MockStagerMockRecorder) DepDir() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DepDir", reflect.TypeOf((*MockStager)(nil).DepDir)) +} + +// DepsIdx mocks base method. +func (m *MockStager) DepsIdx() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DepsIdx") + ret0, _ := ret[0].(string) + return ret0 +} + +// DepsIdx indicates an expected call of DepsIdx. +func (mr *MockStagerMockRecorder) DepsIdx() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DepsIdx", reflect.TypeOf((*MockStager)(nil).DepsIdx)) +} + +// LinkDirectoryInDepDir mocks base method. +func (m *MockStager) LinkDirectoryInDepDir(arg0, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LinkDirectoryInDepDir", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// LinkDirectoryInDepDir indicates an expected call of LinkDirectoryInDepDir. +func (mr *MockStagerMockRecorder) LinkDirectoryInDepDir(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkDirectoryInDepDir", reflect.TypeOf((*MockStager)(nil).LinkDirectoryInDepDir), arg0, arg1) +} + +// WriteConfigYml mocks base method. +func (m *MockStager) WriteConfigYml(arg0 interface{}) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WriteConfigYml", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// WriteConfigYml indicates an expected call of WriteConfigYml. +func (mr *MockStagerMockRecorder) WriteConfigYml(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteConfigYml", reflect.TypeOf((*MockStager)(nil).WriteConfigYml), arg0) +} + +// WriteEnvFile mocks base method. +func (m *MockStager) WriteEnvFile(arg0, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WriteEnvFile", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// WriteEnvFile indicates an expected call of WriteEnvFile. +func (mr *MockStagerMockRecorder) WriteEnvFile(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteEnvFile", reflect.TypeOf((*MockStager)(nil).WriteEnvFile), arg0, arg1) +} + +// WriteProfileD mocks base method. +func (m *MockStager) WriteProfileD(arg0, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WriteProfileD", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// WriteProfileD indicates an expected call of WriteProfileD. +func (mr *MockStagerMockRecorder) WriteProfileD(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteProfileD", reflect.TypeOf((*MockStager)(nil).WriteProfileD), arg0, arg1) +} + +// MockManifest is a mock of Manifest interface. +type MockManifest struct { + ctrl *gomock.Controller + recorder *MockManifestMockRecorder +} + +// MockManifestMockRecorder is the mock recorder for MockManifest. +type MockManifestMockRecorder struct { + mock *MockManifest +} + +// NewMockManifest creates a new mock instance. +func NewMockManifest(ctrl *gomock.Controller) *MockManifest { + mock := &MockManifest{ctrl: ctrl} + mock.recorder = &MockManifestMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockManifest) EXPECT() *MockManifestMockRecorder { + return m.recorder +} + +// AllDependencyVersions mocks base method. +func (m *MockManifest) AllDependencyVersions(arg0 string) []string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AllDependencyVersions", arg0) + ret0, _ := ret[0].([]string) + return ret0 +} + +// AllDependencyVersions indicates an expected call of AllDependencyVersions. +func (mr *MockManifestMockRecorder) AllDependencyVersions(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AllDependencyVersions", reflect.TypeOf((*MockManifest)(nil).AllDependencyVersions), arg0) +} + +// DefaultVersion mocks base method. +func (m *MockManifest) DefaultVersion(arg0 string) (libbuildpack.Dependency, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DefaultVersion", arg0) + ret0, _ := ret[0].(libbuildpack.Dependency) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DefaultVersion indicates an expected call of DefaultVersion. +func (mr *MockManifestMockRecorder) DefaultVersion(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DefaultVersion", reflect.TypeOf((*MockManifest)(nil).DefaultVersion), arg0) +} + +// GetEntry mocks base method. +func (m *MockManifest) GetEntry(arg0 libbuildpack.Dependency) (*libbuildpack.ManifestEntry, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetEntry", arg0) + ret0, _ := ret[0].(*libbuildpack.ManifestEntry) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetEntry indicates an expected call of GetEntry. +func (mr *MockManifestMockRecorder) GetEntry(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEntry", reflect.TypeOf((*MockManifest)(nil).GetEntry), arg0) +} + +// MockInstaller is a mock of Installer interface. +type MockInstaller struct { + ctrl *gomock.Controller + recorder *MockInstallerMockRecorder +} + +// MockInstallerMockRecorder is the mock recorder for MockInstaller. +type MockInstallerMockRecorder struct { + mock *MockInstaller +} + +// NewMockInstaller creates a new mock instance. +func NewMockInstaller(ctrl *gomock.Controller) *MockInstaller { + mock := &MockInstaller{ctrl: ctrl} + mock.recorder = &MockInstallerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockInstaller) EXPECT() *MockInstallerMockRecorder { + return m.recorder +} + +// InstallDependency mocks base method. +func (m *MockInstaller) InstallDependency(arg0 libbuildpack.Dependency, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InstallDependency", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// InstallDependency indicates an expected call of InstallDependency. +func (mr *MockInstallerMockRecorder) InstallDependency(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstallDependency", reflect.TypeOf((*MockInstaller)(nil).InstallDependency), arg0, arg1) +} + +// InstallDependencyWithStrip mocks base method. +func (m *MockInstaller) InstallDependencyWithStrip(arg0 libbuildpack.Dependency, arg1 string, arg2 int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InstallDependencyWithStrip", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// InstallDependencyWithStrip indicates an expected call of InstallDependencyWithStrip. +func (mr *MockInstallerMockRecorder) InstallDependencyWithStrip(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstallDependencyWithStrip", reflect.TypeOf((*MockInstaller)(nil).InstallDependencyWithStrip), arg0, arg1, arg2) +} diff --git a/src/java/finalize/finalize.go b/src/java/finalize/finalize.go index 62b93142f..63e87e039 100644 --- a/src/java/finalize/finalize.go +++ b/src/java/finalize/finalize.go @@ -15,11 +15,11 @@ import ( ) type Finalizer struct { - Stager *libbuildpack.Stager - Manifest *libbuildpack.Manifest - Installer *libbuildpack.Installer + Stager common.Stager + Manifest common.Manifest + Installer common.Installer Log *libbuildpack.Logger - Command *libbuildpack.Command + Command common.Command Container containers.Container JRE jres.JRE ContainerName string diff --git a/src/java/frameworks/cf_metrics_exporter.go b/src/java/frameworks/cf_metrics_exporter.go index 7222727d3..0f5b73cf4 100644 --- a/src/java/frameworks/cf_metrics_exporter.go +++ b/src/java/frameworks/cf_metrics_exporter.go @@ -27,9 +27,6 @@ type CfMetricsExporterFramework struct { func NewCfMetricsExporterFramework(ctx *common.Context) *CfMetricsExporterFramework { installer := ctx.Installer - if installer == nil { - installer = libbuildpack.NewInstaller(ctx.Manifest) - } return &CfMetricsExporterFramework{context: ctx, installer: installer} } diff --git a/src/java/frameworks/cf_metrics_exporter_test.go b/src/java/frameworks/cf_metrics_exporter_test.go index a1057aff3..cfc813604 100644 --- a/src/java/frameworks/cf_metrics_exporter_test.go +++ b/src/java/frameworks/cf_metrics_exporter_test.go @@ -98,6 +98,7 @@ func TestSupplyPlacesJarCorrectly(t *testing.T) { ctx := &common.Context{Manifest: manifest} ctx.Stager = libbuildpack.NewStager(args, libbuildpack.NewLogger(os.Stdout), manifest) ctx.Log = libbuildpack.NewLogger(os.Stdout) + ctx.Installer = libbuildpack.NewInstaller(manifest) // Pre-create the expected JAR file jarName := "cf-metrics-exporter-0.7.1.jar" // adjust if version changes in manifest @@ -146,6 +147,7 @@ func TestSupplyLogsProps(t *testing.T) { args := []string{"", "", tmpDepDir, "0"} ctx := &common.Context{Manifest: manifest} ctx.Stager = libbuildpack.NewStager(args, libbuildpack.NewLogger(os.Stdout), manifest) + ctx.Installer = libbuildpack.NewInstaller(manifest) // Pre-create the expected JAR file jarName := "cf-metrics-exporter-0.7.1.jar" diff --git a/src/java/supply/supply.go b/src/java/supply/supply.go index 778edc00f..b5faa1954 100644 --- a/src/java/supply/supply.go +++ b/src/java/supply/supply.go @@ -11,11 +11,11 @@ import ( ) type Supplier struct { - Stager *libbuildpack.Stager - Manifest *libbuildpack.Manifest - Installer *libbuildpack.Installer + Stager common.Stager + Manifest common.Manifest + Installer common.Installer Log *libbuildpack.Logger - Command *libbuildpack.Command + Command common.Command Container containers.Container } @@ -51,13 +51,13 @@ func Run(s *Supplier) error { s.Container = container // Install JRE - returns installed JRE for config persistence - jre, jreName, err := s.installJRE() + jre, jreName, err := s.InstallJRE() if err != nil { return err } // Install frameworks (APM agents, etc.) - if err := s.installFrameworks(); err != nil { + if err := s.InstallFrameworks(); err != nil { s.Log.Error("Failed to install frameworks: %s", err.Error()) return err } @@ -83,9 +83,9 @@ func Run(s *Supplier) error { return nil } -// installJRE installs the Java Runtime Environment. +// InstallJRE installs the Java Runtime Environment. // Returns the installed JRE instance and its name so the caller can persist them to config.yml. -func (s *Supplier) installJRE() (jres.JRE, string, error) { +func (s *Supplier) InstallJRE() (jres.JRE, string, error) { // Create JRE context ctx := &common.Context{ Stager: s.Stager, @@ -120,8 +120,8 @@ func (s *Supplier) installJRE() (jres.JRE, string, error) { return jre, jreName, nil } -// installFrameworks installs framework components (APM agents, etc.) -func (s *Supplier) installFrameworks() error { +// InstallFrameworks installs framework components (APM agents, etc.) +func (s *Supplier) InstallFrameworks() error { s.Log.BeginStep("Installing frameworks") // Create framework context diff --git a/src/java/supply/supply_test.go b/src/java/supply/supply_test.go index ef5ab1e76..421340bee 100644 --- a/src/java/supply/supply_test.go +++ b/src/java/supply/supply_test.go @@ -1,6 +1,8 @@ package supply_test import ( + "github.com/cloudfoundry/java-buildpack/src/internal/mocks" + "github.com/golang/mock/gomock" "os" "path/filepath" "time" @@ -13,13 +15,16 @@ import ( var _ = Describe("Supply", func() { var ( - buildDir string - cacheDir string - depsDir string - depsIdx string - supplier *supply.Supplier - stager *libbuildpack.Stager - logger *libbuildpack.Logger + buildDir string + cacheDir string + depsDir string + depsIdx string + mockCtrl *gomock.Controller + mockManifest *mocks.MockManifest + mockInstaller *mocks.MockInstaller + supplier *supply.Supplier + stager *libbuildpack.Stager + logger *libbuildpack.Logger ) BeforeEach(func() { @@ -55,6 +60,10 @@ dependencies: [] // Create logger logger = libbuildpack.NewLogger(GinkgoWriter) + mockCtrl = gomock.NewController(GinkgoT()) + mockManifest = mocks.NewMockManifest(mockCtrl) + mockInstaller = mocks.NewMockInstaller(mockCtrl) + // Create manifest with buildpack dir manifest, err := libbuildpack.NewManifest(buildpackDir, logger, time.Now()) Expect(err).NotTo(HaveOccurred()) @@ -63,29 +72,110 @@ dependencies: [] stager = libbuildpack.NewStager([]string{buildDir, cacheDir, depsDir, depsIdx}, logger, manifest) supplier = &supply.Supplier{ - Stager: stager, - Manifest: manifest, - Log: logger, - Command: &libbuildpack.Command{}, + Stager: stager, + Manifest: mockManifest, + Installer: mockInstaller, + Log: logger, + Command: &libbuildpack.Command{}, } }) AfterEach(func() { + mockCtrl.Finish() + os.RemoveAll(buildDir) os.RemoveAll(cacheDir) os.RemoveAll(depsDir) }) - Describe("Container Detection", func() { - Context("when a Spring Boot application is present", func() { + Describe("Various Container Supply", func() { + BeforeEach(func() { + // create jdk install dir + jdkInstallDir := filepath.Join(depsDir, depsIdx, "jre") + Expect(os.MkdirAll(filepath.Join(jdkInstallDir), 0755)).To(Succeed()) + + // create jvmkill install dir + jvmkillInstallDir := filepath.Join(depsDir, depsIdx, "tmp", "jvmkill-install") + Expect(os.MkdirAll(filepath.Join(jvmkillInstallDir), 0755)).To(Succeed()) + + // create memory calculator install dir + memCalcInstallDir := filepath.Join(depsDir, depsIdx, "tmp", "memory-calculator") + Expect(os.MkdirAll(filepath.Join(jvmkillInstallDir), 0755)).To(Succeed()) + + // create bin/java used to locate JAVA_HOME directory after JRE extraction + Expect(os.MkdirAll(filepath.Join(jdkInstallDir, "jre-17.0.15", "bin"), 0755)).To(Succeed()) + javaBin := filepath.Join(jdkInstallDir, "jre-17.0.15", "bin", "java") + Expect(os.WriteFile(javaBin, []byte("mockfile"), 0644)).To(Succeed()) + + // adjust JRE component mocks used during supply + depJre := libbuildpack.Dependency{Name: "openjdk", Version: "17.0.15"} + mockManifest.EXPECT().DefaultVersion("openjdk").Return(depJre, nil) + depJVMKill := libbuildpack.Dependency{Name: "jvmkill", Version: "1.17.0"} + mockManifest.EXPECT().DefaultVersion("jvmkill").Return(depJVMKill, nil) + depMemCalc := libbuildpack.Dependency{Name: "memory-calculator", Version: "4.2.0"} + mockManifest.EXPECT().DefaultVersion("memory-calculator").Return(depMemCalc, nil) + + mockInstaller.EXPECT().InstallDependency(depJre, jdkInstallDir).Return(nil) + mockInstaller.EXPECT().InstallDependency(depJVMKill, jvmkillInstallDir).Return(nil) + mockInstaller.EXPECT().InstallDependency(depMemCalc, memCalcInstallDir).Return(nil) + + ccmInstallDir := filepath.Join(depsDir, depsIdx, "client_certificate_mapper") + Expect(os.MkdirAll(filepath.Join(ccmInstallDir), 0755)).To(Succeed()) + cspInstallDir := filepath.Join(depsDir, depsIdx, "container_security_provider") + Expect(os.MkdirAll(filepath.Join(cspInstallDir), 0755)).To(Succeed()) + + depClientCertificateMapper := libbuildpack.Dependency{Name: "client-certificate-mapper", Version: "2.0.1"} + mockManifest.EXPECT().DefaultVersion("client-certificate-mapper").Return(depClientCertificateMapper, nil) + depContainerSecProvider := libbuildpack.Dependency{Name: "container-security-provider", Version: "1.20.0"} + mockManifest.EXPECT().DefaultVersion("container-security-provider").Return(depContainerSecProvider, nil) + + mockInstaller.EXPECT().InstallDependency(depClientCertificateMapper, ccmInstallDir).Return(nil) + mockInstaller.EXPECT().InstallDependency(depContainerSecProvider, cspInstallDir).Return(nil) + }) + + Context("when a Tomcat application is present", func() { BeforeEach(func() { - // Create a Spring Boot JAR with BOOT-INF - bootInfDir := filepath.Join(buildDir, "BOOT-INF") - Expect(os.MkdirAll(bootInfDir, 0755)).To(Succeed()) + // Create WEB-INF directory + webInfDir := filepath.Join(buildDir, "WEB-INF") + Expect(os.MkdirAll(webInfDir, 0755)).To(Succeed()) + + mockManifest.EXPECT().AllDependencyVersions("tomcat").Return([]string{"10.1.50"}) + tomcatInstallDir := filepath.Join(depsDir, depsIdx, "tomcat") + Expect(os.MkdirAll(filepath.Join(tomcatInstallDir), 0755)).To(Succeed()) + + depTomcat := libbuildpack.Dependency{Name: "tomcat", Version: "10.1.50"} + mockInstaller.EXPECT().InstallDependencyWithStrip(depTomcat, tomcatInstallDir, 1).Return(nil) + + tomcatLifeCycleSupportInstallDir := filepath.Join(depsDir, depsIdx, "tomcat", "lib") + Expect(os.MkdirAll(filepath.Join(tomcatLifeCycleSupportInstallDir), 0755)).To(Succeed()) + + tomcatAccessLoggingSupportInstallDir := filepath.Join(depsDir, depsIdx, "tomcat", "lib") + Expect(os.MkdirAll(filepath.Join(tomcatAccessLoggingSupportInstallDir), 0755)).To(Succeed()) + + tomcatLoggingSupportInstallDir := filepath.Join(depsDir, depsIdx, "tomcat", "bin") + Expect(os.MkdirAll(filepath.Join(tomcatLoggingSupportInstallDir), 0755)).To(Succeed()) + + depTomcatLifeCycleSupport := libbuildpack.Dependency{Name: "tomcat-lifecycle-support", Version: "3.4.0"} + mockManifest.EXPECT().DefaultVersion("tomcat-lifecycle-support").Return(depTomcatLifeCycleSupport, nil) + depTomcatAccessLoggingSupport := libbuildpack.Dependency{Name: "tomcat-access-logging-support", Version: "3.4.0"} + mockManifest.EXPECT().DefaultVersion("tomcat-access-logging-support").Return(depTomcatAccessLoggingSupport, nil) + depTomcatLoggingSupport := libbuildpack.Dependency{Name: "tomcat-logging-support", Version: "3.4.0"} + mockManifest.EXPECT().DefaultVersion("tomcat-logging-support").Return(depTomcatLoggingSupport, nil) + + mockInstaller.EXPECT().InstallDependency(depTomcatLifeCycleSupport, tomcatLifeCycleSupportInstallDir).Return(nil) + mockInstaller.EXPECT().InstallDependency(depTomcatAccessLoggingSupport, tomcatAccessLoggingSupportInstallDir).Return(nil) + mockInstaller.EXPECT().InstallDependency(depTomcatLoggingSupport, tomcatLoggingSupportInstallDir).Return(nil) + + mockManifest.EXPECT().GetEntry(depTomcatLoggingSupport).Return(&libbuildpack.ManifestEntry{}, nil) }) - It("creates the supplier with required components", func() { - // Verify supplier is properly initialized + It("Supply passes successfully", func() { + depDir := stager.DepDir() + err := supply.Run(supplier) + + Expect(err).To(BeNil()) + Expect(depDir).To(ContainSubstring(depsDir)) + Expect(supplier).NotTo(BeNil()) Expect(supplier.Stager).NotTo(BeNil()) Expect(supplier.Manifest).NotTo(BeNil()) @@ -94,17 +184,36 @@ dependencies: [] }) }) - Context("when a Tomcat application is present", func() { + Context("when a Spring-boot application is present", func() { BeforeEach(func() { - // Create WEB-INF directory - webInfDir := filepath.Join(buildDir, "WEB-INF") - Expect(os.MkdirAll(webInfDir, 0755)).To(Succeed()) + // Create a Spring Boot JAR with BOOT-INF + bootInfDir := filepath.Join(buildDir, "BOOT-INF") + Expect(os.MkdirAll(bootInfDir, 0755)).To(Succeed()) + Expect(os.MkdirAll(filepath.Join(buildDir, "META-INF"), 0755)).To(Succeed()) + + manifestFile := filepath.Join(buildDir, "META-INF", "MANIFEST.MF") + Expect(os.WriteFile(manifestFile, []byte("Spring-Boot-Version: 3.x.x"), 0644)).To(Succeed()) + + javaCfEnvInstallDir := filepath.Join(depsDir, depsIdx, "java_cf_env") + Expect(os.MkdirAll(filepath.Join(javaCfEnvInstallDir), 0755)).To(Succeed()) + + depJavaCfEnv := libbuildpack.Dependency{Name: "java-cfenv", Version: "3.5.0"} + mockManifest.EXPECT().DefaultVersion("java-cfenv").Return(depJavaCfEnv, nil) + mockInstaller.EXPECT().InstallDependency(depJavaCfEnv, javaCfEnvInstallDir).Return(nil) }) - It("creates the supplier with required components", func() { + It("Supply passes successfully", func() { + depDir := stager.DepDir() + err := supply.Run(supplier) + + Expect(err).To(BeNil()) + Expect(depDir).To(ContainSubstring(depsDir)) + Expect(supplier).NotTo(BeNil()) Expect(supplier.Stager).NotTo(BeNil()) Expect(supplier.Manifest).NotTo(BeNil()) + Expect(supplier.Log).NotTo(BeNil()) + Expect(supplier.Command).NotTo(BeNil()) }) }) @@ -113,21 +222,22 @@ dependencies: [] // Create a .groovy file groovyFile := filepath.Join(buildDir, "app.groovy") Expect(os.WriteFile(groovyFile, []byte("println 'hello'"), 0644)).To(Succeed()) - }) - It("creates the supplier with required components", func() { - Expect(supplier).NotTo(BeNil()) - Expect(supplier.Stager).NotTo(BeNil()) - Expect(supplier.Manifest).NotTo(BeNil()) + groovyInstallDir := filepath.Join(depsDir, depsIdx, "groovy") + err := os.MkdirAll(filepath.Join(groovyInstallDir), 0755) + Expect(err).To(BeNil()) + + depGroovy := libbuildpack.Dependency{Name: "groovy", Version: "4.0.29"} + mockManifest.EXPECT().DefaultVersion("groovy").Return(depGroovy, nil) + mockInstaller.EXPECT().InstallDependencyWithStrip(depGroovy, groovyInstallDir, 1).Return(nil) }) - }) - Context("when no recognized application type is present", func() { - It("fails to detect a container", func() { - // This would be tested via supply.Run() which we can't easily test - // without mocking the installer to avoid real downloads. - // Integration tests cover this scenario. - Expect(supplier).NotTo(BeNil()) + It("Supply passes successfully", func() { + depDir := stager.DepDir() + err := supply.Run(supplier) + + Expect(err).To(BeNil()) + Expect(depDir).To(ContainSubstring(depsDir)) }) }) }) From edf16cc213b7ae5da6cdc5f3924bac9cc83162a4 Mon Sep 17 00:00:00 2001 From: Kiril Keranov Date: Wed, 25 Feb 2026 16:59:26 +0200 Subject: [PATCH 3/8] Add gomock to vendor;adjust supply --- src/java/supply/supply.go | 12 +- vendor/github.com/golang/mock/AUTHORS | 12 + vendor/github.com/golang/mock/CONTRIBUTORS | 37 ++ vendor/github.com/golang/mock/LICENSE | 202 ++++++++ vendor/github.com/golang/mock/gomock/call.go | 445 ++++++++++++++++++ .../github.com/golang/mock/gomock/callset.go | 113 +++++ .../golang/mock/gomock/controller.go | 336 +++++++++++++ .../github.com/golang/mock/gomock/matchers.go | 341 ++++++++++++++ vendor/modules.txt | 3 + 9 files changed, 1495 insertions(+), 6 deletions(-) create mode 100644 vendor/github.com/golang/mock/AUTHORS create mode 100644 vendor/github.com/golang/mock/CONTRIBUTORS create mode 100644 vendor/github.com/golang/mock/LICENSE create mode 100644 vendor/github.com/golang/mock/gomock/call.go create mode 100644 vendor/github.com/golang/mock/gomock/callset.go create mode 100644 vendor/github.com/golang/mock/gomock/controller.go create mode 100644 vendor/github.com/golang/mock/gomock/matchers.go diff --git a/src/java/supply/supply.go b/src/java/supply/supply.go index b5faa1954..6b08a4858 100644 --- a/src/java/supply/supply.go +++ b/src/java/supply/supply.go @@ -51,13 +51,13 @@ func Run(s *Supplier) error { s.Container = container // Install JRE - returns installed JRE for config persistence - jre, jreName, err := s.InstallJRE() + jre, jreName, err := s.installJRE() if err != nil { return err } // Install frameworks (APM agents, etc.) - if err := s.InstallFrameworks(); err != nil { + if err := s.installFrameworks(); err != nil { s.Log.Error("Failed to install frameworks: %s", err.Error()) return err } @@ -83,9 +83,9 @@ func Run(s *Supplier) error { return nil } -// InstallJRE installs the Java Runtime Environment. +// installJRE installs the Java Runtime Environment. // Returns the installed JRE instance and its name so the caller can persist them to config.yml. -func (s *Supplier) InstallJRE() (jres.JRE, string, error) { +func (s *Supplier) installJRE() (jres.JRE, string, error) { // Create JRE context ctx := &common.Context{ Stager: s.Stager, @@ -120,8 +120,8 @@ func (s *Supplier) InstallJRE() (jres.JRE, string, error) { return jre, jreName, nil } -// InstallFrameworks installs framework components (APM agents, etc.) -func (s *Supplier) InstallFrameworks() error { +// installFrameworks installs framework components (APM agents, etc.) +func (s *Supplier) installFrameworks() error { s.Log.BeginStep("Installing frameworks") // Create framework context diff --git a/vendor/github.com/golang/mock/AUTHORS b/vendor/github.com/golang/mock/AUTHORS new file mode 100644 index 000000000..660b8ccc8 --- /dev/null +++ b/vendor/github.com/golang/mock/AUTHORS @@ -0,0 +1,12 @@ +# This is the official list of GoMock authors for copyright purposes. +# This file is distinct from the CONTRIBUTORS files. +# See the latter for an explanation. + +# Names should be added to this file as +# Name or Organization +# The email address is not required for organizations. + +# Please keep the list sorted. + +Alex Reece +Google Inc. diff --git a/vendor/github.com/golang/mock/CONTRIBUTORS b/vendor/github.com/golang/mock/CONTRIBUTORS new file mode 100644 index 000000000..def849cab --- /dev/null +++ b/vendor/github.com/golang/mock/CONTRIBUTORS @@ -0,0 +1,37 @@ +# This is the official list of people who can contribute (and typically +# have contributed) code to the gomock repository. +# The AUTHORS file lists the copyright holders; this file +# lists people. For example, Google employees are listed here +# but not in AUTHORS, because Google holds the copyright. +# +# The submission process automatically checks to make sure +# that people submitting code are listed in this file (by email address). +# +# Names should be added to this file only after verifying that +# the individual or the individual's organization has agreed to +# the appropriate Contributor License Agreement, found here: +# +# http://code.google.com/legal/individual-cla-v1.0.html +# http://code.google.com/legal/corporate-cla-v1.0.html +# +# The agreement for individuals can be filled out on the web. +# +# When adding J Random Contributor's name to this file, +# either J's name or J's organization's name should be +# added to the AUTHORS file, depending on whether the +# individual or corporate CLA was used. + +# Names should be added to this file like so: +# Name +# +# An entry with two email addresses specifies that the +# first address should be used in the submit logs and +# that the second address should be recognized as the +# same person when interacting with Rietveld. + +# Please keep the list sorted. + +Aaron Jacobs +Alex Reece +David Symonds +Ryan Barrett diff --git a/vendor/github.com/golang/mock/LICENSE b/vendor/github.com/golang/mock/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/vendor/github.com/golang/mock/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/golang/mock/gomock/call.go b/vendor/github.com/golang/mock/gomock/call.go new file mode 100644 index 000000000..13c9f44b1 --- /dev/null +++ b/vendor/github.com/golang/mock/gomock/call.go @@ -0,0 +1,445 @@ +// Copyright 2010 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gomock + +import ( + "fmt" + "reflect" + "strconv" + "strings" +) + +// Call represents an expected call to a mock. +type Call struct { + t TestHelper // for triggering test failures on invalid call setup + + receiver interface{} // the receiver of the method call + method string // the name of the method + methodType reflect.Type // the type of the method + args []Matcher // the args + origin string // file and line number of call setup + + preReqs []*Call // prerequisite calls + + // Expectations + minCalls, maxCalls int + + numCalls int // actual number made + + // actions are called when this Call is called. Each action gets the args and + // can set the return values by returning a non-nil slice. Actions run in the + // order they are created. + actions []func([]interface{}) []interface{} +} + +// newCall creates a *Call. It requires the method type in order to support +// unexported methods. +func newCall(t TestHelper, receiver interface{}, method string, methodType reflect.Type, args ...interface{}) *Call { + t.Helper() + + // TODO: check arity, types. + mArgs := make([]Matcher, len(args)) + for i, arg := range args { + if m, ok := arg.(Matcher); ok { + mArgs[i] = m + } else if arg == nil { + // Handle nil specially so that passing a nil interface value + // will match the typed nils of concrete args. + mArgs[i] = Nil() + } else { + mArgs[i] = Eq(arg) + } + } + + // callerInfo's skip should be updated if the number of calls between the user's test + // and this line changes, i.e. this code is wrapped in another anonymous function. + // 0 is us, 1 is RecordCallWithMethodType(), 2 is the generated recorder, and 3 is the user's test. + origin := callerInfo(3) + actions := []func([]interface{}) []interface{}{func([]interface{}) []interface{} { + // Synthesize the zero value for each of the return args' types. + rets := make([]interface{}, methodType.NumOut()) + for i := 0; i < methodType.NumOut(); i++ { + rets[i] = reflect.Zero(methodType.Out(i)).Interface() + } + return rets + }} + return &Call{t: t, receiver: receiver, method: method, methodType: methodType, + args: mArgs, origin: origin, minCalls: 1, maxCalls: 1, actions: actions} +} + +// AnyTimes allows the expectation to be called 0 or more times +func (c *Call) AnyTimes() *Call { + c.minCalls, c.maxCalls = 0, 1e8 // close enough to infinity + return c +} + +// MinTimes requires the call to occur at least n times. If AnyTimes or MaxTimes have not been called or if MaxTimes +// was previously called with 1, MinTimes also sets the maximum number of calls to infinity. +func (c *Call) MinTimes(n int) *Call { + c.minCalls = n + if c.maxCalls == 1 { + c.maxCalls = 1e8 + } + return c +} + +// MaxTimes limits the number of calls to n times. If AnyTimes or MinTimes have not been called or if MinTimes was +// previously called with 1, MaxTimes also sets the minimum number of calls to 0. +func (c *Call) MaxTimes(n int) *Call { + c.maxCalls = n + if c.minCalls == 1 { + c.minCalls = 0 + } + return c +} + +// DoAndReturn declares the action to run when the call is matched. +// The return values from this function are returned by the mocked function. +// It takes an interface{} argument to support n-arity functions. +func (c *Call) DoAndReturn(f interface{}) *Call { + // TODO: Check arity and types here, rather than dying badly elsewhere. + v := reflect.ValueOf(f) + + c.addAction(func(args []interface{}) []interface{} { + c.t.Helper() + vArgs := make([]reflect.Value, len(args)) + ft := v.Type() + if c.methodType.NumIn() != ft.NumIn() { + c.t.Fatalf("wrong number of arguments in DoAndReturn func for %T.%v: got %d, want %d [%s]", + c.receiver, c.method, ft.NumIn(), c.methodType.NumIn(), c.origin) + return nil + } + for i := 0; i < len(args); i++ { + if args[i] != nil { + vArgs[i] = reflect.ValueOf(args[i]) + } else { + // Use the zero value for the arg. + vArgs[i] = reflect.Zero(ft.In(i)) + } + } + vRets := v.Call(vArgs) + rets := make([]interface{}, len(vRets)) + for i, ret := range vRets { + rets[i] = ret.Interface() + } + return rets + }) + return c +} + +// Do declares the action to run when the call is matched. The function's +// return values are ignored to retain backward compatibility. To use the +// return values call DoAndReturn. +// It takes an interface{} argument to support n-arity functions. +func (c *Call) Do(f interface{}) *Call { + // TODO: Check arity and types here, rather than dying badly elsewhere. + v := reflect.ValueOf(f) + + c.addAction(func(args []interface{}) []interface{} { + c.t.Helper() + if c.methodType.NumIn() != v.Type().NumIn() { + c.t.Fatalf("wrong number of arguments in Do func for %T.%v: got %d, want %d [%s]", + c.receiver, c.method, v.Type().NumIn(), c.methodType.NumIn(), c.origin) + return nil + } + vArgs := make([]reflect.Value, len(args)) + ft := v.Type() + for i := 0; i < len(args); i++ { + if args[i] != nil { + vArgs[i] = reflect.ValueOf(args[i]) + } else { + // Use the zero value for the arg. + vArgs[i] = reflect.Zero(ft.In(i)) + } + } + v.Call(vArgs) + return nil + }) + return c +} + +// Return declares the values to be returned by the mocked function call. +func (c *Call) Return(rets ...interface{}) *Call { + c.t.Helper() + + mt := c.methodType + if len(rets) != mt.NumOut() { + c.t.Fatalf("wrong number of arguments to Return for %T.%v: got %d, want %d [%s]", + c.receiver, c.method, len(rets), mt.NumOut(), c.origin) + } + for i, ret := range rets { + if got, want := reflect.TypeOf(ret), mt.Out(i); got == want { + // Identical types; nothing to do. + } else if got == nil { + // Nil needs special handling. + switch want.Kind() { + case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: + // ok + default: + c.t.Fatalf("argument %d to Return for %T.%v is nil, but %v is not nillable [%s]", + i, c.receiver, c.method, want, c.origin) + } + } else if got.AssignableTo(want) { + // Assignable type relation. Make the assignment now so that the generated code + // can return the values with a type assertion. + v := reflect.New(want).Elem() + v.Set(reflect.ValueOf(ret)) + rets[i] = v.Interface() + } else { + c.t.Fatalf("wrong type of argument %d to Return for %T.%v: %v is not assignable to %v [%s]", + i, c.receiver, c.method, got, want, c.origin) + } + } + + c.addAction(func([]interface{}) []interface{} { + return rets + }) + + return c +} + +// Times declares the exact number of times a function call is expected to be executed. +func (c *Call) Times(n int) *Call { + c.minCalls, c.maxCalls = n, n + return c +} + +// SetArg declares an action that will set the nth argument's value, +// indirected through a pointer. Or, in the case of a slice, SetArg +// will copy value's elements into the nth argument. +func (c *Call) SetArg(n int, value interface{}) *Call { + c.t.Helper() + + mt := c.methodType + // TODO: This will break on variadic methods. + // We will need to check those at invocation time. + if n < 0 || n >= mt.NumIn() { + c.t.Fatalf("SetArg(%d, ...) called for a method with %d args [%s]", + n, mt.NumIn(), c.origin) + } + // Permit setting argument through an interface. + // In the interface case, we don't (nay, can't) check the type here. + at := mt.In(n) + switch at.Kind() { + case reflect.Ptr: + dt := at.Elem() + if vt := reflect.TypeOf(value); !vt.AssignableTo(dt) { + c.t.Fatalf("SetArg(%d, ...) argument is a %v, not assignable to %v [%s]", + n, vt, dt, c.origin) + } + case reflect.Interface: + // nothing to do + case reflect.Slice: + // nothing to do + default: + c.t.Fatalf("SetArg(%d, ...) referring to argument of non-pointer non-interface non-slice type %v [%s]", + n, at, c.origin) + } + + c.addAction(func(args []interface{}) []interface{} { + v := reflect.ValueOf(value) + switch reflect.TypeOf(args[n]).Kind() { + case reflect.Slice: + setSlice(args[n], v) + default: + reflect.ValueOf(args[n]).Elem().Set(v) + } + return nil + }) + return c +} + +// isPreReq returns true if other is a direct or indirect prerequisite to c. +func (c *Call) isPreReq(other *Call) bool { + for _, preReq := range c.preReqs { + if other == preReq || preReq.isPreReq(other) { + return true + } + } + return false +} + +// After declares that the call may only match after preReq has been exhausted. +func (c *Call) After(preReq *Call) *Call { + c.t.Helper() + + if c == preReq { + c.t.Fatalf("A call isn't allowed to be its own prerequisite") + } + if preReq.isPreReq(c) { + c.t.Fatalf("Loop in call order: %v is a prerequisite to %v (possibly indirectly).", c, preReq) + } + + c.preReqs = append(c.preReqs, preReq) + return c +} + +// Returns true if the minimum number of calls have been made. +func (c *Call) satisfied() bool { + return c.numCalls >= c.minCalls +} + +// Returns true if the maximum number of calls have been made. +func (c *Call) exhausted() bool { + return c.numCalls >= c.maxCalls +} + +func (c *Call) String() string { + args := make([]string, len(c.args)) + for i, arg := range c.args { + args[i] = arg.String() + } + arguments := strings.Join(args, ", ") + return fmt.Sprintf("%T.%v(%s) %s", c.receiver, c.method, arguments, c.origin) +} + +// Tests if the given call matches the expected call. +// If yes, returns nil. If no, returns error with message explaining why it does not match. +func (c *Call) matches(args []interface{}) error { + if !c.methodType.IsVariadic() { + if len(args) != len(c.args) { + return fmt.Errorf("expected call at %s has the wrong number of arguments. Got: %d, want: %d", + c.origin, len(args), len(c.args)) + } + + for i, m := range c.args { + if !m.Matches(args[i]) { + return fmt.Errorf( + "expected call at %s doesn't match the argument at index %d.\nGot: %v\nWant: %v", + c.origin, i, formatGottenArg(m, args[i]), m, + ) + } + } + } else { + if len(c.args) < c.methodType.NumIn()-1 { + return fmt.Errorf("expected call at %s has the wrong number of matchers. Got: %d, want: %d", + c.origin, len(c.args), c.methodType.NumIn()-1) + } + if len(c.args) != c.methodType.NumIn() && len(args) != len(c.args) { + return fmt.Errorf("expected call at %s has the wrong number of arguments. Got: %d, want: %d", + c.origin, len(args), len(c.args)) + } + if len(args) < len(c.args)-1 { + return fmt.Errorf("expected call at %s has the wrong number of arguments. Got: %d, want: greater than or equal to %d", + c.origin, len(args), len(c.args)-1) + } + + for i, m := range c.args { + if i < c.methodType.NumIn()-1 { + // Non-variadic args + if !m.Matches(args[i]) { + return fmt.Errorf("expected call at %s doesn't match the argument at index %s.\nGot: %v\nWant: %v", + c.origin, strconv.Itoa(i), formatGottenArg(m, args[i]), m) + } + continue + } + // The last arg has a possibility of a variadic argument, so let it branch + + // sample: Foo(a int, b int, c ...int) + if i < len(c.args) && i < len(args) { + if m.Matches(args[i]) { + // Got Foo(a, b, c) want Foo(matcherA, matcherB, gomock.Any()) + // Got Foo(a, b, c) want Foo(matcherA, matcherB, someSliceMatcher) + // Got Foo(a, b, c) want Foo(matcherA, matcherB, matcherC) + // Got Foo(a, b) want Foo(matcherA, matcherB) + // Got Foo(a, b, c, d) want Foo(matcherA, matcherB, matcherC, matcherD) + continue + } + } + + // The number of actual args don't match the number of matchers, + // or the last matcher is a slice and the last arg is not. + // If this function still matches it is because the last matcher + // matches all the remaining arguments or the lack of any. + // Convert the remaining arguments, if any, into a slice of the + // expected type. + vArgsType := c.methodType.In(c.methodType.NumIn() - 1) + vArgs := reflect.MakeSlice(vArgsType, 0, len(args)-i) + for _, arg := range args[i:] { + vArgs = reflect.Append(vArgs, reflect.ValueOf(arg)) + } + if m.Matches(vArgs.Interface()) { + // Got Foo(a, b, c, d, e) want Foo(matcherA, matcherB, gomock.Any()) + // Got Foo(a, b, c, d, e) want Foo(matcherA, matcherB, someSliceMatcher) + // Got Foo(a, b) want Foo(matcherA, matcherB, gomock.Any()) + // Got Foo(a, b) want Foo(matcherA, matcherB, someEmptySliceMatcher) + break + } + // Wrong number of matchers or not match. Fail. + // Got Foo(a, b) want Foo(matcherA, matcherB, matcherC, matcherD) + // Got Foo(a, b, c) want Foo(matcherA, matcherB, matcherC, matcherD) + // Got Foo(a, b, c, d) want Foo(matcherA, matcherB, matcherC, matcherD, matcherE) + // Got Foo(a, b, c, d, e) want Foo(matcherA, matcherB, matcherC, matcherD) + // Got Foo(a, b, c) want Foo(matcherA, matcherB) + + return fmt.Errorf("expected call at %s doesn't match the argument at index %s.\nGot: %v\nWant: %v", + c.origin, strconv.Itoa(i), formatGottenArg(m, args[i:]), c.args[i]) + } + } + + // Check that all prerequisite calls have been satisfied. + for _, preReqCall := range c.preReqs { + if !preReqCall.satisfied() { + return fmt.Errorf("expected call at %s doesn't have a prerequisite call satisfied:\n%v\nshould be called before:\n%v", + c.origin, preReqCall, c) + } + } + + // Check that the call is not exhausted. + if c.exhausted() { + return fmt.Errorf("expected call at %s has already been called the max number of times", c.origin) + } + + return nil +} + +// dropPrereqs tells the expected Call to not re-check prerequisite calls any +// longer, and to return its current set. +func (c *Call) dropPrereqs() (preReqs []*Call) { + preReqs = c.preReqs + c.preReqs = nil + return +} + +func (c *Call) call() []func([]interface{}) []interface{} { + c.numCalls++ + return c.actions +} + +// InOrder declares that the given calls should occur in order. +func InOrder(calls ...*Call) { + for i := 1; i < len(calls); i++ { + calls[i].After(calls[i-1]) + } +} + +func setSlice(arg interface{}, v reflect.Value) { + va := reflect.ValueOf(arg) + for i := 0; i < v.Len(); i++ { + va.Index(i).Set(v.Index(i)) + } +} + +func (c *Call) addAction(action func([]interface{}) []interface{}) { + c.actions = append(c.actions, action) +} + +func formatGottenArg(m Matcher, arg interface{}) string { + got := fmt.Sprintf("%v (%T)", arg, arg) + if gs, ok := m.(GotFormatter); ok { + got = gs.Got(arg) + } + return got +} diff --git a/vendor/github.com/golang/mock/gomock/callset.go b/vendor/github.com/golang/mock/gomock/callset.go new file mode 100644 index 000000000..49dba787a --- /dev/null +++ b/vendor/github.com/golang/mock/gomock/callset.go @@ -0,0 +1,113 @@ +// Copyright 2011 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gomock + +import ( + "bytes" + "errors" + "fmt" +) + +// callSet represents a set of expected calls, indexed by receiver and method +// name. +type callSet struct { + // Calls that are still expected. + expected map[callSetKey][]*Call + // Calls that have been exhausted. + exhausted map[callSetKey][]*Call +} + +// callSetKey is the key in the maps in callSet +type callSetKey struct { + receiver interface{} + fname string +} + +func newCallSet() *callSet { + return &callSet{make(map[callSetKey][]*Call), make(map[callSetKey][]*Call)} +} + +// Add adds a new expected call. +func (cs callSet) Add(call *Call) { + key := callSetKey{call.receiver, call.method} + m := cs.expected + if call.exhausted() { + m = cs.exhausted + } + m[key] = append(m[key], call) +} + +// Remove removes an expected call. +func (cs callSet) Remove(call *Call) { + key := callSetKey{call.receiver, call.method} + calls := cs.expected[key] + for i, c := range calls { + if c == call { + // maintain order for remaining calls + cs.expected[key] = append(calls[:i], calls[i+1:]...) + cs.exhausted[key] = append(cs.exhausted[key], call) + break + } + } +} + +// FindMatch searches for a matching call. Returns error with explanation message if no call matched. +func (cs callSet) FindMatch(receiver interface{}, method string, args []interface{}) (*Call, error) { + key := callSetKey{receiver, method} + + // Search through the expected calls. + expected := cs.expected[key] + var callsErrors bytes.Buffer + for _, call := range expected { + err := call.matches(args) + if err != nil { + _, _ = fmt.Fprintf(&callsErrors, "\n%v", err) + } else { + return call, nil + } + } + + // If we haven't found a match then search through the exhausted calls so we + // get useful error messages. + exhausted := cs.exhausted[key] + for _, call := range exhausted { + if err := call.matches(args); err != nil { + _, _ = fmt.Fprintf(&callsErrors, "\n%v", err) + continue + } + _, _ = fmt.Fprintf( + &callsErrors, "all expected calls for method %q have been exhausted", method, + ) + } + + if len(expected)+len(exhausted) == 0 { + _, _ = fmt.Fprintf(&callsErrors, "there are no expected calls of the method %q for that receiver", method) + } + + return nil, errors.New(callsErrors.String()) +} + +// Failures returns the calls that are not satisfied. +func (cs callSet) Failures() []*Call { + failures := make([]*Call, 0, len(cs.expected)) + for _, calls := range cs.expected { + for _, call := range calls { + if !call.satisfied() { + failures = append(failures, call) + } + } + } + return failures +} diff --git a/vendor/github.com/golang/mock/gomock/controller.go b/vendor/github.com/golang/mock/gomock/controller.go new file mode 100644 index 000000000..f054200d5 --- /dev/null +++ b/vendor/github.com/golang/mock/gomock/controller.go @@ -0,0 +1,336 @@ +// Copyright 2010 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package gomock is a mock framework for Go. +// +// Standard usage: +// (1) Define an interface that you wish to mock. +// type MyInterface interface { +// SomeMethod(x int64, y string) +// } +// (2) Use mockgen to generate a mock from the interface. +// (3) Use the mock in a test: +// func TestMyThing(t *testing.T) { +// mockCtrl := gomock.NewController(t) +// defer mockCtrl.Finish() +// +// mockObj := something.NewMockMyInterface(mockCtrl) +// mockObj.EXPECT().SomeMethod(4, "blah") +// // pass mockObj to a real object and play with it. +// } +// +// By default, expected calls are not enforced to run in any particular order. +// Call order dependency can be enforced by use of InOrder and/or Call.After. +// Call.After can create more varied call order dependencies, but InOrder is +// often more convenient. +// +// The following examples create equivalent call order dependencies. +// +// Example of using Call.After to chain expected call order: +// +// firstCall := mockObj.EXPECT().SomeMethod(1, "first") +// secondCall := mockObj.EXPECT().SomeMethod(2, "second").After(firstCall) +// mockObj.EXPECT().SomeMethod(3, "third").After(secondCall) +// +// Example of using InOrder to declare expected call order: +// +// gomock.InOrder( +// mockObj.EXPECT().SomeMethod(1, "first"), +// mockObj.EXPECT().SomeMethod(2, "second"), +// mockObj.EXPECT().SomeMethod(3, "third"), +// ) +package gomock + +import ( + "context" + "fmt" + "reflect" + "runtime" + "sync" +) + +// A TestReporter is something that can be used to report test failures. It +// is satisfied by the standard library's *testing.T. +type TestReporter interface { + Errorf(format string, args ...interface{}) + Fatalf(format string, args ...interface{}) +} + +// TestHelper is a TestReporter that has the Helper method. It is satisfied +// by the standard library's *testing.T. +type TestHelper interface { + TestReporter + Helper() +} + +// cleanuper is used to check if TestHelper also has the `Cleanup` method. A +// common pattern is to pass in a `*testing.T` to +// `NewController(t TestReporter)`. In Go 1.14+, `*testing.T` has a cleanup +// method. This can be utilized to call `Finish()` so the caller of this library +// does not have to. +type cleanuper interface { + Cleanup(func()) +} + +// A Controller represents the top-level control of a mock ecosystem. It +// defines the scope and lifetime of mock objects, as well as their +// expectations. It is safe to call Controller's methods from multiple +// goroutines. Each test should create a new Controller and invoke Finish via +// defer. +// +// func TestFoo(t *testing.T) { +// ctrl := gomock.NewController(t) +// defer ctrl.Finish() +// // .. +// } +// +// func TestBar(t *testing.T) { +// t.Run("Sub-Test-1", st) { +// ctrl := gomock.NewController(st) +// defer ctrl.Finish() +// // .. +// }) +// t.Run("Sub-Test-2", st) { +// ctrl := gomock.NewController(st) +// defer ctrl.Finish() +// // .. +// }) +// }) +type Controller struct { + // T should only be called within a generated mock. It is not intended to + // be used in user code and may be changed in future versions. T is the + // TestReporter passed in when creating the Controller via NewController. + // If the TestReporter does not implement a TestHelper it will be wrapped + // with a nopTestHelper. + T TestHelper + mu sync.Mutex + expectedCalls *callSet + finished bool +} + +// NewController returns a new Controller. It is the preferred way to create a +// Controller. +// +// New in go1.14+, if you are passing a *testing.T into this function you no +// longer need to call ctrl.Finish() in your test methods. +func NewController(t TestReporter) *Controller { + h, ok := t.(TestHelper) + if !ok { + h = &nopTestHelper{t} + } + ctrl := &Controller{ + T: h, + expectedCalls: newCallSet(), + } + if c, ok := isCleanuper(ctrl.T); ok { + c.Cleanup(func() { + ctrl.T.Helper() + ctrl.finish(true, nil) + }) + } + + return ctrl +} + +type cancelReporter struct { + t TestHelper + cancel func() +} + +func (r *cancelReporter) Errorf(format string, args ...interface{}) { + r.t.Errorf(format, args...) +} +func (r *cancelReporter) Fatalf(format string, args ...interface{}) { + defer r.cancel() + r.t.Fatalf(format, args...) +} + +func (r *cancelReporter) Helper() { + r.t.Helper() +} + +// WithContext returns a new Controller and a Context, which is cancelled on any +// fatal failure. +func WithContext(ctx context.Context, t TestReporter) (*Controller, context.Context) { + h, ok := t.(TestHelper) + if !ok { + h = &nopTestHelper{t: t} + } + + ctx, cancel := context.WithCancel(ctx) + return NewController(&cancelReporter{t: h, cancel: cancel}), ctx +} + +type nopTestHelper struct { + t TestReporter +} + +func (h *nopTestHelper) Errorf(format string, args ...interface{}) { + h.t.Errorf(format, args...) +} +func (h *nopTestHelper) Fatalf(format string, args ...interface{}) { + h.t.Fatalf(format, args...) +} + +func (h nopTestHelper) Helper() {} + +// RecordCall is called by a mock. It should not be called by user code. +func (ctrl *Controller) RecordCall(receiver interface{}, method string, args ...interface{}) *Call { + ctrl.T.Helper() + + recv := reflect.ValueOf(receiver) + for i := 0; i < recv.Type().NumMethod(); i++ { + if recv.Type().Method(i).Name == method { + return ctrl.RecordCallWithMethodType(receiver, method, recv.Method(i).Type(), args...) + } + } + ctrl.T.Fatalf("gomock: failed finding method %s on %T", method, receiver) + panic("unreachable") +} + +// RecordCallWithMethodType is called by a mock. It should not be called by user code. +func (ctrl *Controller) RecordCallWithMethodType(receiver interface{}, method string, methodType reflect.Type, args ...interface{}) *Call { + ctrl.T.Helper() + + call := newCall(ctrl.T, receiver, method, methodType, args...) + + ctrl.mu.Lock() + defer ctrl.mu.Unlock() + ctrl.expectedCalls.Add(call) + + return call +} + +// Call is called by a mock. It should not be called by user code. +func (ctrl *Controller) Call(receiver interface{}, method string, args ...interface{}) []interface{} { + ctrl.T.Helper() + + // Nest this code so we can use defer to make sure the lock is released. + actions := func() []func([]interface{}) []interface{} { + ctrl.T.Helper() + ctrl.mu.Lock() + defer ctrl.mu.Unlock() + + expected, err := ctrl.expectedCalls.FindMatch(receiver, method, args) + if err != nil { + // callerInfo's skip should be updated if the number of calls between the user's test + // and this line changes, i.e. this code is wrapped in another anonymous function. + // 0 is us, 1 is controller.Call(), 2 is the generated mock, and 3 is the user's test. + origin := callerInfo(3) + ctrl.T.Fatalf("Unexpected call to %T.%v(%v) at %s because: %s", receiver, method, args, origin, err) + } + + // Two things happen here: + // * the matching call no longer needs to check prerequite calls, + // * and the prerequite calls are no longer expected, so remove them. + preReqCalls := expected.dropPrereqs() + for _, preReqCall := range preReqCalls { + ctrl.expectedCalls.Remove(preReqCall) + } + + actions := expected.call() + if expected.exhausted() { + ctrl.expectedCalls.Remove(expected) + } + return actions + }() + + var rets []interface{} + for _, action := range actions { + if r := action(args); r != nil { + rets = r + } + } + + return rets +} + +// Finish checks to see if all the methods that were expected to be called +// were called. It should be invoked for each Controller. It is not idempotent +// and therefore can only be invoked once. +// +// New in go1.14+, if you are passing a *testing.T into NewController function you no +// longer need to call ctrl.Finish() in your test methods. +func (ctrl *Controller) Finish() { + // If we're currently panicking, probably because this is a deferred call. + // This must be recovered in the deferred function. + err := recover() + ctrl.finish(false, err) +} + +func (ctrl *Controller) finish(cleanup bool, panicErr interface{}) { + ctrl.T.Helper() + + ctrl.mu.Lock() + defer ctrl.mu.Unlock() + + if ctrl.finished { + if _, ok := isCleanuper(ctrl.T); !ok { + ctrl.T.Fatalf("Controller.Finish was called more than once. It has to be called exactly once.") + } + return + } + ctrl.finished = true + + // Short-circuit, pass through the panic. + if panicErr != nil { + panic(panicErr) + } + + // Check that all remaining expected calls are satisfied. + failures := ctrl.expectedCalls.Failures() + for _, call := range failures { + ctrl.T.Errorf("missing call(s) to %v", call) + } + if len(failures) != 0 { + if !cleanup { + ctrl.T.Fatalf("aborting test due to missing call(s)") + return + } + ctrl.T.Errorf("aborting test due to missing call(s)") + } +} + +// callerInfo returns the file:line of the call site. skip is the number +// of stack frames to skip when reporting. 0 is callerInfo's call site. +func callerInfo(skip int) string { + if _, file, line, ok := runtime.Caller(skip + 1); ok { + return fmt.Sprintf("%s:%d", file, line) + } + return "unknown file" +} + +// isCleanuper checks it if t's base TestReporter has a Cleanup method. +func isCleanuper(t TestReporter) (cleanuper, bool) { + tr := unwrapTestReporter(t) + c, ok := tr.(cleanuper) + return c, ok +} + +// unwrapTestReporter unwraps TestReporter to the base implementation. +func unwrapTestReporter(t TestReporter) TestReporter { + tr := t + switch nt := t.(type) { + case *cancelReporter: + tr = nt.t + if h, check := tr.(*nopTestHelper); check { + tr = h.t + } + case *nopTestHelper: + tr = nt.t + default: + // not wrapped + } + return tr +} diff --git a/vendor/github.com/golang/mock/gomock/matchers.go b/vendor/github.com/golang/mock/gomock/matchers.go new file mode 100644 index 000000000..2822fb2c8 --- /dev/null +++ b/vendor/github.com/golang/mock/gomock/matchers.go @@ -0,0 +1,341 @@ +// Copyright 2010 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gomock + +import ( + "fmt" + "reflect" + "strings" +) + +// A Matcher is a representation of a class of values. +// It is used to represent the valid or expected arguments to a mocked method. +type Matcher interface { + // Matches returns whether x is a match. + Matches(x interface{}) bool + + // String describes what the matcher matches. + String() string +} + +// WantFormatter modifies the given Matcher's String() method to the given +// Stringer. This allows for control on how the "Want" is formatted when +// printing . +func WantFormatter(s fmt.Stringer, m Matcher) Matcher { + type matcher interface { + Matches(x interface{}) bool + } + + return struct { + matcher + fmt.Stringer + }{ + matcher: m, + Stringer: s, + } +} + +// StringerFunc type is an adapter to allow the use of ordinary functions as +// a Stringer. If f is a function with the appropriate signature, +// StringerFunc(f) is a Stringer that calls f. +type StringerFunc func() string + +// String implements fmt.Stringer. +func (f StringerFunc) String() string { + return f() +} + +// GotFormatter is used to better print failure messages. If a matcher +// implements GotFormatter, it will use the result from Got when printing +// the failure message. +type GotFormatter interface { + // Got is invoked with the received value. The result is used when + // printing the failure message. + Got(got interface{}) string +} + +// GotFormatterFunc type is an adapter to allow the use of ordinary +// functions as a GotFormatter. If f is a function with the appropriate +// signature, GotFormatterFunc(f) is a GotFormatter that calls f. +type GotFormatterFunc func(got interface{}) string + +// Got implements GotFormatter. +func (f GotFormatterFunc) Got(got interface{}) string { + return f(got) +} + +// GotFormatterAdapter attaches a GotFormatter to a Matcher. +func GotFormatterAdapter(s GotFormatter, m Matcher) Matcher { + return struct { + GotFormatter + Matcher + }{ + GotFormatter: s, + Matcher: m, + } +} + +type anyMatcher struct{} + +func (anyMatcher) Matches(interface{}) bool { + return true +} + +func (anyMatcher) String() string { + return "is anything" +} + +type eqMatcher struct { + x interface{} +} + +func (e eqMatcher) Matches(x interface{}) bool { + // In case, some value is nil + if e.x == nil || x == nil { + return reflect.DeepEqual(e.x, x) + } + + // Check if types assignable and convert them to common type + x1Val := reflect.ValueOf(e.x) + x2Val := reflect.ValueOf(x) + + if x1Val.Type().AssignableTo(x2Val.Type()) { + x1ValConverted := x1Val.Convert(x2Val.Type()) + return reflect.DeepEqual(x1ValConverted.Interface(), x2Val.Interface()) + } + + return false +} + +func (e eqMatcher) String() string { + return fmt.Sprintf("is equal to %v (%T)", e.x, e.x) +} + +type nilMatcher struct{} + +func (nilMatcher) Matches(x interface{}) bool { + if x == nil { + return true + } + + v := reflect.ValueOf(x) + switch v.Kind() { + case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, + reflect.Ptr, reflect.Slice: + return v.IsNil() + } + + return false +} + +func (nilMatcher) String() string { + return "is nil" +} + +type notMatcher struct { + m Matcher +} + +func (n notMatcher) Matches(x interface{}) bool { + return !n.m.Matches(x) +} + +func (n notMatcher) String() string { + return "not(" + n.m.String() + ")" +} + +type assignableToTypeOfMatcher struct { + targetType reflect.Type +} + +func (m assignableToTypeOfMatcher) Matches(x interface{}) bool { + return reflect.TypeOf(x).AssignableTo(m.targetType) +} + +func (m assignableToTypeOfMatcher) String() string { + return "is assignable to " + m.targetType.Name() +} + +type allMatcher struct { + matchers []Matcher +} + +func (am allMatcher) Matches(x interface{}) bool { + for _, m := range am.matchers { + if !m.Matches(x) { + return false + } + } + return true +} + +func (am allMatcher) String() string { + ss := make([]string, 0, len(am.matchers)) + for _, matcher := range am.matchers { + ss = append(ss, matcher.String()) + } + return strings.Join(ss, "; ") +} + +type lenMatcher struct { + i int +} + +func (m lenMatcher) Matches(x interface{}) bool { + v := reflect.ValueOf(x) + switch v.Kind() { + case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == m.i + default: + return false + } +} + +func (m lenMatcher) String() string { + return fmt.Sprintf("has length %d", m.i) +} + +type inAnyOrderMatcher struct { + x interface{} +} + +func (m inAnyOrderMatcher) Matches(x interface{}) bool { + given, ok := m.prepareValue(x) + if !ok { + return false + } + wanted, ok := m.prepareValue(m.x) + if !ok { + return false + } + + if given.Len() != wanted.Len() { + return false + } + + usedFromGiven := make([]bool, given.Len()) + foundFromWanted := make([]bool, wanted.Len()) + for i := 0; i < wanted.Len(); i++ { + wantedMatcher := Eq(wanted.Index(i).Interface()) + for j := 0; j < given.Len(); j++ { + if usedFromGiven[j] { + continue + } + if wantedMatcher.Matches(given.Index(j).Interface()) { + foundFromWanted[i] = true + usedFromGiven[j] = true + break + } + } + } + + missingFromWanted := 0 + for _, found := range foundFromWanted { + if !found { + missingFromWanted++ + } + } + extraInGiven := 0 + for _, used := range usedFromGiven { + if !used { + extraInGiven++ + } + } + + return extraInGiven == 0 && missingFromWanted == 0 +} + +func (m inAnyOrderMatcher) prepareValue(x interface{}) (reflect.Value, bool) { + xValue := reflect.ValueOf(x) + switch xValue.Kind() { + case reflect.Slice, reflect.Array: + return xValue, true + default: + return reflect.Value{}, false + } +} + +func (m inAnyOrderMatcher) String() string { + return fmt.Sprintf("has the same elements as %v", m.x) +} + +// Constructors + +// All returns a composite Matcher that returns true if and only all of the +// matchers return true. +func All(ms ...Matcher) Matcher { return allMatcher{ms} } + +// Any returns a matcher that always matches. +func Any() Matcher { return anyMatcher{} } + +// Eq returns a matcher that matches on equality. +// +// Example usage: +// Eq(5).Matches(5) // returns true +// Eq(5).Matches(4) // returns false +func Eq(x interface{}) Matcher { return eqMatcher{x} } + +// Len returns a matcher that matches on length. This matcher returns false if +// is compared to a type that is not an array, chan, map, slice, or string. +func Len(i int) Matcher { + return lenMatcher{i} +} + +// Nil returns a matcher that matches if the received value is nil. +// +// Example usage: +// var x *bytes.Buffer +// Nil().Matches(x) // returns true +// x = &bytes.Buffer{} +// Nil().Matches(x) // returns false +func Nil() Matcher { return nilMatcher{} } + +// Not reverses the results of its given child matcher. +// +// Example usage: +// Not(Eq(5)).Matches(4) // returns true +// Not(Eq(5)).Matches(5) // returns false +func Not(x interface{}) Matcher { + if m, ok := x.(Matcher); ok { + return notMatcher{m} + } + return notMatcher{Eq(x)} +} + +// AssignableToTypeOf is a Matcher that matches if the parameter to the mock +// function is assignable to the type of the parameter to this function. +// +// Example usage: +// var s fmt.Stringer = &bytes.Buffer{} +// AssignableToTypeOf(s).Matches(time.Second) // returns true +// AssignableToTypeOf(s).Matches(99) // returns false +// +// var ctx = reflect.TypeOf((*context.Context)(nil)).Elem() +// AssignableToTypeOf(ctx).Matches(context.Background()) // returns true +func AssignableToTypeOf(x interface{}) Matcher { + if xt, ok := x.(reflect.Type); ok { + return assignableToTypeOfMatcher{xt} + } + return assignableToTypeOfMatcher{reflect.TypeOf(x)} +} + +// InAnyOrder is a Matcher that returns true for collections of the same elements ignoring the order. +// +// Example usage: +// InAnyOrder([]int{1, 2, 3}).Matches([]int{1, 3, 2}) // returns true +// InAnyOrder([]int{1, 2, 3}).Matches([]int{1, 2}) // returns false +func InAnyOrder(x interface{}) Matcher { + return inAnyOrderMatcher{x} +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 94756c4e1..6a18a3744 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -86,6 +86,9 @@ github.com/go-task/slim-sprig/v3 # github.com/gogo/protobuf v1.3.2 ## explicit; go 1.15 github.com/gogo/protobuf/proto +# github.com/golang/mock v1.6.0 +## explicit; go 1.11 +github.com/golang/mock/gomock # github.com/google/go-cmp v0.7.0 ## explicit; go 1.21 github.com/google/go-cmp/cmp From 54963fe7715eb3272cc5fbf7487e48771c1b9009 Mon Sep 17 00:00:00 2001 From: Kiril Keranov Date: Wed, 25 Feb 2026 17:15:22 +0200 Subject: [PATCH 4/8] Add comments and adjust checks --- src/java/supply/supply_test.go | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/src/java/supply/supply_test.go b/src/java/supply/supply_test.go index 421340bee..1a22d97c6 100644 --- a/src/java/supply/supply_test.go +++ b/src/java/supply/supply_test.go @@ -139,6 +139,7 @@ dependencies: [] webInfDir := filepath.Join(buildDir, "WEB-INF") Expect(os.MkdirAll(webInfDir, 0755)).To(Succeed()) + // Create tomcat installdirs, dependencies and mocks used during supply phase mockManifest.EXPECT().AllDependencyVersions("tomcat").Return([]string{"10.1.50"}) tomcatInstallDir := filepath.Join(depsDir, depsIdx, "tomcat") Expect(os.MkdirAll(filepath.Join(tomcatInstallDir), 0755)).To(Succeed()) @@ -155,6 +156,7 @@ dependencies: [] tomcatLoggingSupportInstallDir := filepath.Join(depsDir, depsIdx, "tomcat", "bin") Expect(os.MkdirAll(filepath.Join(tomcatLoggingSupportInstallDir), 0755)).To(Succeed()) + // Create mocks for the tomcat dependencies downloaded during supply depTomcatLifeCycleSupport := libbuildpack.Dependency{Name: "tomcat-lifecycle-support", Version: "3.4.0"} mockManifest.EXPECT().DefaultVersion("tomcat-lifecycle-support").Return(depTomcatLifeCycleSupport, nil) depTomcatAccessLoggingSupport := libbuildpack.Dependency{Name: "tomcat-access-logging-support", Version: "3.4.0"} @@ -170,17 +172,9 @@ dependencies: [] }) It("Supply passes successfully", func() { - depDir := stager.DepDir() err := supply.Run(supplier) Expect(err).To(BeNil()) - Expect(depDir).To(ContainSubstring(depsDir)) - - Expect(supplier).NotTo(BeNil()) - Expect(supplier.Stager).NotTo(BeNil()) - Expect(supplier.Manifest).NotTo(BeNil()) - Expect(supplier.Log).NotTo(BeNil()) - Expect(supplier.Command).NotTo(BeNil()) }) }) @@ -191,9 +185,11 @@ dependencies: [] Expect(os.MkdirAll(bootInfDir, 0755)).To(Succeed()) Expect(os.MkdirAll(filepath.Join(buildDir, "META-INF"), 0755)).To(Succeed()) + // Create META-INF/MANIFEST.MF with corresponding content of a Spring Boot app manifestFile := filepath.Join(buildDir, "META-INF", "MANIFEST.MF") Expect(os.WriteFile(manifestFile, []byte("Spring-Boot-Version: 3.x.x"), 0644)).To(Succeed()) + //Create install dir and mock for the java cf env spring boot related dependency javaCfEnvInstallDir := filepath.Join(depsDir, depsIdx, "java_cf_env") Expect(os.MkdirAll(filepath.Join(javaCfEnvInstallDir), 0755)).To(Succeed()) @@ -203,17 +199,9 @@ dependencies: [] }) It("Supply passes successfully", func() { - depDir := stager.DepDir() err := supply.Run(supplier) Expect(err).To(BeNil()) - Expect(depDir).To(ContainSubstring(depsDir)) - - Expect(supplier).NotTo(BeNil()) - Expect(supplier.Stager).NotTo(BeNil()) - Expect(supplier.Manifest).NotTo(BeNil()) - Expect(supplier.Log).NotTo(BeNil()) - Expect(supplier.Command).NotTo(BeNil()) }) }) @@ -223,6 +211,7 @@ dependencies: [] groovyFile := filepath.Join(buildDir, "app.groovy") Expect(os.WriteFile(groovyFile, []byte("println 'hello'"), 0644)).To(Succeed()) + //Create groovy install dir and dependency mock groovyInstallDir := filepath.Join(depsDir, depsIdx, "groovy") err := os.MkdirAll(filepath.Join(groovyInstallDir), 0755) Expect(err).To(BeNil()) @@ -233,11 +222,9 @@ dependencies: [] }) It("Supply passes successfully", func() { - depDir := stager.DepDir() err := supply.Run(supplier) Expect(err).To(BeNil()) - Expect(depDir).To(ContainSubstring(depsDir)) }) }) }) From 480f7282647123708836016f59b24af9c387c477 Mon Sep 17 00:00:00 2001 From: Kiril Keranov Date: Wed, 25 Feb 2026 17:44:49 +0200 Subject: [PATCH 5/8] Fix metrics exported obsolete installer --- src/java/frameworks/cf_metrics_exporter.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/java/frameworks/cf_metrics_exporter.go b/src/java/frameworks/cf_metrics_exporter.go index 0f5b73cf4..7bec7271e 100644 --- a/src/java/frameworks/cf_metrics_exporter.go +++ b/src/java/frameworks/cf_metrics_exporter.go @@ -21,13 +21,11 @@ type Installer interface { } type CfMetricsExporterFramework struct { - context *common.Context - installer Installer + context *common.Context } func NewCfMetricsExporterFramework(ctx *common.Context) *CfMetricsExporterFramework { - installer := ctx.Installer - return &CfMetricsExporterFramework{context: ctx, installer: installer} + return &CfMetricsExporterFramework{context: ctx} } func (f *CfMetricsExporterFramework) Detect() (string, error) { @@ -76,7 +74,7 @@ func (f *CfMetricsExporterFramework) Supply() error { // Download the JAR if not present if _, err := os.Stat(jarPath); os.IsNotExist(err) { - if err := f.installer.InstallDependency(dep, agentDir); err != nil { + if err := f.context.Installer.InstallDependency(dep, agentDir); err != nil { return fmt.Errorf("failed to download cf-metrics-exporter: %w", err) } if _, err := os.Stat(jarPath); err != nil { From e4cf47f040b5aae44931d460e0a7b2889d0f0944 Mon Sep 17 00:00:00 2001 From: Kiril Keranov Date: Thu, 26 Feb 2026 15:08:18 +0200 Subject: [PATCH 6/8] Fix/adjust hollow finalize tests --- src/java/finalize/finalize.go | 7 ++- src/java/finalize/finalize_test.go | 87 ++++++++++++++++++++++-------- src/java/supply/supply_test.go | 24 +++------ 3 files changed, 75 insertions(+), 43 deletions(-) diff --git a/src/java/finalize/finalize.go b/src/java/finalize/finalize.go index 63e87e039..2174ccfcc 100644 --- a/src/java/finalize/finalize.go +++ b/src/java/finalize/finalize.go @@ -36,9 +36,9 @@ type SupplyConfig struct { // NewFinalizer creates a Finalizer by reading the config.yml written by the supply phase. // This follows the pattern established by go-buildpack and dotnet-core-buildpack. -func NewFinalizer(stager *libbuildpack.Stager, manifest *libbuildpack.Manifest, - installer *libbuildpack.Installer, logger *libbuildpack.Logger, - command *libbuildpack.Command) (*Finalizer, error) { +func NewFinalizer(stager common.Stager, manifest common.Manifest, + installer common.Installer, logger *libbuildpack.Logger, + command common.Command) (*Finalizer, error) { raw := struct { Config SupplyConfig `yaml:"config"` @@ -87,7 +87,6 @@ func Run(f *Finalizer) error { f.Container = container f.Log.Info("Finalizing container: %s", f.ContainerName) - // Resolve JRE using the name stored by supply — no re-detection needed. jre, err := resolveJRE(ctx, f.JREName) if err != nil { diff --git a/src/java/finalize/finalize_test.go b/src/java/finalize/finalize_test.go index b254a4066..b13a192fe 100644 --- a/src/java/finalize/finalize_test.go +++ b/src/java/finalize/finalize_test.go @@ -1,6 +1,8 @@ package finalize_test import ( + "github.com/cloudfoundry/java-buildpack/src/internal/mocks" + "github.com/golang/mock/gomock" "os" "path/filepath" "time" @@ -13,14 +15,16 @@ import ( var _ = Describe("Finalize", func() { var ( - buildDir string - cacheDir string - depsDir string - depsIdx string - stager *libbuildpack.Stager - manifest *libbuildpack.Manifest - installer *libbuildpack.Installer - logger *libbuildpack.Logger + buildDir string + cacheDir string + depsDir string + depsIdx string + stager *libbuildpack.Stager + mockCtrl *gomock.Controller + mockManifest *mocks.MockManifest + mockInstaller *mocks.MockInstaller + finalizer *finalize.Finalizer + logger *libbuildpack.Logger ) BeforeEach(func() { @@ -53,42 +57,79 @@ dependencies: [] logger = libbuildpack.NewLogger(GinkgoWriter) - manifest, err = libbuildpack.NewManifest(buildpackDir, logger, time.Now()) - Expect(err).NotTo(HaveOccurred()) + mockCtrl = gomock.NewController(GinkgoT()) + mockManifest = mocks.NewMockManifest(mockCtrl) + mockInstaller = mocks.NewMockInstaller(mockCtrl) - installer = libbuildpack.NewInstaller(manifest) + manifest, err := libbuildpack.NewManifest(buildpackDir, logger, time.Now()) + Expect(err).NotTo(HaveOccurred()) stager = libbuildpack.NewStager([]string{buildDir, cacheDir, depsDir, depsIdx}, logger, manifest) + + finalizer = &finalize.Finalizer{ + Stager: stager, + Manifest: mockManifest, + Installer: mockInstaller, + Log: logger, + Command: &libbuildpack.Command{}, + } }) AfterEach(func() { + mockCtrl.Finish() + os.RemoveAll(buildDir) os.RemoveAll(cacheDir) os.RemoveAll(depsDir) }) - Describe("Container Lookup", func() { - Context("when a Spring Boot application is present", func() { + Describe("Various Container Finalize", func() { + Context("When a Spring Boot application is present", func() { BeforeEach(func() { + // Create a Spring Boot JAR with BOOT-INF bootInfDir := filepath.Join(buildDir, "BOOT-INF") Expect(os.MkdirAll(bootInfDir, 0755)).To(Succeed()) + Expect(os.MkdirAll(filepath.Join(buildDir, "META-INF"), 0755)).To(Succeed()) + + // Create META-INF/MANIFEST.MF with corresponding content of a Spring Boot app + manifestFile := filepath.Join(buildDir, "META-INF", "MANIFEST.MF") + Expect(os.WriteFile(manifestFile, []byte("Spring-Boot-Version: 3.x.x"), 0644)).To(Succeed()) + + finalizer.JREName = "OpenJDK" + finalizer.ContainerName = "Spring Boot" }) - It("has a valid stager pointing to the build directory", func() { - Expect(stager).NotTo(BeNil()) - Expect(stager.BuildDir()).To(Equal(buildDir)) + It("Finalize passes successfully", func() { + Expect(finalize.Run(finalizer)).To(Succeed()) }) }) - Context("when a Tomcat application is present", func() { + Context("When a Tomcat application is present", func() { BeforeEach(func() { webInfDir := filepath.Join(buildDir, "WEB-INF") Expect(os.MkdirAll(webInfDir, 0755)).To(Succeed()) + + finalizer.JREName = "OpenJDK" + finalizer.ContainerName = "Tomcat" + }) + + It("Finalize passes successfully", func() { + Expect(finalize.Run(finalizer)).To(Succeed()) + }) + }) + + Context("When a Groovy application is present", func() { + BeforeEach(func() { + // Create a .groovy file + groovyFile := filepath.Join(buildDir, "app.groovy") + Expect(os.WriteFile(groovyFile, []byte("println 'hello'"), 0644)).To(Succeed()) + + finalizer.JREName = "OpenJDK" + finalizer.ContainerName = "Groovy" }) - It("has a valid stager pointing to the build directory", func() { - Expect(stager).NotTo(BeNil()) - Expect(stager.BuildDir()).To(Equal(buildDir)) + It("Finalize passes successfully", func() { + Expect(finalize.Run(finalizer)).To(Succeed()) }) }) }) @@ -159,7 +200,7 @@ dependencies: [] Expect(err).NotTo(HaveOccurred()) // NewFinalizer must successfully read the config.yml written above - f, err := finalize.NewFinalizer(stager, manifest, installer, logger, &libbuildpack.Command{}) + f, err := finalize.NewFinalizer(stager, mockManifest, mockInstaller, logger, &libbuildpack.Command{}) Expect(err).NotTo(HaveOccurred()) Expect(f.ContainerName).To(Equal("spring-boot")) Expect(f.JREName).To(Equal("OpenJDK")) @@ -167,7 +208,7 @@ dependencies: [] It("NewFinalizer fails when config.yml is missing", func() { // No config.yml written — NewFinalizer must return an error - _, err := finalize.NewFinalizer(stager, manifest, installer, logger, &libbuildpack.Command{}) + _, err := finalize.NewFinalizer(stager, mockManifest, mockInstaller, logger, &libbuildpack.Command{}) Expect(err).To(HaveOccurred()) }) @@ -176,7 +217,7 @@ dependencies: [] err := stager.WriteConfigYml(map[string]string{}) Expect(err).NotTo(HaveOccurred()) - _, err = finalize.NewFinalizer(stager, manifest, installer, logger, &libbuildpack.Command{}) + _, err = finalize.NewFinalizer(stager, mockManifest, mockInstaller, logger, &libbuildpack.Command{}) Expect(err).To(HaveOccurred()) }) }) diff --git a/src/java/supply/supply_test.go b/src/java/supply/supply_test.go index 1a22d97c6..a46fad476 100644 --- a/src/java/supply/supply_test.go +++ b/src/java/supply/supply_test.go @@ -133,7 +133,7 @@ dependencies: [] mockInstaller.EXPECT().InstallDependency(depContainerSecProvider, cspInstallDir).Return(nil) }) - Context("when a Tomcat application is present", func() { + Context("When a Tomcat application is present", func() { BeforeEach(func() { // Create WEB-INF directory webInfDir := filepath.Join(buildDir, "WEB-INF") @@ -172,13 +172,11 @@ dependencies: [] }) It("Supply passes successfully", func() { - err := supply.Run(supplier) - - Expect(err).To(BeNil()) + Expect(supply.Run(supplier)).To(Succeed()) }) }) - Context("when a Spring-boot application is present", func() { + Context("When a Spring-boot application is present", func() { BeforeEach(func() { // Create a Spring Boot JAR with BOOT-INF bootInfDir := filepath.Join(buildDir, "BOOT-INF") @@ -199,13 +197,11 @@ dependencies: [] }) It("Supply passes successfully", func() { - err := supply.Run(supplier) - - Expect(err).To(BeNil()) + Expect(supply.Run(supplier)).To(Succeed()) }) }) - Context("when a Groovy application is present", func() { + Context("When a Groovy application is present", func() { BeforeEach(func() { // Create a .groovy file groovyFile := filepath.Join(buildDir, "app.groovy") @@ -213,8 +209,7 @@ dependencies: [] //Create groovy install dir and dependency mock groovyInstallDir := filepath.Join(depsDir, depsIdx, "groovy") - err := os.MkdirAll(filepath.Join(groovyInstallDir), 0755) - Expect(err).To(BeNil()) + Expect(os.MkdirAll(filepath.Join(groovyInstallDir), 0755)).To(Succeed()) depGroovy := libbuildpack.Dependency{Name: "groovy", Version: "4.0.29"} mockManifest.EXPECT().DefaultVersion("groovy").Return(depGroovy, nil) @@ -222,9 +217,7 @@ dependencies: [] }) It("Supply passes successfully", func() { - err := supply.Run(supplier) - - Expect(err).To(BeNil()) + Expect(supply.Run(supplier)).To(Succeed()) }) }) }) @@ -274,8 +267,7 @@ dependencies: [] }) It("handles empty config gracefully", func() { - err := stager.WriteConfigYml(nil) - Expect(err).NotTo(HaveOccurred()) + Expect(stager.WriteConfigYml(nil)).To(Succeed()) }) }) }) From c163c97ef2f90681522b09a3284fbac0b88027c7 Mon Sep 17 00:00:00 2001 From: Kiril Keranov Date: Fri, 27 Feb 2026 13:11:55 +0200 Subject: [PATCH 7/8] Add comments --- src/internal/mocks/mocks.go | 2 +- src/java/common/context.go | 2 ++ src/java/supply/supply_test.go | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/internal/mocks/mocks.go b/src/internal/mocks/mocks.go index 7174c7238..f1e04b6d9 100644 --- a/src/internal/mocks/mocks.go +++ b/src/internal/mocks/mocks.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: src/java/common/context.go +// Source: context.go // Package mocks is a generated GoMock package. package mocks diff --git a/src/java/common/context.go b/src/java/common/context.go index b9c8b2d3e..470636c3b 100644 --- a/src/java/common/context.go +++ b/src/java/common/context.go @@ -11,6 +11,8 @@ import ( "strings" ) +//go:generate mockgen -source=context.go --destination=../../internal/mocks/mocks.go --package=mocks + type Command interface { Execute(string, io.Writer, io.Writer, string, ...string) error } diff --git a/src/java/supply/supply_test.go b/src/java/supply/supply_test.go index a46fad476..3f7a9e32a 100644 --- a/src/java/supply/supply_test.go +++ b/src/java/supply/supply_test.go @@ -119,6 +119,7 @@ dependencies: [] mockInstaller.EXPECT().InstallDependency(depJVMKill, jvmkillInstallDir).Return(nil) mockInstaller.EXPECT().InstallDependency(depMemCalc, memCalcInstallDir).Return(nil) + // adjust mocks for the mandatory frameworks used during staging ccmInstallDir := filepath.Join(depsDir, depsIdx, "client_certificate_mapper") Expect(os.MkdirAll(filepath.Join(ccmInstallDir), 0755)).To(Succeed()) cspInstallDir := filepath.Join(depsDir, depsIdx, "container_security_provider") From fedea773683f601e9c48c5e4e39dd948ae0b7600 Mon Sep 17 00:00:00 2001 From: Kiril Keranov Date: Fri, 27 Feb 2026 16:32:06 +0200 Subject: [PATCH 8/8] Address review comments --- src/internal/mocks/mocks.go | 14 ++++++++++++++ src/java/common/context.go | 1 + src/java/frameworks/cf_metrics_exporter.go | 8 -------- src/java/supply/supply_test.go | 12 +++++++++++- 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/internal/mocks/mocks.go b/src/internal/mocks/mocks.go index f1e04b6d9..690d75040 100644 --- a/src/internal/mocks/mocks.go +++ b/src/internal/mocks/mocks.go @@ -91,6 +91,20 @@ func (mr *MockStagerMockRecorder) BuildDir() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildDir", reflect.TypeOf((*MockStager)(nil).BuildDir)) } +// CacheDir mocks base method. +func (m *MockStager) CacheDir() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CacheDir") + ret0, _ := ret[0].(string) + return ret0 +} + +// CacheDir indicates an expected call of CacheDir. +func (mr *MockStagerMockRecorder) CacheDir() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CacheDir", reflect.TypeOf((*MockStager)(nil).CacheDir)) +} + // DepDir mocks base method. func (m *MockStager) DepDir() string { m.ctrl.T.Helper() diff --git a/src/java/common/context.go b/src/java/common/context.go index 470636c3b..5e36fc157 100644 --- a/src/java/common/context.go +++ b/src/java/common/context.go @@ -22,6 +22,7 @@ type Stager interface { BuildDir() string DepDir() string DepsIdx() string + CacheDir() string WriteConfigYml(interface{}) error WriteEnvFile(string, string) error WriteProfileD(string, string) error diff --git a/src/java/frameworks/cf_metrics_exporter.go b/src/java/frameworks/cf_metrics_exporter.go index 7bec7271e..66fafb491 100644 --- a/src/java/frameworks/cf_metrics_exporter.go +++ b/src/java/frameworks/cf_metrics_exporter.go @@ -12,14 +12,6 @@ import ( const cfMetricsExporterDependencyName = "cf-metrics-exporter" const cfMetricsExporterDirName = "cf_metrics_exporter" -// Installer interface for dependency installation -// Allows for mocking in tests -// Only the InstallDependency method is needed for this framework -// (matches the signature of libbuildpack.Installer) -type Installer interface { - InstallDependency(dep libbuildpack.Dependency, outputDir string) error -} - type CfMetricsExporterFramework struct { context *common.Context } diff --git a/src/java/supply/supply_test.go b/src/java/supply/supply_test.go index 3f7a9e32a..18a6086e2 100644 --- a/src/java/supply/supply_test.go +++ b/src/java/supply/supply_test.go @@ -100,7 +100,7 @@ dependencies: [] // create memory calculator install dir memCalcInstallDir := filepath.Join(depsDir, depsIdx, "tmp", "memory-calculator") - Expect(os.MkdirAll(filepath.Join(jvmkillInstallDir), 0755)).To(Succeed()) + Expect(os.MkdirAll(filepath.Join(memCalcInstallDir), 0755)).To(Succeed()) // create bin/java used to locate JAVA_HOME directory after JRE extraction Expect(os.MkdirAll(filepath.Join(jdkInstallDir, "jre-17.0.15", "bin"), 0755)).To(Succeed()) @@ -223,6 +223,16 @@ dependencies: [] }) }) + Describe("No Container Supply", func() { + Context("when no recognized application type is present", func() { + It("fails to detect a container", func() { + err := supply.Run(supplier) + Expect(err).NotTo(BeNil()) + Expect(err.Error()).To(ContainSubstring("no suitable container found")) + }) + }) + }) + Describe("Stager Configuration", func() { It("creates necessary directories in deps dir", func() { depDir := stager.DepDir()