From ef6227dbc456782f2aa0b1d9cbc7ac18edc68bc4 Mon Sep 17 00:00:00 2001 From: Hao Liu <44379968+TheRealHaoLiu@users.noreply.github.com> Date: Tue, 29 Apr 2025 13:52:27 -0400 Subject: [PATCH 01/21] cmd: Add unit tests and fix WebSocket path default (#1312) --- cmd/config_test.go | 286 +++++++++++++++++++++++++++++++++++++++++++ cmd/defaults.go | 2 +- cmd/defaults_test.go | 268 ++++++++++++++++++++++++++++++++++++++++ cmd/root_test.go | 58 +++++++++ 4 files changed, 613 insertions(+), 1 deletion(-) create mode 100644 cmd/config_test.go create mode 100644 cmd/defaults_test.go create mode 100644 cmd/root_test.go diff --git a/cmd/config_test.go b/cmd/config_test.go new file mode 100644 index 000000000..d3ce32616 --- /dev/null +++ b/cmd/config_test.go @@ -0,0 +1,286 @@ +package cmd + +import ( + "reflect" + "testing" + + "github.com/ansible/receptor/pkg/backends" + "github.com/ansible/receptor/pkg/controlsvc" + "github.com/ansible/receptor/pkg/workceptor" +) + +func TestIsConfigEmpty(t *testing.T) { + // Test with an empty struct + type emptyStruct struct { + Field1 string + Field2 int + Field3 bool + } + empty := emptyStruct{} + if !isConfigEmpty(reflect.ValueOf(empty)) { + t.Error("Expected empty struct to be identified as empty") + } + + // Test with a non-empty struct + nonEmpty := emptyStruct{Field1: "value"} + if isConfigEmpty(reflect.ValueOf(nonEmpty)) { + t.Error("Expected non-empty struct to be identified as non-empty") + } +} + +func TestSetBackendConfigDefaults(t *testing.T) { + // Create a BackendConfig with no defaults set + config := &BackendConfig{ + TCPListeners: []*backends.TCPListenerCfg{ + { + BindAddr: "", + Cost: 0, + }, + }, + UDPListeners: []*backends.UDPListenerCfg{ + { + BindAddr: "", + Cost: 0, + }, + }, + WSListeners: []*backends.WebsocketListenerCfg{ + { + BindAddr: "", + Cost: 0, + Path: "", + }, + }, + TCPPeers: []*backends.TCPDialerCfg{ + { + Cost: 0, + Redial: false, + }, + }, + UDPPeers: []*backends.UDPDialerCfg{ + { + Cost: 0, + Redial: false, + }, + }, + WSPeers: []*backends.WebsocketDialerCfg{ + { + Cost: 0, + Redial: false, + }, + }, + } + + // Apply defaults + SetBackendConfigDefaults(config) + + // Check TCP Listener defaults + if config.TCPListeners[0].BindAddr != "0.0.0.0" { + t.Errorf("Expected TCP Listener BindAddr to be '0.0.0.0', got '%s'", config.TCPListeners[0].BindAddr) + } + if config.TCPListeners[0].Cost != 1.0 { + t.Errorf("Expected TCP Listener Cost to be 1.0, got %f", config.TCPListeners[0].Cost) + } + + // Check UDP Listener defaults + if config.UDPListeners[0].BindAddr != "0.0.0.0" { + t.Errorf("Expected UDP Listener BindAddr to be '0.0.0.0', got '%s'", config.UDPListeners[0].BindAddr) + } + if config.UDPListeners[0].Cost != 1.0 { + t.Errorf("Expected UDP Listener Cost to be 1.0, got %f", config.UDPListeners[0].Cost) + } + + // Check WS Listener defaults + if config.WSListeners[0].BindAddr != "0.0.0.0" { + t.Errorf("Expected WS Listener BindAddr to be '0.0.0.0', got '%s'", config.WSListeners[0].BindAddr) + } + if config.WSListeners[0].Cost != 1.0 { + t.Errorf("Expected WS Listener Cost to be 1.0, got %f", config.WSListeners[0].Cost) + } + + // Check TCP Peer defaults + if config.TCPPeers[0].Cost != 1.0 { + t.Errorf("Expected TCP Peer Cost to be 1.0, got %f", config.TCPPeers[0].Cost) + } + if !config.TCPPeers[0].Redial { + t.Error("Expected TCP Peer Redial to be true") + } + + // Check UDP Peer defaults + if config.UDPPeers[0].Cost != 1.0 { + t.Errorf("Expected UDP Peer Cost to be 1.0, got %f", config.UDPPeers[0].Cost) + } + if !config.UDPPeers[0].Redial { + t.Error("Expected UDP Peer Redial to be true") + } + + // Check WS Peer defaults + if config.WSPeers[0].Cost != 1.0 { + t.Errorf("Expected WS Peer Cost to be 1.0, got %f", config.WSPeers[0].Cost) + } + if !config.WSPeers[0].Redial { + t.Error("Expected WS Peer Redial to be true") + } +} + +func TestSetReceptorConfigDefaults(t *testing.T) { + // Create a ReceptorConfig with no defaults set + config := &ReceptorConfig{ + Node: nil, + LogLevel: nil, + ControlServices: []*controlsvc.CmdlineConfigUnix{ + { + Service: "", + Permissions: 0, + }, + }, + WorkKubernetes: []*workceptor.KubeWorkerCfg{ + { + AuthMethod: "", + StreamMethod: "", + }, + }, + } + + // Apply defaults + SetReceptorConfigDefaults(config) + + // Check Node defaults + if config.Node == nil { + t.Error("Expected Node to be initialized") + } else if config.Node.DataDir != "/tmp/receptor" { + t.Errorf("Expected Node DataDir to be '/tmp/receptor', got '%s'", config.Node.DataDir) + } + + // Check ControlService defaults + if config.ControlServices[0].Service != "control" { + t.Errorf("Expected ControlService Service to be 'control', got '%s'", config.ControlServices[0].Service) + } + if config.ControlServices[0].Permissions != 0o600 { + t.Errorf("Expected ControlService Permissions to be 0o600, got %o", config.ControlServices[0].Permissions) + } + + // Check WorkKubernetes defaults + if config.WorkKubernetes[0].AuthMethod != "incluster" { + t.Errorf("Expected WorkKubernetes AuthMethod to be 'incluster', got '%s'", config.WorkKubernetes[0].AuthMethod) + } + if config.WorkKubernetes[0].StreamMethod != "logger" { + t.Errorf("Expected WorkKubernetes StreamMethod to be 'logger', got '%s'", config.WorkKubernetes[0].StreamMethod) + } +} + +// mockCommand is a struct that implements the Initer, Preparer, and Runer interfaces for testing. +type mockCommand struct { + initCalled bool + prepareCalled bool + runCalled bool + initError error + prepareError error + runError error +} + +// Init implements the Initer interface. +func (m *mockCommand) Init() error { + m.initCalled = true + + return m.initError +} + +// Prepare implements the Preparer interface. +func (m *mockCommand) Prepare() error { + m.prepareCalled = true + + return m.prepareError +} + +// Run implements the Runer interface. +func (m *mockCommand) Run() error { + m.runCalled = true + + return m.runError +} + +func TestRunPhases(t *testing.T) { + // Test Init phase + mock := &mockCommand{} + RunPhases("Init", reflect.ValueOf(mock)) + if !mock.initCalled { + t.Error("Expected Init to be called") + } + if mock.prepareCalled || mock.runCalled { + t.Error("Expected only Init to be called") + } + + // Test Prepare phase + mock = &mockCommand{} + RunPhases("Prepare", reflect.ValueOf(mock)) + if !mock.prepareCalled { + t.Error("Expected Prepare to be called") + } + if mock.initCalled || mock.runCalled { + t.Error("Expected only Prepare to be called") + } + + // Test Run phase + mock = &mockCommand{} + RunPhases("Run", reflect.ValueOf(mock)) + if !mock.runCalled { + t.Error("Expected Run to be called") + } + if mock.initCalled || mock.prepareCalled { + t.Error("Expected only Run to be called") + } + + // Test with an invalid phase + mock = &mockCommand{} + RunPhases("InvalidPhase", reflect.ValueOf(mock)) + if mock.initCalled || mock.prepareCalled || mock.runCalled { + t.Error("Expected no methods to be called for invalid phase") + } +} + +func TestParseReceptorConfig(t *testing.T) { + // Skip this test for now as it requires more complex setup + t.Skip("Skipping TestParseReceptorConfig as it requires more complex setup") +} + +func TestParseBackendConfig(t *testing.T) { + // Skip this test for now as it requires more complex setup + t.Skip("Skipping TestParseBackendConfig as it requires more complex setup") +} + +func TestParseCertificatesConfig(t *testing.T) { + // Skip this test for now as it requires more complex setup + t.Skip("Skipping TestParseCertificatesConfig as it requires more complex setup") +} + +// testConfig is a struct with a slice of mockCommand for testing RunConfigV2. +type testConfig struct { + Commands []*mockCommand + Empty string +} + +func TestRunConfigV2(t *testing.T) { + // Create a test config with some commands + config := testConfig{ + Commands: []*mockCommand{ + {}, + {}, + }, + } + + // Run the config + RunConfigV2(reflect.ValueOf(config)) + + // Check that all commands were called for all phases + for _, cmd := range config.Commands { + if !cmd.initCalled { + t.Error("Expected Init to be called") + } + if !cmd.prepareCalled { + t.Error("Expected Prepare to be called") + } + if !cmd.runCalled { + t.Error("Expected Run to be called") + } + } +} diff --git a/cmd/defaults.go b/cmd/defaults.go index a06d903a8..9de74a201 100644 --- a/cmd/defaults.go +++ b/cmd/defaults.go @@ -33,7 +33,7 @@ func SetWSListenerDefaults(config *BackendConfig) { listener.BindAddr = "0.0.0.0" } if listener.Path == "" { - listener.BindAddr = "/" + listener.Path = "/" } } } diff --git a/cmd/defaults_test.go b/cmd/defaults_test.go new file mode 100644 index 000000000..e4482e419 --- /dev/null +++ b/cmd/defaults_test.go @@ -0,0 +1,268 @@ +package cmd + +import ( + "testing" + + "github.com/ansible/receptor/pkg/backends" + "github.com/ansible/receptor/pkg/controlsvc" + "github.com/ansible/receptor/pkg/logger" + "github.com/ansible/receptor/pkg/types" + "github.com/ansible/receptor/pkg/workceptor" +) + +func TestSetTCPListenerDefaults(t *testing.T) { + config := &BackendConfig{ + TCPListeners: []*backends.TCPListenerCfg{ + { + BindAddr: "", + Cost: 0, + }, + }, + } + + SetTCPListenerDefaults(config) + + if config.TCPListeners[0].BindAddr != "0.0.0.0" { + t.Errorf("Expected BindAddr to be '0.0.0.0', got '%s'", config.TCPListeners[0].BindAddr) + } + if config.TCPListeners[0].Cost != 1.0 { + t.Errorf("Expected Cost to be 1.0, got %f", config.TCPListeners[0].Cost) + } +} + +func TestSetUDPListenerDefaults(t *testing.T) { + config := &BackendConfig{ + UDPListeners: []*backends.UDPListenerCfg{ + { + BindAddr: "", + Cost: 0, + }, + }, + } + + SetUDPListenerDefaults(config) + + if config.UDPListeners[0].BindAddr != "0.0.0.0" { + t.Errorf("Expected BindAddr to be '0.0.0.0', got '%s'", config.UDPListeners[0].BindAddr) + } + if config.UDPListeners[0].Cost != 1.0 { + t.Errorf("Expected Cost to be 1.0, got %f", config.UDPListeners[0].Cost) + } +} + +func TestSetWSListenerDefaults(t *testing.T) { + config := &BackendConfig{ + WSListeners: []*backends.WebsocketListenerCfg{ + { + BindAddr: "", + Cost: 0, + Path: "", + }, + }, + } + + SetWSListenerDefaults(config) + + if config.WSListeners[0].BindAddr != "0.0.0.0" { + t.Errorf("Expected BindAddr to be '0.0.0.0', got '%s'", config.WSListeners[0].BindAddr) + } + if config.WSListeners[0].Cost != 1.0 { + t.Errorf("Expected Cost to be 1.0, got %f", config.WSListeners[0].Cost) + } + if config.WSListeners[0].Path != "/" { + t.Errorf("Expected Path to be '/', got '%s'", config.WSListeners[0].Path) + } +} + +func TestSetUDPPeerDefaults(t *testing.T) { + config := &BackendConfig{ + UDPPeers: []*backends.UDPDialerCfg{ + { + Cost: 0, + Redial: false, + }, + }, + } + + SetUDPPeerDefaults(config) + + if config.UDPPeers[0].Cost != 1.0 { + t.Errorf("Expected Cost to be 1.0, got %f", config.UDPPeers[0].Cost) + } + if !config.UDPPeers[0].Redial { + t.Error("Expected Redial to be true") + } +} + +func TestSetTCPPeerDefaults(t *testing.T) { + config := &BackendConfig{ + TCPPeers: []*backends.TCPDialerCfg{ + { + Cost: 0, + Redial: false, + }, + }, + } + + SetTCPPeerDefaults(config) + + if config.TCPPeers[0].Cost != 1.0 { + t.Errorf("Expected Cost to be 1.0, got %f", config.TCPPeers[0].Cost) + } + if !config.TCPPeers[0].Redial { + t.Error("Expected Redial to be true") + } +} + +func TestSetWSPeerDefaults(t *testing.T) { + config := &BackendConfig{ + WSPeers: []*backends.WebsocketDialerCfg{ + { + Cost: 0, + Redial: false, + }, + }, + } + + SetWSPeerDefaults(config) + + if config.WSPeers[0].Cost != 1.0 { + t.Errorf("Expected Cost to be 1.0, got %f", config.WSPeers[0].Cost) + } + if !config.WSPeers[0].Redial { + t.Error("Expected Redial to be true") + } +} + +func TestSetCmdlineUnixDefaults(t *testing.T) { + config := &ReceptorConfig{ + ControlServices: []*controlsvc.CmdlineConfigUnix{ + { + Service: "", + Permissions: 0, + }, + }, + } + + SetCmdlineUnixDefaults(config) + + if config.ControlServices[0].Service != "control" { + t.Errorf("Expected Service to be 'control', got '%s'", config.ControlServices[0].Service) + } + if config.ControlServices[0].Permissions != 0o600 { + t.Errorf("Expected Permissions to be 0o600, got %o", config.ControlServices[0].Permissions) + } +} + +func TestSetLogLevelDefaults(t *testing.T) { + // Test with nil LogLevel + config := &ReceptorConfig{ + LogLevel: nil, + } + + SetLogLevelDefaults(config) + if config.LogLevel != nil { + t.Error("Expected LogLevel to remain nil") + } + + // Test with empty LogLevel + config = &ReceptorConfig{ + LogLevel: &logger.LoglevelCfg{ + Level: "", + }, + } + + SetLogLevelDefaults(config) + if config.LogLevel.Level != "error" { + t.Errorf("Expected Level to be 'error', got '%s'", config.LogLevel.Level) + } + + // Test with non-empty LogLevel + config = &ReceptorConfig{ + LogLevel: &logger.LoglevelCfg{ + Level: "debug", + }, + } + + SetLogLevelDefaults(config) + if config.LogLevel.Level != "debug" { + t.Errorf("Expected Level to remain 'debug', got '%s'", config.LogLevel.Level) + } +} + +func TestSetNodeDefaults(t *testing.T) { + // Test with nil Node + config := &ReceptorConfig{ + Node: nil, + } + + SetNodeDefaults(config) + if config.Node == nil { + t.Error("Expected Node to be initialized") + } else if config.Node.DataDir != "/tmp/receptor" { + t.Errorf("Expected DataDir to be '/tmp/receptor', got '%s'", config.Node.DataDir) + } + + // Test with empty DataDir + config = &ReceptorConfig{ + Node: &types.NodeCfg{ + DataDir: "", + }, + } + + SetNodeDefaults(config) + if config.Node.DataDir != "/tmp/receptor" { + t.Errorf("Expected DataDir to be '/tmp/receptor', got '%s'", config.Node.DataDir) + } + + // Test with non-empty DataDir + config = &ReceptorConfig{ + Node: &types.NodeCfg{ + DataDir: "/custom/path", + }, + } + + SetNodeDefaults(config) + if config.Node.DataDir != "/custom/path" { + t.Errorf("Expected DataDir to remain '/custom/path', got '%s'", config.Node.DataDir) + } +} + +func TestSetKubeWorkerDefaults(t *testing.T) { + config := &ReceptorConfig{ + WorkKubernetes: []*workceptor.KubeWorkerCfg{ + { + AuthMethod: "", + StreamMethod: "", + }, + }, + } + + SetKubeWorkerDefaults(config) + + if config.WorkKubernetes[0].AuthMethod != "incluster" { + t.Errorf("Expected AuthMethod to be 'incluster', got '%s'", config.WorkKubernetes[0].AuthMethod) + } + if config.WorkKubernetes[0].StreamMethod != "logger" { + t.Errorf("Expected StreamMethod to be 'logger', got '%s'", config.WorkKubernetes[0].StreamMethod) + } + + // Test with non-empty values + config = &ReceptorConfig{ + WorkKubernetes: []*workceptor.KubeWorkerCfg{ + { + AuthMethod: "custom", + StreamMethod: "custom", + }, + }, + } + + SetKubeWorkerDefaults(config) + + if config.WorkKubernetes[0].AuthMethod != "custom" { + t.Errorf("Expected AuthMethod to remain 'custom', got '%s'", config.WorkKubernetes[0].AuthMethod) + } + if config.WorkKubernetes[0].StreamMethod != "custom" { + t.Errorf("Expected StreamMethod to remain 'custom', got '%s'", config.WorkKubernetes[0].StreamMethod) + } +} diff --git a/cmd/root_test.go b/cmd/root_test.go new file mode 100644 index 000000000..dce6ec4f0 --- /dev/null +++ b/cmd/root_test.go @@ -0,0 +1,58 @@ +package cmd + +import ( + "os" + "testing" +) + +func TestInitConfig(t *testing.T) { + // Save the original cfgFile value + originalCfgFile := cfgFile + defer func() { + cfgFile = originalCfgFile + }() + + // Test with a specific config file + tmpfile, err := os.CreateTemp("", "receptor-config-*.yaml") + if err != nil { + t.Fatalf("Failed to create temp file: %v", err) + } + defer os.Remove(tmpfile.Name()) + + configContent := ` +node: + id: test-node + data-dir: /tmp/test-receptor +` + if _, err := tmpfile.Write([]byte(configContent)); err != nil { + t.Fatalf("Failed to write to temp file: %v", err) + } + if err := tmpfile.Close(); err != nil { + t.Fatalf("Failed to close temp file: %v", err) + } + + // Set the config file + cfgFile = tmpfile.Name() + + // Call initConfig + initConfig() + + // Test with no config file (should use default) + cfgFile = "" + initConfig() +} + +func TestExecute(t *testing.T) { + // Skip this test for now as it requires cobra import + t.Skip("Skipping TestExecute as it requires cobra import") +} + +func TestHandleRootCommand(t *testing.T) { + // Skip this test for now as it calls os.Exit + t.Skip("Skipping TestHandleRootCommand as it calls os.Exit") +} + +func TestReloadServices(t *testing.T) { + // Skip this test for now as it requires more complex setup + t.Skip("Skipping TestReloadServices as it requires more complex setup") +} From c89aaedf3552125a5a0355383de61d1a0361c023 Mon Sep 17 00:00:00 2001 From: Matthew Sandoval Date: Wed, 30 Apr 2025 03:39:37 -0700 Subject: [PATCH 02/21] Fix flock_test from interrupting utils package tests (#1314) --- pkg/utils/flock_test.go | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/pkg/utils/flock_test.go b/pkg/utils/flock_test.go index 4755eead9..cb7894956 100644 --- a/pkg/utils/flock_test.go +++ b/pkg/utils/flock_test.go @@ -6,6 +6,7 @@ package utils_test import ( "os" "path/filepath" + "strconv" "testing" "github.com/ansible/receptor/pkg/utils" @@ -26,7 +27,7 @@ func TestTryFLock(t *testing.T) { args: args{ filename: filepath.Join(os.TempDir(), "good_flock_listener"), }, - want: &utils.FLock{0}, + want: &utils.FLock{Fd: 0}, wantErr: false, }, { @@ -57,6 +58,25 @@ func TestTryFLock(t *testing.T) { } func TestFLock_Unlock(t *testing.T) { + f, err := os.CreateTemp("", "flock-test") + if err != nil { + t.Error(err) + } + defer os.Remove(f.Name()) + defer f.Close() + + var maxInt uintptr + if strconv.IntSize == 32 { + maxInt = uintptr(1<<31 - 1) + } else { + maxInt = uintptr(1<<63 - 1) + } + + fd := f.Fd() + if fd > maxInt { + t.Error(err) + } + type fields struct { Fd int } @@ -68,7 +88,7 @@ func TestFLock_Unlock(t *testing.T) { { name: "Positive", fields: fields{ - Fd: 1, + Fd: int(f.Fd()), // #nosec G115 }, wantErr: false, }, From 784f36220331dd74fe6be4cc3f8f45bd88550633 Mon Sep 17 00:00:00 2001 From: Lisa Ranjbar Miller Date: Wed, 30 Apr 2025 10:46:21 -0700 Subject: [PATCH 03/21] update check for connection info to have less time complexity in netceptor.go/runProtocol(...) (#1304) --- pkg/netceptor/netceptor.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pkg/netceptor/netceptor.go b/pkg/netceptor/netceptor.go index 664822125..f7452245f 100644 --- a/pkg/netceptor/netceptor.go +++ b/pkg/netceptor/netceptor.go @@ -2002,19 +2002,20 @@ func (s *Netceptor) runProtocol(ctx context.Context, sess BackendSession, bi *Ba return s.sendAndLogConnectionRejection(remoteNodeID, ci, "it is not in the allowed peers list") } + // Check if there is connection cost for this remoteNodeID remoteNodeCost, ok := bi.nodeCost[remoteNodeID] if ok { ci.Cost = remoteNodeCost connectionCost = remoteNodeCost } s.connLock.Lock() - for conn := range s.connections { - if remoteNodeID == conn { - remoteNodeAccepted = false - break - } + // Check if there connInfo for this remoteNodeID + _, ok = s.connections[remoteNodeID] + if ok { + remoteNodeAccepted = false } + if !remoteNodeAccepted { s.connLock.Unlock() From b3b79ebb4d20e8f8487e3f6906703a66da3831df Mon Sep 17 00:00:00 2001 From: Dave Mulford <243049+davemulford@users.noreply.github.com> Date: Wed, 30 Apr 2025 15:18:35 -0400 Subject: [PATCH 04/21] Refactor unixsock and add Windows test (#1308) --- pkg/utils/unixsock.go | 9 ++++----- pkg/utils/unixsock_errors.go | 22 +++++++++++++++++++++ pkg/utils/unixsock_test.go | 36 +++++++++++++++++++++++++++++++++++ pkg/utils/unixsock_windows.go | 3 +-- 4 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 pkg/utils/unixsock_errors.go diff --git a/pkg/utils/unixsock.go b/pkg/utils/unixsock.go index 6b6fa39cb..65ebd7f02 100644 --- a/pkg/utils/unixsock.go +++ b/pkg/utils/unixsock.go @@ -4,7 +4,6 @@ package utils import ( - "fmt" "net" "os" ) @@ -13,26 +12,26 @@ import ( func UnixSocketListen(filename string, permissions os.FileMode) (net.Listener, *FLock, error) { lock, err := TryFLock(filename + ".lock") if err != nil { - return nil, nil, fmt.Errorf("could not acquire lock on socket file: %s", err) + return nil, nil, MakeUnixSocketError(ErrSocketLockFileNotAcquired, err) } err = os.RemoveAll(filename) if err != nil { _ = lock.Unlock() - return nil, nil, fmt.Errorf("could not overwrite socket file: %s", err) + return nil, nil, MakeUnixSocketError(ErrSocketFileNotOverwritten, err) } uli, err := net.Listen("unix", filename) if err != nil { _ = lock.Unlock() - return nil, nil, fmt.Errorf("could not listen on socket file: %s", err) + return nil, nil, MakeUnixSocketError(ErrSocketFileListen, err) } err = os.Chmod(filename, permissions) if err != nil { _ = uli.Close() _ = lock.Unlock() - return nil, nil, fmt.Errorf("error setting socket file permissions: %s", err) + return nil, nil, MakeUnixSocketError(ErrSocketFilePermissionsNotSet, err) } return uli, lock, nil diff --git a/pkg/utils/unixsock_errors.go b/pkg/utils/unixsock_errors.go new file mode 100644 index 000000000..b874eb26b --- /dev/null +++ b/pkg/utils/unixsock_errors.go @@ -0,0 +1,22 @@ +package utils + +import ( + "errors" + "fmt" +) + +var ( + ErrSocketLockFileNotAcquired = errors.New("could not acquire lock on socket file") + ErrSocketFileNotOverwritten = errors.New("could not overwrite socket file") + ErrSocketFileListen = errors.New("could not listen on socket file") + ErrSocketFilePermissionsNotSet = errors.New("error setting socket file permissions") + ErrWindowsNotSupported = errors.New("unix sockets not available on Windows") +) + +func MakeUnixSocketError(err, underlyingErr error) error { + return fmt.Errorf("%s: %s", err, underlyingErr) +} + +func MakeWindowsSocketError() error { + return ErrWindowsNotSupported +} diff --git a/pkg/utils/unixsock_test.go b/pkg/utils/unixsock_test.go index 0ffcdd770..6ecc3a7c0 100644 --- a/pkg/utils/unixsock_test.go +++ b/pkg/utils/unixsock_test.go @@ -85,3 +85,39 @@ func TestUnixSocketListen(t *testing.T) { }) } } + +func TestUnixSocketErrors(t *testing.T) { + tests := []struct { + name string + errorFuncUnix func(error, error) error + errorFuncWindows func() error + }{ + { + name: "MakeUnixSocketError", + errorFuncUnix: utils.MakeUnixSocketError, + }, + { + name: "MakeWindowsSocketError", + errorFuncWindows: utils.MakeWindowsSocketError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.errorFuncUnix == nil && tt.errorFuncWindows == nil { + t.Error("either errorFuncUnix or errorFuncWindows must be set") + } + + var err error = nil + if tt.errorFuncUnix != nil { + err = tt.errorFuncUnix(utils.ErrSocketFileListen, utils.ErrSocketFileListen) + } else if tt.errorFuncWindows != nil { + err = tt.errorFuncWindows() + } + + if err == nil { + t.Errorf("%s: Error expected and none received", tt.name) + } + }) + } +} diff --git a/pkg/utils/unixsock_windows.go b/pkg/utils/unixsock_windows.go index 9021746f0..7ba4ace66 100644 --- a/pkg/utils/unixsock_windows.go +++ b/pkg/utils/unixsock_windows.go @@ -4,12 +4,11 @@ package utils import ( - "fmt" "net" "os" ) // UnixSocketListen is not available on Windows func UnixSocketListen(filename string, permissions os.FileMode) (net.Listener, *FLock, error) { - return nil, nil, fmt.Errorf("Unix sockets not available on Windows") + return nil, nil, MakeWindowsSocketError() } From 3a591679a3b4625b565f139ddd740f8947194e6a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 May 2025 09:15:39 -0700 Subject: [PATCH 05/21] Bump github.com/spf13/cobra from 1.8.1 to 1.9.1 (#1271) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 6e4aa9a75..9837f194b 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/rogpeppe/go-internal v1.13.1 github.com/sirupsen/logrus v1.9.3 github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 - github.com/spf13/cobra v1.8.1 + github.com/spf13/cobra v1.9.1 github.com/spf13/viper v1.19.0 github.com/vishvananda/netlink v1.3.0 go.uber.org/goleak v1.3.0 @@ -76,7 +76,7 @@ require ( github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/pflag v1.0.6 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/vishvananda/netns v0.0.4 // indirect github.com/x448/float16 v0.8.4 // indirect diff --git a/go.sum b/go.sum index 62235b751..4d623ab45 100644 --- a/go.sum +++ b/go.sum @@ -16,7 +16,7 @@ github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBT github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= @@ -219,10 +219,10 @@ github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= From 06eef5e15584b41144139b688f4a2a3335efe68a Mon Sep 17 00:00:00 2001 From: AaronH88 Date: Thu, 1 May 2025 17:23:08 +0100 Subject: [PATCH 06/21] Add unit tests for utils/broker (#1315) --- pkg/utils/broker_test.go | 400 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 400 insertions(+) create mode 100644 pkg/utils/broker_test.go diff --git a/pkg/utils/broker_test.go b/pkg/utils/broker_test.go new file mode 100644 index 000000000..5e627a122 --- /dev/null +++ b/pkg/utils/broker_test.go @@ -0,0 +1,400 @@ +package utils_test + +import ( + "context" + "reflect" + "sync" + "testing" + "time" + + "github.com/ansible/receptor/pkg/utils" +) + +// TestBrokerSubscribe tests the Subscribe method of the Broker. +func TestBrokerSubscribe(t *testing.T) { + type testCase struct { + name string + contextTimeout time.Duration + cancelContext bool + expectNil bool + } + + tests := []testCase{ + { + name: "Subscribe with active context", + contextTimeout: 1 * time.Second, + cancelContext: false, + expectNil: false, + }, + { + name: "Subscribe with canceled context", + contextTimeout: 1 * time.Second, + cancelContext: true, + expectNil: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), tt.contextTimeout) + defer cancel() + + broker := utils.NewBroker(ctx, reflect.TypeOf("")) + + if tt.cancelContext { + cancel() + // Give some time for the cancellation to propagate + time.Sleep(10 * time.Millisecond) + } + + ch := broker.Subscribe() + if (ch == nil) != tt.expectNil { + t.Errorf("Subscribe() returned nil: %v, expected nil: %v", ch == nil, tt.expectNil) + } + }) + } +} + +// TestBrokerUnsubscribe tests the Unsubscribe method of the Broker. +func TestBrokerUnsubscribe(t *testing.T) { + type testCase struct { + name string + contextTimeout time.Duration + cancelContext bool + } + + tests := []testCase{ + { + name: "Unsubscribe with active context", + contextTimeout: 1 * time.Second, + cancelContext: false, + }, + { + name: "Unsubscribe with canceled context", + contextTimeout: 1 * time.Second, + cancelContext: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), tt.contextTimeout) + defer cancel() + + broker := utils.NewBroker(ctx, reflect.TypeOf("")) + ch := broker.Subscribe() + + if tt.cancelContext { + cancel() + // Give some time for the cancellation to propagate + time.Sleep(10 * time.Millisecond) + } + + // This should not panic regardless of context state + broker.Unsubscribe(ch) + }) + } +} + +// TestBrokerPublish tests the Publish method of the Broker. +func TestBrokerPublish(t *testing.T) { + type testCase struct { + name string + msgType reflect.Type + msg interface{} + expectError bool + contextTimeout time.Duration + cancelContext bool + } + + tests := []testCase{ + { + name: "Publish string message with string broker", + msgType: reflect.TypeOf(""), + msg: "test message", + expectError: false, + contextTimeout: 1 * time.Second, + cancelContext: false, + }, + { + name: "Publish int message with string broker", + msgType: reflect.TypeOf(""), + msg: 123, + expectError: true, + contextTimeout: 1 * time.Second, + cancelContext: false, + }, + { + name: "Publish with canceled context", + msgType: reflect.TypeOf(""), + msg: "test message", + expectError: false, // Publish doesn't return an error when context is canceled + contextTimeout: 1 * time.Second, + cancelContext: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), tt.contextTimeout) + defer cancel() + + broker := utils.NewBroker(ctx, tt.msgType) + + if tt.cancelContext { + cancel() + // Give some time for the cancellation to propagate + time.Sleep(10 * time.Millisecond) + } + + err := broker.Publish(tt.msg) + if (err != nil) != tt.expectError { + t.Errorf("Publish() error = %v, expectError %v", err, tt.expectError) + } + }) + } +} + +// TestBrokerEndToEnd tests the full publish-subscribe workflow. +func TestBrokerEndToEnd(t *testing.T) { + type testCase struct { + name string + numSubscribers int + numMessages int + contextTimeout time.Duration + } + + tests := []testCase{ + { + name: "Single subscriber, single message", + numSubscribers: 1, + numMessages: 1, + contextTimeout: 1 * time.Second, + }, + { + name: "Multiple subscribers, single message", + numSubscribers: 5, + numMessages: 1, + contextTimeout: 1 * time.Second, + }, + { + name: "Single subscriber, multiple messages", + numSubscribers: 1, + numMessages: 5, + contextTimeout: 1 * time.Second, + }, + { + name: "Multiple subscribers, multiple messages", + numSubscribers: 5, + numMessages: 5, + contextTimeout: 1 * time.Second, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), tt.contextTimeout) + defer cancel() + + broker := utils.NewBroker(ctx, reflect.TypeOf("")) + + // Create subscribers + var subscribers []chan interface{} + for i := 0; i < tt.numSubscribers; i++ { + ch := broker.Subscribe() + if ch == nil { + t.Fatalf("Subscribe() returned nil") + } + subscribers = append(subscribers, ch) + } + + // Create a WaitGroup to ensure all messages are received + var wg sync.WaitGroup + + // Set up message reception tracking + receivedMsgs := make([][]string, tt.numSubscribers) + for i := range receivedMsgs { + receivedMsgs[i] = make([]string, 0, tt.numMessages) + } + + // Start goroutines to collect messages from each subscriber + for i, ch := range subscribers { + wg.Add(1) + go func(idx int, ch chan interface{}) { + defer wg.Done() + for j := 0; j < tt.numMessages; j++ { + select { + case msg := <-ch: + if msgStr, ok := msg.(string); ok { + receivedMsgs[idx] = append(receivedMsgs[idx], msgStr) + } else { + t.Errorf("Subscriber %d received non-string message: %v", idx, msg) + } + case <-time.After(500 * time.Millisecond): + t.Errorf("Subscriber %d timed out waiting for message %d", idx, j) + + return + } + } + }(i, ch) + } + + // Send messages + messages := make([]string, tt.numMessages) + for i := 0; i < tt.numMessages; i++ { + messages[i] = "test message " + string(rune('A'+i)) + err := broker.Publish(messages[i]) + if err != nil { + t.Fatalf("Publish() error = %v", err) + } + // Small delay to ensure message processing + time.Sleep(5 * time.Millisecond) + } + + // Wait for all subscribers to receive all messages + wg.Wait() + + // Verify all subscribers received all messages + for i, received := range receivedMsgs { + if len(received) != tt.numMessages { + t.Errorf("Subscriber %d received %d messages, expected %d", i, len(received), tt.numMessages) + + continue + } + + // Check each message + for j, msg := range received { + if j < len(messages) && msg != messages[j] { + t.Errorf("Subscriber %d received message %d = %v, want %v", i, j, msg, messages[j]) + } + } + } + + // Unsubscribe all + for _, ch := range subscribers { + broker.Unsubscribe(ch) + } + }) + } +} + +// TestBrokerContextCancellation tests that the broker properly handles context cancellation. +func TestBrokerContextCancellation(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + broker := utils.NewBroker(ctx, reflect.TypeOf("")) + + // Subscribe before cancellation + ch := broker.Subscribe() + if ch == nil { + t.Fatalf("Subscribe() returned nil") + } + + // Cancel context + cancel() + time.Sleep(10 * time.Millisecond) // Give time for cancellation to propagate + + // Verify channel is closed + select { + case _, ok := <-ch: + if ok { + t.Errorf("Channel should be closed after context cancellation") + } + case <-time.After(100 * time.Millisecond): + t.Errorf("Timed out waiting for channel to close") + } + + // Verify Subscribe returns nil after cancellation + ch2 := broker.Subscribe() + if ch2 != nil { + t.Errorf("Subscribe() should return nil after context cancellation") + } +} + +// TestBrokerConcurrency tests the broker under concurrent operations. +func TestBrokerConcurrency(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + broker := utils.NewBroker(ctx, reflect.TypeOf("")) + + const numSubscribers = 10 + const numPublishers = 5 + const messagesPerPublisher = 20 + + // Use a mutex to protect the counter + var mu sync.Mutex + receivedMessages := make([]int, numSubscribers) + + // WaitGroup for subscribers + var subWg sync.WaitGroup + + // WaitGroup for publishers + var pubWg sync.WaitGroup + + // Create subscribers + subscribers := make([]chan interface{}, numSubscribers) + for i := 0; i < numSubscribers; i++ { + subscribers[i] = broker.Subscribe() + if subscribers[i] == nil { + t.Fatalf("Subscribe() returned nil") + } + + // Start a goroutine to read messages + subWg.Add(1) + go func(idx int, ch chan interface{}) { + defer subWg.Done() + for { + select { + case _, ok := <-ch: + if !ok { + return + } + mu.Lock() + receivedMessages[idx]++ + mu.Unlock() + case <-ctx.Done(): + return + } + } + }(i, subscribers[i]) + } + + // Create publishers + for i := 0; i < numPublishers; i++ { + pubWg.Add(1) + go func(idx int) { + defer pubWg.Done() + for j := 0; j < messagesPerPublisher; j++ { + msg := "message from publisher " + string(rune('A'+idx)) + " #" + string(rune('0'+j)) + err := broker.Publish(msg) + if err != nil { + t.Errorf("Publish() error = %v", err) + } + // Small delay to simulate work + time.Sleep(2 * time.Millisecond) + } + }(i) + } + + // Wait for publishers to finish + pubWg.Wait() + + // Give subscribers time to process all messages + time.Sleep(100 * time.Millisecond) + + // Unsubscribe all + for _, ch := range subscribers { + broker.Unsubscribe(ch) + } + + // Wait for subscribers to finish + subWg.Wait() + + // Verify each subscriber received the expected number of messages + expectedMessages := numPublishers * messagesPerPublisher + for i, count := range receivedMessages { + if count != expectedMessages { + t.Errorf("Subscriber %d received %d messages, expected %d", i, count, expectedMessages) + } + } +} From 0d359e533531895e2295325cd2c306199a27acb5 Mon Sep 17 00:00:00 2001 From: Andrea Restle-Lay Date: Thu, 8 May 2025 08:40:04 -0400 Subject: [PATCH 07/21] AAP-43201 netceptor logging (#1300) --- pkg/backends/tcp.go | 4 ++ pkg/logger/logger.go | 63 ++++++++++++++++++++++++- pkg/logger/logger_test.go | 76 ++++++++++++++++++++++++------- pkg/netceptor/external_backend.go | 4 ++ pkg/netceptor/netceptor.go | 18 +++++++- 5 files changed, 146 insertions(+), 19 deletions(-) diff --git a/pkg/backends/tcp.go b/pkg/backends/tcp.go index 397b743c4..d2f7135ef 100644 --- a/pkg/backends/tcp.go +++ b/pkg/backends/tcp.go @@ -62,6 +62,10 @@ func (b *TCPDialer) Start(ctx context.Context, wg *sync.WaitGroup) (chan netcept return nil, err } + if b.logger != nil { + b.logger.Debug("TCPDialer connected to TCP %s Address %s\n", b.address, conn.RemoteAddr().String()) + } + return newTCPSession(conn, closeChan), nil }) } diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index ffaab5bff..eeda20a36 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -88,6 +88,7 @@ type ReceptorLogger struct { log.Logger Prefix string m sync.Mutex + Suffix map[string]string } // NewReceptorLogger to instantiate a new logger object. @@ -98,6 +99,36 @@ func NewReceptorLogger(prefix string) *ReceptorLogger { } } +// NewReceptorLoggerWithSuffix to instantiate a new logger object with a new Suffix. +func NewReceptorLoggerWithSuffix(prefix string, suffix map[string]string) *ReceptorLogger { + logger := NewReceptorLogger(prefix) + logger.SetSuffix(suffix) + + return logger +} + +// SetSuffix sets the suffix for the logger, overwriting any existing suffix. +func (rl *ReceptorLogger) SetSuffix(suffix map[string]string) { + rl.m.Lock() + defer rl.m.Unlock() + rl.Suffix = suffix +} + +// UpdateSuffix allows adding suffix key/value pairs to an existing suffix. +// If the key already exists, the value will be overwritten. +// If the suffix is nil, it will be initialized. +// This is useful for adding additional context to log messages when you want to keep the existing suffix key/value pairs. +func (rl *ReceptorLogger) UpdateSuffix(suffix map[string]string) { + rl.m.Lock() + defer rl.m.Unlock() + if rl.Suffix == nil { + rl.Suffix = make(map[string]string) + } + for k, v := range suffix { + rl.Suffix[k] = v + } +} + // SetOutput sets the output destination for the logger. func (rl *ReceptorLogger) SetOutput(w io.Writer) { rl.Logger.SetOutput(w) @@ -218,10 +249,39 @@ func (rl *ReceptorLogger) Log(level int, format string, v ...interface{}) { if logLevel >= level { rl.Logger.SetPrefix(prefix) + format, v = rl.appendSuffix(format, v) rl.Logger.Printf(format, v...) } } +func (rl *ReceptorLogger) appendSuffix(format string, v []interface{}) (string, []interface{}) { + if rl.Suffix != nil { + rl.m.Lock() + defer rl.m.Unlock() + + var b strings.Builder + b.WriteString("{") + first := true + for k, val := range rl.Suffix { + if !first { + b.WriteString(",") + } + first = false + b.WriteString(`"`) + b.WriteString(k) + b.WriteString(`":"`) + b.WriteString(val) + b.WriteString(`"`) + } + b.WriteString("}") + + format = strings.ReplaceAll(format, "\n", "") + " %s" + v = append(v, b.String()) + } + + return format, v +} + // SanitizedLog adds a prefix and prints a given log message. func (rl *ReceptorLogger) SanitizedLog(level int, format string, v ...interface{}) { if logger != nil { @@ -245,7 +305,8 @@ func (rl *ReceptorLogger) SanitizedLog(level int, format string, v ...interface{ } if logLevel >= level { - message := fmt.Sprintf(format, v...) + message, v := rl.appendSuffix(format, v) + message = fmt.Sprintf(message, v...) sanMessage := strings.ReplaceAll(message, "\n", "") rl.Logger.SetPrefix(prefix) rl.Logger.Print(sanMessage) diff --git a/pkg/logger/logger_test.go b/pkg/logger/logger_test.go index a19452095..68caeb1bf 100644 --- a/pkg/logger/logger_test.go +++ b/pkg/logger/logger_test.go @@ -3,7 +3,7 @@ package logger_test import ( "bytes" "fmt" - "os" + "strings" "testing" "github.com/ansible/receptor/pkg/logger" @@ -72,14 +72,10 @@ func TestLogLevelToNameWithError(t *testing.T) { } func TestDebugPayload(t *testing.T) { - logFilePath := "/tmp/test-output" + var logBuffer bytes.Buffer logger.SetGlobalLogLevel(4) receptorLogger := logger.NewReceptorLogger("testDebugPayload") - logFile, err := os.OpenFile(logFilePath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o600) - if err != nil { - t.Error("error creating test-output file") - } - + receptorLogger.SetOutput(&logBuffer) payload := "Testing debugPayload" workUnitID := "1234" connectionType := "unix socket" @@ -104,19 +100,67 @@ func TestDebugPayload(t *testing.T) { for _, testCase := range debugPayloadTestCases { t.Run(testCase.name, func(t *testing.T) { - receptorLogger.SetOutput(logFile) receptorLogger.DebugPayload(testCase.debugPayload, testCase.payload, testCase.workUnitID, testCase.connectionType) + testOutput := logBuffer.Bytes() - testOutput, err := os.ReadFile(logFilePath) - if err != nil { - t.Error("error reading test-output file") - } - if !bytes.Contains(testOutput, []byte(testCase.expectedLog)) { + if !strings.Contains(string(testOutput), testCase.expectedLog) { t.Errorf("failed to log correctly, expected: %v got %v", testCase.expectedLog, string(testOutput)) } - if err := os.Truncate(logFilePath, 0); err != nil { - t.Errorf("failed to truncate: %v", err) - } + logBuffer.Reset() }) } } + +func assertSuffixFieldsPresent(t *testing.T, logLine string, expected map[string]string) { + t.Helper() + for k, v := range expected { + needle := `"` + k + `":"` + v + `"` + if !strings.Contains(logLine, needle) { + t.Errorf("expected key-value pair %s not found in log: %s", needle, logLine) + } + } +} + +func TestGetLoggerWithSuffix(t *testing.T) { + logger.SetGlobalLogLevel(4) + + t.Run("initial suffix", func(t *testing.T) { + var logBuffer bytes.Buffer + testname := "Initial Suffix Example" + suffix := map[string]string{ + "node_id": "controller", + "remote_id": "hop", + } + receptorLogger := logger.NewReceptorLoggerWithSuffix("", suffix) + receptorLogger.SetOutput(&logBuffer) + + receptorLogger.Error("%s", testname) + if !strings.Contains(logBuffer.String(), testname) { + t.Errorf("expected log message %s not found in log: %s", testname, logBuffer.String()) + } + assertSuffixFieldsPresent(t, logBuffer.String(), suffix) + }) + t.Run("updated suffix", func(t *testing.T) { + var logBuffer bytes.Buffer + testname := "Updated Suffix Example" + suffix := map[string]string{ + "node_id": "controller", + "remote_id": "hop", + } + receptorLogger := logger.NewReceptorLoggerWithSuffix("", suffix) + receptorLogger.SetOutput(&logBuffer) + + updated := map[string]string{ + "cost": "12", + } + receptorLogger.UpdateSuffix(updated) + receptorLogger.SanitizedError("%s", testname) + + if !strings.Contains(logBuffer.String(), testname) { + t.Errorf("expected log message %s not found in log: %s", testname, logBuffer.String()) + } + + assertSuffixFieldsPresent(t, logBuffer.String(), suffix) + assertSuffixFieldsPresent(t, logBuffer.String(), updated) + }) +} diff --git a/pkg/netceptor/external_backend.go b/pkg/netceptor/external_backend.go index 1c01b547c..898c6d943 100644 --- a/pkg/netceptor/external_backend.go +++ b/pkg/netceptor/external_backend.go @@ -204,3 +204,7 @@ func (es *ExternalSession) Close() error { return err } + +func (mc *netMessageConn) RemoteAddr() net.Addr { + return mc.conn.RemoteAddr() +} diff --git a/pkg/netceptor/netceptor.go b/pkg/netceptor/netceptor.go index f7452245f..304c1da0e 100644 --- a/pkg/netceptor/netceptor.go +++ b/pkg/netceptor/netceptor.go @@ -355,6 +355,7 @@ func NewWithConsts(ctx context.Context, nodeID string, MinVersion: tls.VersionTLS12, } s.AddNameHash(nodeID) + s.GetLogger().SetSuffix(map[string]string{"node_id": nodeID}) s.context, s.cancelFunc = context.WithCancel(ctx) s.unreachableBroker = utils.NewBroker(s.context, reflect.TypeOf(UnreachableNotification{})) s.routingUpdateBroker = utils.NewBroker(s.context, reflect.TypeOf(map[string]string{})) @@ -1987,6 +1988,8 @@ func (s *Netceptor) runProtocol(ctx context.Context, sess BackendSession, bi *Ba if remoteNodeID == s.nodeID { return s.sendAndLogConnectionRejection(remoteNodeID, ci, "it tried to connect using our own node ID") } + suffix := map[string]string{"remote_id": remoteNodeID} + s.GetLogger().UpdateSuffix(suffix) remoteNodeAccepted := true if bi.allowedPeers != nil { remoteNodeAccepted = false @@ -2002,6 +2005,7 @@ func (s *Netceptor) runProtocol(ctx context.Context, sess BackendSession, bi *Ba return s.sendAndLogConnectionRejection(remoteNodeID, ci, "it is not in the allowed peers list") } + // Check if there is connection cost for this remoteNodeID // Check if there is connection cost for this remoteNodeID remoteNodeCost, ok := bi.nodeCost[remoteNodeID] if ok { @@ -2010,11 +2014,21 @@ func (s *Netceptor) runProtocol(ctx context.Context, sess BackendSession, bi *Ba } s.connLock.Lock() - // Check if there connInfo for this remoteNodeID - _, ok = s.connections[remoteNodeID] + // Check if there is already connInfo for this remoteNodeID + existingConn, ok := s.connections[remoteNodeID] if ok { remoteNodeAccepted = false } + var connError error + connError = nil + + // Verify that the existing connection is valid + if ok && existingConn != nil { + connError = existingConn.Context.Err() + } + if ok && connError != nil { + s.Logger.Error("Context for existing connection error: %s", connError) + } if !remoteNodeAccepted { s.connLock.Unlock() From 70d7a1967764cc988e9301aa62b9d72eb8c36944 Mon Sep 17 00:00:00 2001 From: PabloHiro Date: Thu, 29 May 2025 12:26:10 +0200 Subject: [PATCH 08/21] feat: Use pyproject.toml instead of setup.cfg + pinned dependencies + ruff as linter. --- Makefile | 24 +++---- docs/source/conf.py | 1 - receptor-python-worker/pyproject.toml | 16 ++++- receptor-python-worker/setup.cfg | 20 ------ receptorctl/.coveragerc | 2 - receptorctl/.pip-tools.toml | 5 -- receptorctl/MANIFEST.in | 3 - receptorctl/README.md | 8 +-- receptorctl/noxfile.py | 70 ++++--------------- receptorctl/pyproject.toml | 51 +++++++++++++- receptorctl/requirements/lint.in | 4 -- receptorctl/requirements/lint.txt | 26 ------- receptorctl/requirements/pip-tools.in | 3 - receptorctl/requirements/pip-tools.txt | 26 ------- receptorctl/requirements/requirements.txt | 14 ++++ receptorctl/requirements/tests.in | 5 -- receptorctl/requirements/tests.txt | 22 ------ receptorctl/setup.cfg | 28 -------- .../container-builder/Containerfile | 6 +- 19 files changed, 104 insertions(+), 230 deletions(-) delete mode 100644 receptor-python-worker/setup.cfg delete mode 100644 receptorctl/.coveragerc delete mode 100644 receptorctl/.pip-tools.toml delete mode 100644 receptorctl/requirements/lint.in delete mode 100644 receptorctl/requirements/lint.txt delete mode 100644 receptorctl/requirements/pip-tools.in delete mode 100644 receptorctl/requirements/pip-tools.txt create mode 100644 receptorctl/requirements/requirements.txt delete mode 100644 receptorctl/requirements/tests.in delete mode 100644 receptorctl/requirements/tests.txt delete mode 100644 receptorctl/setup.cfg diff --git a/Makefile b/Makefile index 2159bab08..f98268a47 100644 --- a/Makefile +++ b/Makefile @@ -62,6 +62,8 @@ receptor: $(shell find pkg -type f -name '*.go') ./cmd/receptor-cl/receptor.go clean: @rm -fv .container-flag* @rm -fv .VERSION + @rm -fv receptorctl/.VERSION + @rm -fv receptor-python-worker/.VERSION @rm -rfv dist/ @rm -fv $(KUBECTL_BINARY) @rm -fv packaging/container/receptor @@ -96,7 +98,7 @@ kubectl: lint: @golint cmd/... pkg/... example/... -receptorctl-lint: receptor receptorctl/.VERSION +receptorctl-lint: receptor @cd receptorctl && nox -s lint format: @@ -169,7 +171,7 @@ test: receptor -race \ -timeout 5m -receptorctl-test: receptorctl/.VERSION receptor +receptorctl-test: receptor @cd receptorctl && nox -s tests testloop: receptor @@ -184,27 +186,21 @@ version: @echo $(VERSION) > .VERSION @echo ".VERSION created for $(VERSION)" -receptorctl/.VERSION: - echo $(VERSION) > $@ - RECEPTORCTL_WHEEL = receptorctl/dist/receptorctl-$(VERSION:v%=%)-py3-none-any.whl -$(RECEPTORCTL_WHEEL): receptorctl/README.md receptorctl/.VERSION $(shell find receptorctl/receptorctl -type f -name '*.py') - @cd receptorctl && python3 -m build --wheel +$(RECEPTORCTL_WHEEL): $(shell find receptorctl/receptorctl -type f -name '*.py') + @cd receptorctl && SETUPTOOLS_SCM_PRETEND_VERSION_FOR_RECEPTORCTL=$(VERSION) python3 -m build --wheel receptorctl_wheel: $(RECEPTORCTL_WHEEL) RECEPTORCTL_SDIST = receptorctl/dist/receptorctl-$(VERSION:v%=%).tar.gz -$(RECEPTORCTL_SDIST): receptorctl/README.md receptorctl/.VERSION $(shell find receptorctl/receptorctl -type f -name '*.py') - @cd receptorctl && python3 -m build --sdist +$(RECEPTORCTL_SDIST): $(shell find receptorctl/receptorctl -type f -name '*.py') + @cd receptorctl && SETUPTOOLS_SCM_PRETEND_VERSION_FOR_RECEPTORCTL=$(VERSION) python3 -m build --sdist receptorctl_sdist: $(RECEPTORCTL_SDIST) -receptor-python-worker/.VERSION: - echo $(VERSION) > $@ - RECEPTOR_PYTHON_WORKER_WHEEL = receptor-python-worker/dist/receptor_python_worker-$(VERSION:v%=%)-py3-none-any.whl -$(RECEPTOR_PYTHON_WORKER_WHEEL): receptor-python-worker/README.md receptor-python-worker/.VERSION $(shell find receptor-python-worker/receptor_python_worker -type f -name '*.py') - @cd receptor-python-worker && python3 -m build --wheel +$(RECEPTOR_PYTHON_WORKER_WHEEL): $(shell find receptor-python-worker/receptor_python_worker -type f -name '*.py') + @cd receptor-python-worker && SETUPTOOLS_SCM_PRETEND_VERSION_FOR_RECEPTOR_PYTHON_WORKER=$(VERSION) python3 -m build --wheel # Container command can be docker or podman CONTAINERCMD ?= podman diff --git a/docs/source/conf.py b/docs/source/conf.py index 7e898ed1d..2b78cb0de 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -28,7 +28,6 @@ # ones. extensions = [ "sphinx.ext.autosectionlabel", - "pbr.sphinxext", ] autosectionlabel_prefix_document = True diff --git a/receptor-python-worker/pyproject.toml b/receptor-python-worker/pyproject.toml index fed528d4a..3839d130a 100644 --- a/receptor-python-worker/pyproject.toml +++ b/receptor-python-worker/pyproject.toml @@ -1,3 +1,17 @@ +[project] +name = "receptor-python-worker" +authors = [{name = "Red Hat", email = "info@ansible.com"}] +description = "The receptor-python-worker command is called by Receptor to supervise the operation of a Python worker plugin." +readme = "README.md" +license = "Apache-2.0" +dynamic = ["version"] + [build-system] -requires = ["setuptools"] +requires = ["setuptools", "setuptools-scm"] build-backend = "setuptools.build_meta" + +[tool.setuptools_scm] +fallback_version = "0.0.0" + +[project.scripts] +receptor-python-worker = "receptor_python_worker:run" \ No newline at end of file diff --git a/receptor-python-worker/setup.cfg b/receptor-python-worker/setup.cfg deleted file mode 100644 index bdb8c731e..000000000 --- a/receptor-python-worker/setup.cfg +++ /dev/null @@ -1,20 +0,0 @@ -[metadata] -name = receptor-python-worker -author = Red Hat -author_email = info@ansible.com -summary = "The receptor-python-worker command is called by Receptor to supervise the operation of a Python worker plugin." -home_page = https://github.com/ansible/receptor/tree/devel/receptor-python-worker -description_file = README.md -description_content_type = text/markdown -version = file: .VERSION - -[options] -packages = find: - -[options.entry_points] -console_scripts = - receptor-python-worker = receptor_python_worker:run - -[files] -packages = - receptor-python-worker diff --git a/receptorctl/.coveragerc b/receptorctl/.coveragerc deleted file mode 100644 index c712d2595..000000000 --- a/receptorctl/.coveragerc +++ /dev/null @@ -1,2 +0,0 @@ -[run] -omit = tests/* diff --git a/receptorctl/.pip-tools.toml b/receptorctl/.pip-tools.toml deleted file mode 100644 index c1f6c7ad6..000000000 --- a/receptorctl/.pip-tools.toml +++ /dev/null @@ -1,5 +0,0 @@ -[tool.pip-tools] -resolver = "backtracking" -allow-unsafe = true -strip-extras = true -quiet = true diff --git a/receptorctl/MANIFEST.in b/receptorctl/MANIFEST.in index 652d90d26..d818bca12 100644 --- a/receptorctl/MANIFEST.in +++ b/receptorctl/MANIFEST.in @@ -1,6 +1,3 @@ recursive-include receptorctl *.py -include .VERSION exclude .gitignore exclude noxfile.py -exclude build-requirements.txt -exclude test-requirements.txt diff --git a/receptorctl/README.md b/receptorctl/README.md index 8cea752ff..1b857ff4b 100644 --- a/receptorctl/README.md +++ b/receptorctl/README.md @@ -15,13 +15,7 @@ Before you submit a PR, you should install `nox` and verify your changes. You can run `nox` with no arguments to execute all checks and tests. Alternatively, you can run only certain tasks as outlined in the following sections. -> By default nox sessions install pinned dependencies from the `requirements` directory. - -You can use unpinned dependencies as follows: - -```bash -PINNED=false nox -s lint -``` +> By default nox sessions install pinned dependencies from `pyproject.toml`. ## Checking changes to Receptorctl diff --git a/receptorctl/noxfile.py b/receptorctl/noxfile.py index 9fc3198b8..941a003d6 100644 --- a/receptorctl/noxfile.py +++ b/receptorctl/noxfile.py @@ -10,46 +10,12 @@ python_versions = ["3.8", "3.9", "3.10", "3.11", "3.12"] LINT_FILES: tuple[str, ...] = (*iglob("**/*.py"),) -PINNED = os.environ.get("PINNED", "true").lower() in {"1", "true"} requirements_directory = Path("requirements").resolve() -requirements_files = [requirements_input_file_path.stem for requirements_input_file_path in requirements_directory.glob("*.in")] - -def install(session: nox.Session, *args, req: str, **kwargs): - if PINNED: - pip_constraint = requirements_directory / f"{req}.txt" - kwargs.setdefault("env", {})["PIP_CONSTRAINT"] = pip_constraint - session.log(f"export PIP_CONSTRAINT={pip_constraint!r}") - session.install("-r", f"{requirements_directory}/{req}.in", *args, **kwargs) - - -def version(session: nox.Session): - """ - Create a .VERSION file. - """ - try: - official_version = session.run_install( - "git", - "describe", - "--exact-match", - "--tags", - external=True, - stderr=subprocess.DEVNULL, - ) - except nox.command.CommandFailed: - official_version = None - print("Using the closest annotated tag instead of an exact match.") - - if official_version: - version = official_version.strip() - else: - tag = session.run_install("git", "describe", "--tags", "--always", silent=True, external=True) - rev = session.run_install("git", "rev-parse", "--short", "HEAD", silent=True, external=True) - version = tag.split("-")[0] + "+" + rev - - Path(".VERSION").write_text(version) +def install(session: nox.Session, *args, **kwargs): + session.install(".[test]", *args, **kwargs) @nox.session(python=LATEST_PYTHON_VERSION) @@ -57,8 +23,7 @@ def coverage(session: nox.Session): """ Run receptorctl tests with code coverage """ - install(session, req="tests") - version(session) + install(session) session.install("-e", ".") session.run( "pytest", @@ -78,8 +43,7 @@ def tests(session: nox.Session): """ Run receptorctl tests """ - install(session, req="tests") - version(session) + install(session) session.install("-e", ".") session.run("pytest", "-v", "tests", *session.posargs) @@ -89,8 +53,8 @@ def check_style(session: nox.Session): """ Check receptorctl Python code style """ - install(session, req="lint") - session.run("flake8", *session.posargs, *LINT_FILES) + install(session) + session.run("ruff", "check", *session.posargs, *LINT_FILES) @nox.session @@ -98,8 +62,8 @@ def check_format(session: nox.Session): """ Check receptorctl Python file formatting without making changes """ - install(session, req="lint") - session.run("black", "--check", *session.posargs, *LINT_FILES) + install(session) + session.run("ruff", "format", "--check", *session.posargs, *LINT_FILES) @nox.session @@ -107,8 +71,8 @@ def format(session: nox.Session): """ Format receptorctl Python files """ - install(session, req="lint") - session.run("black", *session.posargs, *LINT_FILES) + install(session) + session.run("ruff", "format", *session.posargs, *LINT_FILES) @nox.session @@ -121,23 +85,17 @@ def lint(session: nox.Session): @nox.session(name="pip-compile", python=["3.12"]) -@nox.parametrize(["req"], arg_values_list=requirements_files, ids=requirements_files) -def pip_compile(session: nox.Session, req: str): +def pip_compile(session: nox.Session): """Generate lock files from input files or upgrade packages in lock files.""" - # fmt: off - session.install( - "-r", str(requirements_directory / "pip-tools.in"), - "-c", str(requirements_directory / "pip-tools.txt"), - ) - # fmt: on + install(session) # Use --upgrade by default unless a user passes -P. upgrade_related_cli_flags = ("-P", "--upgrade-package", "--no-upgrade") has_upgrade_related_cli_flags = any(arg.startswith(upgrade_related_cli_flags) for arg in session.posargs) injected_extra_cli_args = () if has_upgrade_related_cli_flags else ("--upgrade",) - output_file = os.path.relpath(Path(requirements_directory / f"{req}.txt")) - input_file = os.path.relpath(Path(requirements_directory / f"{req}.in")) + output_file = os.path.relpath(Path(requirements_directory / "requirements.txt")) + input_file = "pyproject.toml" session.run( "pip-compile", diff --git a/receptorctl/pyproject.toml b/receptorctl/pyproject.toml index e1cddc27b..aa4558fc1 100644 --- a/receptorctl/pyproject.toml +++ b/receptorctl/pyproject.toml @@ -1,6 +1,51 @@ +[project] +name = "receptorctl" +authors = [{name = "Red Hat", email = "info@ansible.com"}] +description = "Receptorctl is a front-end CLI and importable Python library that interacts with Receptor over its control socket interface." +readme = "README.md" +dynamic = ["version"] +dependencies = [ + "python-dateutil>=2.8.1", + "click>=8.1.3, <8.2.0", + "PyYAML>=5.4.1", +] + +[project.license] +text = "Apache-2.0" + +[project.urls] +Homepage = "https://ansible.readthedocs.io/projects/receptor/" +Documentation = "https://ansible.readthedocs.io/projects/receptor/en/latest/" +Repository = "https://github.com/ansible/receptor" +Issues = "https://github.com/ansible/receptor/issues" + [build-system] -requires = ["setuptools"] +requires = ["setuptools>=75.3.2", "setuptools-scm>=7.1.0"] build-backend = "setuptools.build_meta" -[tool.black] -exclude = "(build|.eggs)" +[tool.setuptools_scm] +fallback_version = "0.0.0" + +[project.optional-dependencies] +test = [ + "coverage", + "pip-tools>=7", + "pytest", + "pytest-cov", + "ruff", +] + +[project.scripts] +receptorctl = "receptorctl:run" + +[tool.ruff] +line-length = 100 + +[tool.pip-tools] +resolver = "backtracking" +allow-unsafe = true +strip-extras = true +quiet = true + +[tool.coverage.run] +omit = ["tests/*"] \ No newline at end of file diff --git a/receptorctl/requirements/lint.in b/receptorctl/requirements/lint.in deleted file mode 100644 index 096539957..000000000 --- a/receptorctl/requirements/lint.in +++ /dev/null @@ -1,4 +0,0 @@ -# This requirements file is used for receptorctl lint and formatting checks - -flake8 # <-- used to check Python code style -black # <-- used to check Python code formatting diff --git a/receptorctl/requirements/lint.txt b/receptorctl/requirements/lint.txt deleted file mode 100644 index 2c2d8ec14..000000000 --- a/receptorctl/requirements/lint.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --allow-unsafe --output-file=requirements/lint.txt --strip-extras requirements/lint.in -# -black==24.10.0 - # via -r requirements/lint.in -click==8.1.7 - # via black -flake8==7.1.1 - # via -r requirements/lint.in -mccabe==0.7.0 - # via flake8 -mypy-extensions==1.0.0 - # via black -packaging==24.0 - # via black -pathspec==0.12.1 - # via black -platformdirs==4.2.2 - # via black -pycodestyle==2.12.0 - # via flake8 -pyflakes==3.2.0 - # via flake8 diff --git a/receptorctl/requirements/pip-tools.in b/receptorctl/requirements/pip-tools.in deleted file mode 100644 index 4af050690..000000000 --- a/receptorctl/requirements/pip-tools.in +++ /dev/null @@ -1,3 +0,0 @@ -# This requirements file is used to generate lock files for receptorctl - -pip-tools >= 7 # .pip-tools.toml was introduced in v6.14 diff --git a/receptorctl/requirements/pip-tools.txt b/receptorctl/requirements/pip-tools.txt deleted file mode 100644 index 99c2e6533..000000000 --- a/receptorctl/requirements/pip-tools.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --allow-unsafe --output-file=requirements/pip-tools.txt --strip-extras requirements/pip-tools.in -# -build==1.2.1 - # via pip-tools -click==8.1.7 - # via pip-tools -packaging==24.0 - # via build -pip-tools==7.4.1 - # via -r pip-tools.in -pyproject-hooks==1.1.0 - # via - # build - # pip-tools -wheel==0.43.0 - # via pip-tools - -# The following packages are considered to be unsafe in a requirements file: -pip==24.0 - # via pip-tools -setuptools==70.0.0 - # via pip-tools diff --git a/receptorctl/requirements/requirements.txt b/receptorctl/requirements/requirements.txt new file mode 100644 index 000000000..1d146ec3e --- /dev/null +++ b/receptorctl/requirements/requirements.txt @@ -0,0 +1,14 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --allow-unsafe --output-file=requirements/requirements.txt --strip-extras pyproject.toml +# +click==8.1.8 + # via receptorctl (pyproject.toml) +python-dateutil==2.9.0.post0 + # via receptorctl (pyproject.toml) +pyyaml==6.0.2 + # via receptorctl (pyproject.toml) +six==1.17.0 + # via python-dateutil diff --git a/receptorctl/requirements/tests.in b/receptorctl/requirements/tests.in deleted file mode 100644 index 87768a9a4..000000000 --- a/receptorctl/requirements/tests.in +++ /dev/null @@ -1,5 +0,0 @@ -# This requirements file is used for receptorctl tests - -pytest==8.3.3 # <-- used to run receptorctl tests -pytest-cov -coverage diff --git a/receptorctl/requirements/tests.txt b/receptorctl/requirements/tests.txt deleted file mode 100644 index d27cf76a2..000000000 --- a/receptorctl/requirements/tests.txt +++ /dev/null @@ -1,22 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --allow-unsafe --output-file=requirements/tests.txt --strip-extras requirements/tests.in -# -coverage==7.6.1 - # via - # -r requirements/tests.in - # pytest-cov -iniconfig==2.0.0 - # via pytest -packaging==24.0 - # via pytest -pluggy==1.5.0 - # via pytest -pytest==8.3.3 - # via - # -r requirements/tests.in - # pytest-cov -pytest-cov==5.0.0 - # via -r requirements/tests.in diff --git a/receptorctl/setup.cfg b/receptorctl/setup.cfg deleted file mode 100644 index 512fcb845..000000000 --- a/receptorctl/setup.cfg +++ /dev/null @@ -1,28 +0,0 @@ -[metadata] -name = receptorctl -author = Red Hat -author_email = info@ansible.com -summary = "Receptorctl is a front-end CLI and importable Python library that interacts with Receptor over its control socket interface." -home_page = https://ansible.readthedocs.io/projects/receptor/ -description_file = README.md -description_content_type = text/markdown -license = "Apache License 2.0" -version = file: .VERSION - -[options] -packages = find: -install_requires = - python-dateutil - click - pyyaml - -[options.entry_points] -console_scripts = - receptorctl = receptorctl:run - -[files] -packages = - receptorctl - -[flake8] -max-line-length = 100 diff --git a/tests/environments/container-builder/Containerfile b/tests/environments/container-builder/Containerfile index c86ed8f60..f036c8a4b 100644 --- a/tests/environments/container-builder/Containerfile +++ b/tests/environments/container-builder/Containerfile @@ -32,8 +32,7 @@ RUN set -x \ WORKDIR /dependencies ADD ./go.mod \ ./go.sum \ - ../receptorctl/requirements/tests.txt \ - ../receptorctl/requirements/tests.in ./ + ../receptorctl/requirements/requirements.txt ./ RUN set -x \ # Go && go get -u golang.org/x/lint/golint \ @@ -43,8 +42,7 @@ RUN set -x \ && source /opt/venv/bin/activate \ && pip${PYTHON_VERSION} install \ --upgrade \ - -r tests.in \ - -c tests.txt + -r requirements.txt ADD ./tests/environments/container-builder/build-artifacts.sh / From 0bbaed539e29e95f1b2651a0ac613022cad953f5 Mon Sep 17 00:00:00 2001 From: PabloHiro Date: Thu, 29 May 2025 12:26:18 +0200 Subject: [PATCH 09/21] feat: ruff lint --- receptorctl/receptorctl/cli.py | 16 ++++----------- receptorctl/receptorctl/socket_interface.py | 22 +++++++-------------- receptorctl/tests/conftest.py | 17 ++++------------ receptorctl/tests/lib.py | 11 ++--------- receptorctl/tests/test_cli.py | 4 +--- receptorctl/tests/test_workunit.py | 17 +++++----------- 6 files changed, 23 insertions(+), 64 deletions(-) diff --git a/receptorctl/receptorctl/cli.py b/receptorctl/receptorctl/cli.py index a34c6fc80..6b3df79b3 100644 --- a/receptorctl/receptorctl/cli.py +++ b/receptorctl/receptorctl/cli.py @@ -182,9 +182,7 @@ def status(ctx, printjson): ads = status.pop("Advertisements", None) if ads: print_message() - print_message( - f"{'Node':<{longest_node}} Service Type Last Seen Tags" - ) + print_message(f"{'Node':<{longest_node}} Service Type Last Seen Tags") for ad in ads: time = dateutil.parser.parse(ad["Time"]) if ad["ConnType"] == 0: @@ -238,9 +236,7 @@ def print_worktypes(header, isSecure): @click.pass_context @click.argument("node") @click.option("--count", default=4, help="Number of pings to send", show_default=True) -@click.option( - "--delay", default=1.0, help="Time to wait between pings", show_default=True -) +@click.option("--delay", default=1.0, help="Time to wait between pings", show_default=True) def ping(ctx, node, count, delay): rc = get_rc(ctx) ping_error = False @@ -251,9 +247,7 @@ def ping(ctx, node, count, delay): else: ping_error = True if "From" in results and "TimeStr" in results: - print_error( - f"{results['Error']} from {results['From']} in {results['TimeStr']}" - ) + print_error(f"{results['Error']} from {results['From']} in {results['TimeStr']}") else: print_error(f"{results['Error']}") if i < count - 1: @@ -299,9 +293,7 @@ def traceroute(ctx, node): @click.pass_context @click.argument("node") @click.argument("service") -@click.option( - "--raw", "-r", default=False, is_flag=True, help="Set terminal to raw mode" -) +@click.option("--raw", "-r", default=False, is_flag=True, help="Set terminal to raw mode") @click.option( "--tls-client", "tlsclient", diff --git a/receptorctl/receptorctl/socket_interface.py b/receptorctl/receptorctl/socket_interface.py index 79a4e7f2f..107905887 100644 --- a/receptorctl/receptorctl/socket_interface.py +++ b/receptorctl/receptorctl/socket_interface.py @@ -85,9 +85,9 @@ def simple_command(self, command): def connect(self): if self._socket is not None: return - m = re.compile( - "(tcp|tls):(//)?([a-zA-Z0-9-.:]+):([0-9]+)|(unix:(//)?)?([^:]+)" - ).fullmatch(self._socketaddress) + m = re.compile("(tcp|tls):(//)?([a-zA-Z0-9-.:]+):([0-9]+)|(unix:(//)?)?([^:]+)").fullmatch( + self._socketaddress + ) if m: unixsocket = m[7] host = m[3] @@ -128,17 +128,13 @@ def connect(self): context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 if self._key and self._cert: - context.load_cert_chain( - certfile=self._cert, keyfile=self._key - ) + context.load_cert_chain(certfile=self._cert, keyfile=self._key) if not self._insecureskipverify: context.check_hostname = True else: context.check_hostname = False - self._socket = context.wrap_socket( - self._socket, server_hostname=host - ) + self._socket = context.wrap_socket(self._socket, server_hostname=host) self._socket.connect(sockaddr) except OSError: self._socket.close() @@ -223,9 +219,7 @@ def submit_work( command = f"{commandJson}\n" self.writestr(command) text = self.readstr() - m = re.compile( - "Work unit created with ID (.+). Send stdin data and EOF." - ).fullmatch(text) + m = re.compile("Work unit created with ID (.+). Send stdin data and EOF.").fullmatch(text) if not m: errmsg = "Failed to start work unit" if str.startswith(text, "ERROR: "): @@ -248,9 +242,7 @@ def submit_work( result = json.loads(text) return result - def get_work_results( - self, unit_id, startpos=0, return_socket=False, return_sockfile=True - ): + def get_work_results(self, unit_id, startpos=0, return_socket=False, return_sockfile=True): self.connect() self.writestr(f"work results {unit_id} {startpos}\n") text = self.readstr() diff --git a/receptorctl/tests/conftest.py b/receptorctl/tests/conftest.py index bc1811364..72edaf502 100644 --- a/receptorctl/tests/conftest.py +++ b/receptorctl/tests/conftest.py @@ -73,9 +73,7 @@ def __change_config_files_dir(self, mesh_name: str): self.config_files.append(os.path.join(self.config_files_dir, f)) def __create_certificates(self): - self.certificate_files = create_certificate( - self.get_mesh_tmp_dir(), "node1" - ) + self.certificate_files = create_certificate(self.get_mesh_tmp_dir(), "node1") def get_mesh_name(self): return self.config_files_dir.split("/")[-1] @@ -93,10 +91,7 @@ def __check_dependencies(self): try: subprocess.check_output(["openssl", "version"]) except FileNotFoundError: - raise Exception( - "openssl binary not found\n" - 'Consider run "sudo dnf install openssl"' - ) + raise Exception('openssl binary not found\nConsider run "sudo dnf install openssl"') def __create_tmp_dir(self): mesh_tmp_dir_path = self.get_mesh_tmp_dir() @@ -149,9 +144,7 @@ def receptor_bin_path(): subprocess.check_output(["receptor", "--version"]) return "receptor" except subprocess.CalledProcessError: - raise Exception( - "Receptor binary not found in $PATH or in '../../tests/artifacts-output'" - ) + raise Exception("Receptor binary not found in $PATH or in '../../tests/artifacts-output'") @pytest.fixture(scope="class") @@ -255,9 +248,7 @@ def default_receptor_controller_unix(receptor_mesh): def start_nodes(receptor_mesh, receptor_nodes, receptor_bin_path): for i, config_file in enumerate(receptor_mesh.config_files): - log_file_name = ( - config_file.split("/")[-1].replace(".yaml", ".log").replace(".yml", ".log") - ) + log_file_name = config_file.split("/")[-1].replace(".yaml", ".log").replace(".yml", ".log") receptor_nodes.log_files.append( open( os.path.join(receptor_mesh.get_mesh_tmp_dir(), log_file_name), diff --git a/receptorctl/tests/lib.py b/receptorctl/tests/lib.py index fcd8bebd0..c5b1f027d 100644 --- a/receptorctl/tests/lib.py +++ b/receptorctl/tests/lib.py @@ -44,12 +44,7 @@ def generate_cert_with_ca(name, caKeyPath, caCrtPath, commonName): ext.write("subjectAltName=DNS:" + commonName) # Receptor NodeID (otherName) to SAN ext.write( - ",otherName:" - + __OIDReceptorName - + ";" - + __OIDReceptorNameFormat - + ":" - + commonName + ",otherName:" + __OIDReceptorName + ";" + __OIDReceptorNameFormat + ":" + commonName ) ext.close() subprocess.check_output(["openssl", "genrsa", "-out", keyPath, "2048"]) @@ -95,9 +90,7 @@ def generate_cert_with_ca(name, caKeyPath, caCrtPath, commonName): # Create a new CA caKeyPath, caCrtPath = generate_cert("ca", "ca") - clientKeyPath, clientCrtPath = generate_cert_with_ca( - "client", caKeyPath, caCrtPath, commonName - ) + clientKeyPath, clientCrtPath = generate_cert_with_ca("client", caKeyPath, caCrtPath, commonName) generate_cert_with_ca("server", caKeyPath, caCrtPath, commonName) return { diff --git a/receptorctl/tests/test_cli.py b/receptorctl/tests/test_cli.py index 93e35529f..42ab5a110 100644 --- a/receptorctl/tests/test_cli.py +++ b/receptorctl/tests/test_cli.py @@ -22,6 +22,4 @@ def test_cli_cmd_status(self, invoke_as_json): "SystemMemoryMiB", "Version", ] - ) == set( - json_output.keys() - ), "The command returned unexpected keys from json output" + ) == set(json_output.keys()), "The command returned unexpected keys from json output" diff --git a/receptorctl/tests/test_workunit.py b/receptorctl/tests/test_workunit.py index 2c26a6724..af0ac8f32 100644 --- a/receptorctl/tests/test_workunit.py +++ b/receptorctl/tests/test_workunit.py @@ -62,9 +62,7 @@ def _wait_for_workunit_state( @pytest.fixture(scope="function") def wait_for_work_finished(wait_for_workunit_state): - def _wait_for_work_finished( - node_controller, unitid: str, timeout_seconds: int = 30 - ) -> bool: + def _wait_for_work_finished(node_controller, unitid: str, timeout_seconds: int = 30) -> bool: """Wait for a workunit to finish Args: @@ -106,17 +104,12 @@ def test_workunit_simple( state_unitid = work.pop("unitid") assert state_result == "Job Started" - assert wait_for_work_finished( - node1_controller, state_unitid, wait_for - ), "Workunit timed out and never finished" - - work_result = ( - node1_controller.get_work_results(state_unitid) - .read() - .decode("utf-8") - .strip() + assert wait_for_work_finished(node1_controller, state_unitid, wait_for), ( + "Workunit timed out and never finished" ) + work_result = node1_controller.get_work_results(state_unitid).read().decode("utf-8").strip() + assert payload.upper() == work_result, ( f"Workunit did not report the expected result:\n - payload: {payload}" f"\n - work_result: {work_result}" From d2915fd7e047d4ae4ea57e8606a7d30d0758ddd2 Mon Sep 17 00:00:00 2001 From: Andrea Restle-Lay Date: Fri, 30 May 2025 12:46:15 -0400 Subject: [PATCH 10/21] AAP-45811 Job_context.go Unit Test (#1328) Co-authored-by: Pablo H. --- pkg/utils/job_context_test.go | 124 ++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/pkg/utils/job_context_test.go b/pkg/utils/job_context_test.go index be051d0b1..6d669c1d8 100644 --- a/pkg/utils/job_context_test.go +++ b/pkg/utils/job_context_test.go @@ -2,6 +2,7 @@ package utils_test import ( "context" + "fmt" "reflect" "sync" "testing" @@ -10,6 +11,9 @@ import ( "github.com/ansible/receptor/pkg/utils" ) +// Time for a job to stop running. +const jobFinishTimeout = 500 * time.Millisecond + type fields struct { Ctx context.Context JcCancel context.CancelFunc @@ -201,3 +205,123 @@ func TestJobContextWait(t *testing.T) { }) } } + +func TestJobContext_NewJob(t *testing.T) { + t.Run("Start new job when none running", func(t *testing.T) { + jc := &utils.JobContext{} + success := jc.NewJob(context.Background(), 1, true) + if !success { + t.Errorf("Expected to start new job, but failed") + } + if !jc.Running() { + t.Errorf("Expected job context to be running") + } + jc.WorkerDone() + jc.Wait() + }) + + t.Run("Prevent new job when already running and returnIfRunning=true, meaning don't replace", func(t *testing.T) { + jc := &utils.JobContext{} + ctx := context.Background() + started := jc.NewJob(ctx, 1, true) + if !started { + t.Fatalf("Failed to start initial job") + } + defer jc.WorkerDone() + replaced := jc.NewJob(ctx, 1, true) + if replaced { + t.Errorf("Expected NewJob to return false when already running and returnIfRunning=true") + } + }) + + t.Run("Replace running job when returnIfRunning=false, meaning replace the workers with new ones.", func(t *testing.T) { + jc := &utils.JobContext{} + ctx := context.Background() + + if !jc.NewJob(ctx, 1, true) { + t.Fatalf("Failed to start initial job") + } + initialWg := jc.Wg + + jc.WorkerDone() + jc.Wait() // but not for finished job + if !jc.Running() { + t.Errorf("Expected job context to be running after initial job done") + } + + replaced := jc.NewJob(ctx, 2, false) // Start a new job with 2 workers, specifying that you want them replaced. + if !replaced { + t.Errorf("Expected NewJob to replace current job when returnIfRunning=false") + } + + if jc.Wg == initialWg { + t.Errorf("Expected WaitGroup to be replaced on new job, but it's the same instance") + } + + jc.WorkerDone() + jc.WorkerDone() + jc.Wait() + if err := WaitUntilFinished(jc, jobFinishTimeout); err != nil { + t.Fatalf("JobContext did not finish in time: %v", err) + } + + if jc.Running() { + t.Errorf("Expected job context to not be running after workers done") + } + }) +} + +func TestWaitUntilFinishedTimeout(t *testing.T) { + jc := &utils.JobContext{} + + startHungJob(jc) + + timeout := 10 * time.Millisecond + start := time.Now() + err := WaitUntilFinished(jc, timeout) + elapsed := time.Since(start) + + if err == nil || err.Error() != "timeout" { + t.Errorf("expected timeout error, got: %v", err) + } + + if elapsed < timeout { + t.Errorf("expected to wait at least %v, waited only %v", timeout, elapsed) + } +} + +func startHungJob(jc *utils.JobContext) { + ctx := context.Background() + ok := jc.NewJob(ctx, 1, false) // spawn 1 worker, allow override = false + if !ok { + panic("failed to start hung job") + } + + go func() { + // Simulate a never-ending job (never calls WorkerDone) + select {} // or: <-ctx.Done() + }() +} + +// WaitUntilFinished waits until the JobContext is no longer running, or until timeout. +// It polls the Running() state using a small delay to avoid a tight loop. +// In order to simulate a hung job, we do not call mw.Wait() here, as that would block indefinitely. +// If the job is not finished within the timeout, it returns an error. +// The thought being that this could be used to record jobs that do not finish in a reasonable time. +// These jobs may be stuck and could be recorded in a database or log messages for later investigation. +// Out of scope for this unit test ticket so recording here for later work. +func WaitUntilFinished(mw *utils.JobContext, timeout time.Duration) error { + done := make(chan struct{}) + go func() { + for mw.Running() { + time.Sleep(1 * time.Millisecond) + } + close(done) + }() + select { + case <-done: + return nil + case <-time.After(timeout): + return fmt.Errorf("timeout") + } +} From c54c9247cfc2dfedd9e994bff7e5130d34be17f8 Mon Sep 17 00:00:00 2001 From: Lisa Ranjbar Miller Date: Fri, 30 May 2025 10:40:11 -0700 Subject: [PATCH 11/21] Migrate mock generation to go generate (#1297) --- CONTRIBUTING.md | 83 ++++++++++ Makefile | 7 + docs/source/developer_guide.rst | 23 ++- generate.go | 24 +++ pkg/certificates/ca.go | 47 +----- .../mock_certificates/{Oser.go => oser.go} | 21 +-- .../mock_certificates/{Rsaer.go => rsaer.go} | 5 +- pkg/certificates/oser.go | 25 +++ pkg/certificates/rsaer.go | 19 +++ pkg/controlsvc/controlsvc_test.go | 25 +-- pkg/controlsvc/mock_controlsvc/controlsvc.go | 8 +- pkg/controlsvc/mock_controlsvc/interfaces.go | 16 +- pkg/controlsvc/mock_controlsvc/io.go | 84 ---------- pkg/services/command.go | 1 - .../mock_interfaces/net_interfaces.go | 14 +- pkg/services/mock_services/command.go | 5 +- pkg/services/mock_services/tcp_proxy.go | 18 ++- pkg/services/mock_services/udp_proxy.go | 12 +- pkg/services/tcp_proxy.go | 2 - .../mock_net.go => utils/mock_utils/net.go} | 152 +++++++++--------- pkg/utils/net.go | 15 ++ .../{baseworkunit.go => command.go} | 4 +- 22 files changed, 348 insertions(+), 262 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 generate.go rename pkg/certificates/mock_certificates/{Oser.go => oser.go} (68%) rename pkg/certificates/mock_certificates/{Rsaer.go => rsaer.go} (90%) create mode 100644 pkg/certificates/oser.go create mode 100644 pkg/certificates/rsaer.go delete mode 100644 pkg/controlsvc/mock_controlsvc/io.go rename pkg/{controlsvc/mock_controlsvc/mock_net.go => utils/mock_utils/net.go} (53%) create mode 100644 pkg/utils/net.go rename pkg/workceptor/mock_workceptor/{baseworkunit.go => command.go} (99%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..d9401593e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,83 @@ +# Receptor + +Hi there! We're excited to have you as a contributor. + +Have questions about this document or anything not covered here? Create a topic using the [AAP tag on the Ansible Forum](https://forum.ansible.com/tag/aap). + +## Table of contents + +- [Things to know prior to submitting code](#things-to-know-prior-to-submitting-code) +- [Setting up your development environment](#setting-up-your-development-environment) + - [Fork and clone the Receptor repo](#fork-and-clone-the-receptor-repo) + - [Development Requirements](#development-requirements) + - [Build and Run the Development Environment](#build-and-run-the-development-environment) +- [What should I work on?](#what-should-i-work-on) +- [Submitting Pull Requests](#submitting-pull-requests) +- [Reporting Issues](#reporting-issues) +- [Getting Help](#getting-help) + +## Things to know prior to submitting code + +- All code submissions are done through pull requests against the `devel` branch. +- You must use `git commit --signoff` for any commit to be merged, and agree that usage of --signoff constitutes agreement with the terms of [DCO 1.1](./DCO_1_1.md). +- Take care to make sure no merge commits are in the submission, and use `git rebase` vs `git merge` for this reason. + - If collaborating with someone else on the same branch, consider using `--force-with-lease` instead of `--force`. This will prevent you from accidentally overwriting commits pushed by someone else. For more information, see [git push docs](https://git-scm.com/docs/git-push#git-push---force-with-leaseltrefnamegt). +- If submitting a large code change, it's a good idea to create a [forum topic tagged with 'aap'](https://forum.ansible.com/tag/aap), and talk about what you would like to do or add first. This not only helps everyone know what's going on, it also helps save time and effort, if the community decides some changes are needed. +- We ask all of our community members and contributors to adhere to the [Ansible code of conduct](http://docs.ansible.com/ansible/latest/community/code_of_conduct.html). If you have questions, or need assistance, please reach out to our community team at [codeofconduct@ansible.com](mailto:codeofconduct@ansible.com) + +## Setting up your development environment + +Our team uses [VS Code](https://code.visualstudio.com/) with the [Golang extension](https://marketplace.visualstudio.com/items?itemName=golang.Go) installed for our development environments. The instrustions below will show how to set up an environment using this tool set. + +### Fork and clone the Receptor repo + +If you have not done so already, you'll need to fork the Receptor repo on GitHub. For more on how to do this, see [Fork a Repo](https://help.github.com/articles/fork-a-repo/). + +### Development Requirements + +- [Git](https://git-scm.com/book/en/v2) +- [Golang](https://go.dev/doc/install) +- [kind](https://kind.sigs.k8s.io/docs/user/quick-start/) +- [make](https://www.gnu.org/software/make/manual/make.html) + +### Build and Run the Development Environment + +#### Building Receptor +`make build-all` + +#### Running tests +`make test` + +## What should I work on? + +We have a ["good first issue" label](https://github.com/ansible/receptor/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) we put on some issues that might be a good starting point for new contributors. + +Fixing bugs and updating the documentation are always appreciated, so reviewing the backlog of issues is always a good place to start. + +## Submitting Pull Requests + +Fixes and Features for Receptor will go through the Github pull request process. Submit your pull request (PR) against the `devel` branch. + +Here are a few things you can do to help the visibility of your change, and increase the likelihood that it will be accepted: + +- No issues when running linters/code checkers +- No issues from unit tests +- Write tests for new functionality, update/add tests for bug fixes +- Make the smallest change possible +- Write good commit messages. See [How to write a Git commit message](https://chris.beams.io/posts/git-commit/). + + +We like to keep our commit history clean, and will require resubmission of pull requests that contain merge commits. Use `git pull --rebase`, rather than +`git pull`, and `git rebase`, rather than `git merge`. + +Sometimes it might take us a while to fully review your PR. We try to keep the `devel` branch in good working order, and so we review requests carefully. Please be patient. + +When your PR is initially submitted the checks will not be run until a maintainer allows them to be. Once a maintainer has done a quick review of your work the PR will have the linter and unit tests run against them via GitHub Actions, and the status reported in the PR. + +## Reporting Issues + +We welcome your feedback, and encourage you to file an issue when you run into a problem. + +## Getting Help + +If you require additional assistance, please submit your question to the [Ansible Forum](https://forum.ansible.com/tag/aap). diff --git a/Makefile b/Makefile index f98268a47..14c23d33d 100644 --- a/Makefile +++ b/Makefile @@ -106,6 +106,13 @@ format: fmt: format +generate: + ${GO} generate ./... + +generate-clean: + @echo "Removing existing mocks" + @find . -type d -name 'mock*' -prune -exec rm -rf {} + + pre-commit: @pre-commit run --all-files diff --git a/docs/source/developer_guide.rst b/docs/source/developer_guide.rst index c2924d4d3..1a0381fcd 100644 --- a/docs/source/developer_guide.rst +++ b/docs/source/developer_guide.rst @@ -24,7 +24,7 @@ Example: .. code-block:: bash - echo -e '{"command": "work", "subcommand": "submit", "node": "execution", "worktype": "cat", }\n"Hi"' | socat - UNIX-CONNECT:/tmp/control.sock + echo -e '{"command": "work", "subcommand": "submit", "node": "execution", "worktype": "cat", }\n"Hi"' | socat - UNIX-CONNECT:/tmp/control.sock ------- Linters @@ -59,7 +59,15 @@ Add/Update tests for bug fixes. Mocking ^^^^^^^ -We are using gomock to generate mocks for our unit tests. The mocks are living inside of a package under the real implementation, prefixed by ``mock_``. An example is the package mock_workceptor under pkg/workceptor. +We are using mockgen to generate mocks for our unit tests. The mocks are living inside of a package under the real implementation, prefixed by ``mock_``. An example is the package mock_workceptor under pkg/workceptor. + +We use go generate to generate our mocks. To generate all the mocks in receptor: + +.. code-block:: bash + + make generate + +To add a mock to go generate you will need to add a line to the `generate.go` file in the root of the repository. In order to genenerate a mock for a particular file, run: @@ -73,6 +81,17 @@ For example, to create/update mocks for Workceptor, we can run: mockgen -source=pkg/workceptor/workceptor.go -destination=pkg/workceptor/mock_workceptor/workceptor.go +After validating the mockgen command generates the mocks correctly you can add this command to the `generate.go` file. + +To remove a mock you will need to remove the associated line in the `generate.go` file. + +Then the following commands to generate a fresh set of mocks: + +.. code-block:: bash + + make generate-clean + make generate + ^^^^^^^^^^ Kubernetes ^^^^^^^^^^ diff --git a/generate.go b/generate.go new file mode 100644 index 000000000..b31ce1144 --- /dev/null +++ b/generate.go @@ -0,0 +1,24 @@ +package main + +//go:generate mockgen -source=pkg/backends/websockets.go -destination=pkg/backends/mock_backends/websockets.go +//go:generate mockgen -source=pkg/certificates/oser.go -destination=pkg/certificates/mock_certificates/oser.go +//go:generate mockgen -source=pkg/certificates/rsaer.go -destination=pkg/certificates/mock_certificates/rsaer.go +//go:generate mockgen -source=pkg/controlsvc/controlsvc.go -destination=pkg/controlsvc/mock_controlsvc/controlsvc.go +//go:generate mockgen -source=pkg/controlsvc/interfaces.go -destination=pkg/controlsvc/mock_controlsvc/interfaces.go +//go:generate mockgen -source=pkg/framer/framer.go -destination=pkg/framer/mock_framer/framer.go +//go:generate mockgen -source=pkg/netceptor/conn.go -destination=pkg/netceptor/mock_netceptor/conn.go +//go:generate mockgen -source=pkg/netceptor/external_backend.go -destination=pkg/netceptor/mock_netceptor/external_backend.go +//go:generate mockgen -source=pkg/netceptor/netceptor.go -destination=pkg/netceptor/mock_netceptor/netceptor.go +//go:generate mockgen -source=pkg/netceptor/packetconn.go -destination=pkg/netceptor/mock_netceptor/packetconn.go +//go:generate mockgen -source=pkg/netceptor/ping.go -destination=pkg/netceptor/mock_netceptor/ping.go +//go:generate mockgen -source=pkg/services/interfaces/net_interfaces.go -destination=pkg/services/interfaces/mock_interfaces/net_interfaces.go +//go:generate mockgen -source=pkg/services/command.go -destination=pkg/services/mock_services/command.go +//go:generate mockgen -source=pkg/services/tcp_proxy.go -destination=pkg/services/mock_services/tcp_proxy.go +//go:generate mockgen -source=pkg/services/udp_proxy.go -destination=pkg/services/mock_services/udp_proxy.go +//go:generate mockgen -source=pkg/utils/net.go -destination=pkg/utils/mock_utils/net.go +//go:generate mockgen -source=pkg/workceptor/command.go -destination=pkg/workceptor/mock_workceptor/command.go +//go:generate mockgen -source=pkg/workceptor/interfaces.go -destination=pkg/workceptor/mock_workceptor/interfaces.go +//go:generate mockgen -source=pkg/workceptor/kubernetes.go -destination=pkg/workceptor/mock_workceptor/kubernetes.go +//go:generate mockgen -source=pkg/workceptor/stdio_utils.go -destination=pkg/workceptor/mock_workceptor/stdio_utils.go +//go:generate mockgen -source=pkg/workceptor/workceptor.go -destination=pkg/workceptor/mock_workceptor/workceptor.go +//go:generate mockgen -source=pkg/workceptor/workunitbase.go -destination=pkg/workceptor/mock_workceptor/workunitbase.go diff --git a/pkg/certificates/ca.go b/pkg/certificates/ca.go index 41c9063c4..4b5035674 100644 --- a/pkg/certificates/ca.go +++ b/pkg/certificates/ca.go @@ -11,49 +11,14 @@ import ( "crypto/x509/pkix" "encoding/pem" "fmt" - "io" - "io/fs" "math/big" "net" - "os" "strings" "time" "github.com/ansible/receptor/pkg/utils" ) -// Oser is the function calls interfaces for mocking os. -type Oser interface { - ReadFile(name string) ([]byte, error) - WriteFile(name string, data []byte, perm fs.FileMode) error -} - -// OsWrapper is the Wrapper structure for Oser. -type OsWrapper struct{} - -// ReadFile for Oser defaults to os library call. -func (ow *OsWrapper) ReadFile(name string) ([]byte, error) { - return os.ReadFile(name) -} - -// WriteFile for Oser defaults to os library call. -func (ow *OsWrapper) WriteFile(name string, data []byte, perm fs.FileMode) error { - return os.WriteFile(name, data, perm) -} - -// Rsaer is the function calls interface for mocking rsa. -type Rsaer interface { - GenerateKey(random io.Reader, bits int) (*rsa.PrivateKey, error) -} - -// RsaWrapper is the Wrapper structure for Rsaer. -type RsaWrapper struct{} - -// GenerateKey for RsaWrapper defaults to rsa library call. -func (rw *RsaWrapper) GenerateKey(random io.Reader, bits int) (*rsa.PrivateKey, error) { - return rsa.GenerateKey(random, bits) -} - // CertNames lists the subjectAltNames that can be assigned to a certificate or request. type CertNames struct { DNSNames []string @@ -70,6 +35,12 @@ type CertOptions struct { NotAfter time.Time } +// CA contains internal data for a certificate authority. +type CA struct { + Certificate *x509.Certificate + PrivateKey *rsa.PrivateKey +} + // LoadFromPEMFile loads certificate data from a PEM file. func LoadFromPEMFile(filename string, osWrapper Oser) ([]interface{}, error) { content, err := Oser.ReadFile(osWrapper, filename) @@ -270,12 +241,6 @@ func LoadPublicKey(filename string, osWrapper Oser) (*rsa.PublicKey, error) { return key, nil } -// CA contains internal data for a certificate authority. -type CA struct { - Certificate *x509.Certificate - PrivateKey *rsa.PrivateKey -} - // CreateCA initializes a new CertKeyPair from given parameters. func CreateCA(opts *CertOptions, rsaWrapper Rsaer) (*CA, error) { if opts.CommonName == "" { diff --git a/pkg/certificates/mock_certificates/Oser.go b/pkg/certificates/mock_certificates/oser.go similarity index 68% rename from pkg/certificates/mock_certificates/Oser.go rename to pkg/certificates/mock_certificates/oser.go index 59a4f1611..1dacda79e 100644 --- a/pkg/certificates/mock_certificates/Oser.go +++ b/pkg/certificates/mock_certificates/oser.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/ansible/receptor/pkg/certificates (interfaces: Oser) +// Source: pkg/certificates/oser.go // // Generated by this command: // -// mockgen -destination=mock_certificates/Oser.go github.com/ansible/receptor/pkg/certificates Oser +// mockgen -source=pkg/certificates/oser.go -destination=pkg/certificates/mock_certificates/oser.go // // Package mock_certificates is a generated GoMock package. @@ -20,6 +20,7 @@ import ( type MockOser struct { ctrl *gomock.Controller recorder *MockOserMockRecorder + isgomock struct{} } // MockOserMockRecorder is the mock recorder for MockOser. @@ -40,30 +41,30 @@ func (m *MockOser) EXPECT() *MockOserMockRecorder { } // ReadFile mocks base method. -func (m *MockOser) ReadFile(arg0 string) ([]byte, error) { +func (m *MockOser) ReadFile(name string) ([]byte, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ReadFile", arg0) + ret := m.ctrl.Call(m, "ReadFile", name) ret0, _ := ret[0].([]byte) ret1, _ := ret[1].(error) return ret0, ret1 } // ReadFile indicates an expected call of ReadFile. -func (mr *MockOserMockRecorder) ReadFile(arg0 any) *gomock.Call { +func (mr *MockOserMockRecorder) ReadFile(name any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadFile", reflect.TypeOf((*MockOser)(nil).ReadFile), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadFile", reflect.TypeOf((*MockOser)(nil).ReadFile), name) } // WriteFile mocks base method. -func (m *MockOser) WriteFile(arg0 string, arg1 []byte, arg2 fs.FileMode) error { +func (m *MockOser) WriteFile(name string, data []byte, perm fs.FileMode) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "WriteFile", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "WriteFile", name, data, perm) ret0, _ := ret[0].(error) return ret0 } // WriteFile indicates an expected call of WriteFile. -func (mr *MockOserMockRecorder) WriteFile(arg0, arg1, arg2 any) *gomock.Call { +func (mr *MockOserMockRecorder) WriteFile(name, data, perm any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteFile", reflect.TypeOf((*MockOser)(nil).WriteFile), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteFile", reflect.TypeOf((*MockOser)(nil).WriteFile), name, data, perm) } diff --git a/pkg/certificates/mock_certificates/Rsaer.go b/pkg/certificates/mock_certificates/rsaer.go similarity index 90% rename from pkg/certificates/mock_certificates/Rsaer.go rename to pkg/certificates/mock_certificates/rsaer.go index eac5479de..b9f352fe8 100644 --- a/pkg/certificates/mock_certificates/Rsaer.go +++ b/pkg/certificates/mock_certificates/rsaer.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: ../ca.go +// Source: pkg/certificates/rsaer.go // // Generated by this command: // -// mockgen --source=../ca.go +// mockgen -source=pkg/certificates/rsaer.go -destination=pkg/certificates/mock_certificates/rsaer.go // // Package mock_certificates is a generated GoMock package. @@ -21,6 +21,7 @@ import ( type MockRsaer struct { ctrl *gomock.Controller recorder *MockRsaerMockRecorder + isgomock struct{} } // MockRsaerMockRecorder is the mock recorder for MockRsaer. diff --git a/pkg/certificates/oser.go b/pkg/certificates/oser.go new file mode 100644 index 000000000..33ae16a3e --- /dev/null +++ b/pkg/certificates/oser.go @@ -0,0 +1,25 @@ +package certificates + +import ( + "io/fs" + "os" +) + +// Oser is the function calls interfaces for mocking os. +type Oser interface { + ReadFile(name string) ([]byte, error) + WriteFile(name string, data []byte, perm fs.FileMode) error +} + +// OsWrapper is the Wrapper structure for Oser. +type OsWrapper struct{} + +// ReadFile for Oser defaults to os library call. +func (ow *OsWrapper) ReadFile(name string) ([]byte, error) { + return os.ReadFile(name) +} + +// WriteFile for Oser defaults to os library call. +func (ow *OsWrapper) WriteFile(name string, data []byte, perm fs.FileMode) error { + return os.WriteFile(name, data, perm) +} diff --git a/pkg/certificates/rsaer.go b/pkg/certificates/rsaer.go new file mode 100644 index 000000000..b84175c20 --- /dev/null +++ b/pkg/certificates/rsaer.go @@ -0,0 +1,19 @@ +package certificates + +import ( + "crypto/rsa" + "io" +) + +// Rsaer is the function calls interface for mocking rsa. +type Rsaer interface { + GenerateKey(random io.Reader, bits int) (*rsa.PrivateKey, error) +} + +// RsaWrapper is the Wrapper structure for Rsaer. +type RsaWrapper struct{} + +// GenerateKey for RsaWrapper defaults to rsa library call. +func (rw *RsaWrapper) GenerateKey(random io.Reader, bits int) (*rsa.PrivateKey, error) { + return rsa.GenerateKey(random, bits) +} diff --git a/pkg/controlsvc/controlsvc_test.go b/pkg/controlsvc/controlsvc_test.go index 6deea0f00..46e38cfaa 100644 --- a/pkg/controlsvc/controlsvc_test.go +++ b/pkg/controlsvc/controlsvc_test.go @@ -12,6 +12,7 @@ import ( "github.com/ansible/receptor/pkg/controlsvc" "github.com/ansible/receptor/pkg/controlsvc/mock_controlsvc" "github.com/ansible/receptor/pkg/logger" + "github.com/ansible/receptor/pkg/utils/mock_utils" "go.uber.org/mock/gomock" ) @@ -26,7 +27,7 @@ func printErrorMessage(t *testing.T, err error) { func TestConnectionListener(t *testing.T) { ctrl := gomock.NewController(t) mockNetceptor := mock_controlsvc.NewMockNetceptorForControlsvc(ctrl) - mockListener := mock_controlsvc.NewMockListener(ctrl) + mockListener := mock_utils.NewMockNetListener(ctrl) logger := logger.NewReceptorLogger("") connectionListenerTestCases := []struct { @@ -63,7 +64,7 @@ func TestConnectionListener(t *testing.T) { func TestSetupConnection(t *testing.T) { ctrl := gomock.NewController(t) mockNetceptor := mock_controlsvc.NewMockNetceptorForControlsvc(ctrl) - mockConn := mock_controlsvc.NewMockConn(ctrl) + mockConn := mock_utils.NewMockNetConn(ctrl) logger := logger.NewReceptorLogger("") setupConnectionTestCases := []struct { @@ -104,7 +105,7 @@ func TestRunControlSvc(t *testing.T) { mockUnix := mock_controlsvc.NewMockUtiler(ctrl) mockNet := mock_controlsvc.NewMockNeter(ctrl) mockTLS := mock_controlsvc.NewMockTlser(ctrl) - mockListener := mock_controlsvc.NewMockListener(ctrl) + mockListener := mock_utils.NewMockNetListener(ctrl) logger := logger.NewReceptorLogger("") @@ -199,8 +200,8 @@ func TestRunControlSvc(t *testing.T) { func TestSockControlRemoteAddr(t *testing.T) { ctrl := gomock.NewController(t) - mockCon := mock_controlsvc.NewMockConn(ctrl) - mockAddr := mock_controlsvc.NewMockAddr(ctrl) + mockCon := mock_utils.NewMockNetConn(ctrl) + mockAddr := mock_utils.NewMockNetAddr(ctrl) sockControl := controlsvc.NewSockControl(mockCon) localhost := "127.0.0.1" @@ -216,7 +217,7 @@ func TestSockControlRemoteAddr(t *testing.T) { func TestSockControlWriteMessage(t *testing.T) { ctrl := gomock.NewController(t) - mockCon := mock_controlsvc.NewMockConn(ctrl) + mockCon := mock_utils.NewMockNetConn(ctrl) sockControl := controlsvc.NewSockControl(mockCon) writeMessageTestCases := []struct { @@ -269,7 +270,7 @@ func TestSockControlWriteMessage(t *testing.T) { func TestSockControlBridgeConn(t *testing.T) { ctrl := gomock.NewController(t) - mockCon := mock_controlsvc.NewMockConn(ctrl) + mockCon := mock_utils.NewMockNetConn(ctrl) mockUtil := mock_controlsvc.NewMockUtiler(ctrl) logger := logger.NewReceptorLogger("") @@ -313,7 +314,7 @@ func TestSockControlBridgeConn(t *testing.T) { func TestSockControlReadFromConn(t *testing.T) { ctrl := gomock.NewController(t) - mockCon := mock_controlsvc.NewMockConn(ctrl) + mockCon := mock_utils.NewMockNetConn(ctrl) mockCopier := mock_controlsvc.NewMockCopier(ctrl) sockControl := controlsvc.NewSockControl(mockCon) @@ -372,7 +373,7 @@ func TestSockControlReadFromConn(t *testing.T) { func TestSockControlWriteToConn(t *testing.T) { ctrl := gomock.NewController(t) - mockCon := mock_controlsvc.NewMockConn(ctrl) + mockCon := mock_utils.NewMockNetConn(ctrl) sockControl := controlsvc.NewSockControl(mockCon) bridgeConnTestCases := []struct { @@ -436,7 +437,7 @@ func TestSockControlWriteToConn(t *testing.T) { func TestSockControlClose(t *testing.T) { ctrl := gomock.NewController(t) - mockCon := mock_controlsvc.NewMockConn(ctrl) + mockCon := mock_utils.NewMockNetConn(ctrl) sockControl := controlsvc.NewSockControl(mockCon) errorMessage := "cannot close connection" @@ -491,8 +492,8 @@ func TestAddControlFunc(t *testing.T) { func TestRunControlSession(t *testing.T) { ctrl := gomock.NewController(t) - mockCon := mock_controlsvc.NewMockConn(ctrl) - mockAddr := mock_controlsvc.NewMockAddr(ctrl) + mockCon := mock_utils.NewMockNetConn(ctrl) + mockAddr := mock_utils.NewMockNetAddr(ctrl) mockNetceptor := mock_controlsvc.NewMockNetceptorForControlsvc(ctrl) logger := logger.NewReceptorLogger("") diff --git a/pkg/controlsvc/mock_controlsvc/controlsvc.go b/pkg/controlsvc/mock_controlsvc/controlsvc.go index a5d46b2e2..85e2811ed 100644 --- a/pkg/controlsvc/mock_controlsvc/controlsvc.go +++ b/pkg/controlsvc/mock_controlsvc/controlsvc.go @@ -255,15 +255,15 @@ func (m *MockUtiler) EXPECT() *MockUtilerMockRecorder { } // BridgeConns mocks base method. -func (m *MockUtiler) BridgeConns(c1 io.ReadWriteCloser, c1Name string, c2 io.ReadWriteCloser, c2Name string, logger *logger.ReceptorLogger) { +func (m *MockUtiler) BridgeConns(c1 io.ReadWriteCloser, c1Name string, c2 io.ReadWriteCloser, c2Name string, arg4 *logger.ReceptorLogger) { m.ctrl.T.Helper() - m.ctrl.Call(m, "BridgeConns", c1, c1Name, c2, c2Name, logger) + m.ctrl.Call(m, "BridgeConns", c1, c1Name, c2, c2Name, arg4) } // BridgeConns indicates an expected call of BridgeConns. -func (mr *MockUtilerMockRecorder) BridgeConns(c1, c1Name, c2, c2Name, logger any) *gomock.Call { +func (mr *MockUtilerMockRecorder) BridgeConns(c1, c1Name, c2, c2Name, arg4 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BridgeConns", reflect.TypeOf((*MockUtiler)(nil).BridgeConns), c1, c1Name, c2, c2Name, logger) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BridgeConns", reflect.TypeOf((*MockUtiler)(nil).BridgeConns), c1, c1Name, c2, c2Name, arg4) } // UnixSocketListen mocks base method. diff --git a/pkg/controlsvc/mock_controlsvc/interfaces.go b/pkg/controlsvc/mock_controlsvc/interfaces.go index 8ce371fac..9fac5ee49 100644 --- a/pkg/controlsvc/mock_controlsvc/interfaces.go +++ b/pkg/controlsvc/mock_controlsvc/interfaces.go @@ -293,17 +293,17 @@ func (m *MockControlFuncOperations) EXPECT() *MockControlFuncOperationsMockRecor } // BridgeConn mocks base method. -func (m *MockControlFuncOperations) BridgeConn(message string, bc io.ReadWriteCloser, bcName string, logger *logger.ReceptorLogger, utils controlsvc.Utiler) error { +func (m *MockControlFuncOperations) BridgeConn(message string, bc io.ReadWriteCloser, bcName string, arg3 *logger.ReceptorLogger, utils controlsvc.Utiler) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "BridgeConn", message, bc, bcName, logger, utils) + ret := m.ctrl.Call(m, "BridgeConn", message, bc, bcName, arg3, utils) ret0, _ := ret[0].(error) return ret0 } // BridgeConn indicates an expected call of BridgeConn. -func (mr *MockControlFuncOperationsMockRecorder) BridgeConn(message, bc, bcName, logger, utils any) *gomock.Call { +func (mr *MockControlFuncOperationsMockRecorder) BridgeConn(message, bc, bcName, arg3, utils any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BridgeConn", reflect.TypeOf((*MockControlFuncOperations)(nil).BridgeConn), message, bc, bcName, logger, utils) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BridgeConn", reflect.TypeOf((*MockControlFuncOperations)(nil).BridgeConn), message, bc, bcName, arg3, utils) } // Close mocks base method. @@ -321,17 +321,17 @@ func (mr *MockControlFuncOperationsMockRecorder) Close() *gomock.Call { } // ReadFromConn mocks base method. -func (m *MockControlFuncOperations) ReadFromConn(message string, out io.Writer, io controlsvc.Copier) error { +func (m *MockControlFuncOperations) ReadFromConn(message string, out io.Writer, arg2 controlsvc.Copier) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ReadFromConn", message, out, io) + ret := m.ctrl.Call(m, "ReadFromConn", message, out, arg2) ret0, _ := ret[0].(error) return ret0 } // ReadFromConn indicates an expected call of ReadFromConn. -func (mr *MockControlFuncOperationsMockRecorder) ReadFromConn(message, out, io any) *gomock.Call { +func (mr *MockControlFuncOperationsMockRecorder) ReadFromConn(message, out, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadFromConn", reflect.TypeOf((*MockControlFuncOperations)(nil).ReadFromConn), message, out, io) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadFromConn", reflect.TypeOf((*MockControlFuncOperations)(nil).ReadFromConn), message, out, arg2) } // RemoteAddr mocks base method. diff --git a/pkg/controlsvc/mock_controlsvc/io.go b/pkg/controlsvc/mock_controlsvc/io.go deleted file mode 100644 index 7b929e88a..000000000 --- a/pkg/controlsvc/mock_controlsvc/io.go +++ /dev/null @@ -1,84 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: io (interfaces: ReadWriteCloser) -// -// Generated by this command: -// -// mockgen io ReadWriteCloser -// - -// Package mock_io is a generated GoMock package. -package mock_controlsvc - -import ( - reflect "reflect" - - gomock "go.uber.org/mock/gomock" -) - -// MockReadWriteCloser is a mock of ReadWriteCloser interface. -type MockReadWriteCloser struct { - ctrl *gomock.Controller - recorder *MockReadWriteCloserMockRecorder - isgomock struct{} -} - -// MockReadWriteCloserMockRecorder is the mock recorder for MockReadWriteCloser. -type MockReadWriteCloserMockRecorder struct { - mock *MockReadWriteCloser -} - -// NewMockReadWriteCloser creates a new mock instance. -func NewMockReadWriteCloser(ctrl *gomock.Controller) *MockReadWriteCloser { - mock := &MockReadWriteCloser{ctrl: ctrl} - mock.recorder = &MockReadWriteCloserMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockReadWriteCloser) EXPECT() *MockReadWriteCloserMockRecorder { - return m.recorder -} - -// Close mocks base method. -func (m *MockReadWriteCloser) Close() error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Close") - ret0, _ := ret[0].(error) - return ret0 -} - -// Close indicates an expected call of Close. -func (mr *MockReadWriteCloserMockRecorder) Close() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockReadWriteCloser)(nil).Close)) -} - -// Read mocks base method. -func (m *MockReadWriteCloser) Read(p []byte) (int, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Read", p) - ret0, _ := ret[0].(int) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Read indicates an expected call of Read. -func (mr *MockReadWriteCloserMockRecorder) Read(p any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockReadWriteCloser)(nil).Read), p) -} - -// Write mocks base method. -func (m *MockReadWriteCloser) Write(p []byte) (int, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Write", p) - ret0, _ := ret[0].(int) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Write indicates an expected call of Write. -func (mr *MockReadWriteCloserMockRecorder) Write(p any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockReadWriteCloser)(nil).Write), p) -} diff --git a/pkg/services/command.go b/pkg/services/command.go index 3a9cc1e54..c10021cf2 100644 --- a/pkg/services/command.go +++ b/pkg/services/command.go @@ -1,4 +1,3 @@ -//go:generate mockgen -source=command.go -destination=mock_services/command.go //go:build !windows // +build !windows diff --git a/pkg/services/interfaces/mock_interfaces/net_interfaces.go b/pkg/services/interfaces/mock_interfaces/net_interfaces.go index b0b93a2f4..6e1c17e55 100644 --- a/pkg/services/interfaces/mock_interfaces/net_interfaces.go +++ b/pkg/services/interfaces/mock_interfaces/net_interfaces.go @@ -6,8 +6,8 @@ // mockgen -source=pkg/services/interfaces/net_interfaces.go -destination=pkg/services/interfaces/mock_interfaces/net_interfaces.go // -// Package mock_net_interface is a generated GoMock package. -package mock_net_interface +// Package mock_netinterface is a generated GoMock package. +package mock_netinterface import ( net "net" @@ -17,7 +17,7 @@ import ( syscall "syscall" time "time" - net_interface "github.com/ansible/receptor/pkg/services/interfaces" + netinterface "github.com/ansible/receptor/pkg/services/interfaces" gomock "go.uber.org/mock/gomock" ) @@ -46,10 +46,10 @@ func (m *MockNetterUDP) EXPECT() *MockNetterUDPMockRecorder { } // DialUDP mocks base method. -func (m *MockNetterUDP) DialUDP(network string, laddr, raddr *net.UDPAddr) (net_interface.UDPConnInterface, error) { +func (m *MockNetterUDP) DialUDP(network string, laddr, raddr *net.UDPAddr) (netinterface.UDPConnInterface, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "DialUDP", network, laddr, raddr) - ret0, _ := ret[0].(net_interface.UDPConnInterface) + ret0, _ := ret[0].(netinterface.UDPConnInterface) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -61,10 +61,10 @@ func (mr *MockNetterUDPMockRecorder) DialUDP(network, laddr, raddr any) *gomock. } // ListenUDP mocks base method. -func (m *MockNetterUDP) ListenUDP(network string, laddr *net.UDPAddr) (net_interface.UDPConnInterface, error) { +func (m *MockNetterUDP) ListenUDP(network string, laddr *net.UDPAddr) (netinterface.UDPConnInterface, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListenUDP", network, laddr) - ret0, _ := ret[0].(net_interface.UDPConnInterface) + ret0, _ := ret[0].(netinterface.UDPConnInterface) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/pkg/services/mock_services/command.go b/pkg/services/mock_services/command.go index e3a9e7abc..298cd88e2 100644 --- a/pkg/services/mock_services/command.go +++ b/pkg/services/mock_services/command.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: command.go +// Source: pkg/services/command.go // // Generated by this command: // -// mockgen -source=command.go -destination=mock_services/command.go +// mockgen -source=pkg/services/command.go -destination=pkg/services/mock_services/command.go // // Package mock_services is a generated GoMock package. @@ -22,6 +22,7 @@ import ( type MockNetCForCommandService struct { ctrl *gomock.Controller recorder *MockNetCForCommandServiceMockRecorder + isgomock struct{} } // MockNetCForCommandServiceMockRecorder is the mock recorder for MockNetCForCommandService. diff --git a/pkg/services/mock_services/tcp_proxy.go b/pkg/services/mock_services/tcp_proxy.go index 584c55d01..5a8d311b3 100644 --- a/pkg/services/mock_services/tcp_proxy.go +++ b/pkg/services/mock_services/tcp_proxy.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: tcp_proxy.go +// Source: pkg/services/tcp_proxy.go // // Generated by this command: // -// mockgen -package mock_services -source=tcp_proxy.go -destination=mock_services/tcp_proxy.go +// mockgen -source=pkg/services/tcp_proxy.go -destination=pkg/services/mock_services/tcp_proxy.go // // Package mock_services is a generated GoMock package. @@ -25,6 +25,7 @@ import ( type MockNetcForTCPProxy struct { ctrl *gomock.Controller recorder *MockNetcForTCPProxyMockRecorder + isgomock struct{} } // MockNetcForTCPProxyMockRecorder is the mock recorder for MockNetcForTCPProxy. @@ -92,6 +93,7 @@ func (mr *MockNetcForTCPProxyMockRecorder) ListenAndAdvertise(service, tlscfg, t type MockNetLib struct { ctrl *gomock.Controller recorder *MockNetLibMockRecorder + isgomock struct{} } // MockNetLibMockRecorder is the mock recorder for MockNetLib. @@ -145,6 +147,7 @@ func (mr *MockNetLibMockRecorder) Listen(network, address any) *gomock.Call { type MockTLSLib struct { ctrl *gomock.Controller recorder *MockTLSLibMockRecorder + isgomock struct{} } // MockTLSLibMockRecorder is the mock recorder for MockTLSLib. @@ -197,6 +200,7 @@ func (mr *MockTLSLibMockRecorder) NewListener(inner, config any) *gomock.Call { type MockNetListenerTCP struct { ctrl *gomock.Controller recorder *MockNetListenerTCPMockRecorder + isgomock struct{} } // MockNetListenerTCPMockRecorder is the mock recorder for MockNetListenerTCP. @@ -263,6 +267,7 @@ func (mr *MockNetListenerTCPMockRecorder) Close() *gomock.Call { type MockUtilsLib struct { ctrl *gomock.Controller recorder *MockUtilsLibMockRecorder + isgomock struct{} } // MockUtilsLibMockRecorder is the mock recorder for MockUtilsLib. @@ -283,21 +288,22 @@ func (m *MockUtilsLib) EXPECT() *MockUtilsLibMockRecorder { } // BridgeConns mocks base method. -func (m *MockUtilsLib) BridgeConns(c1 io.ReadWriteCloser, c1Name string, c2 io.ReadWriteCloser, c2Name string, logger *logger.ReceptorLogger) { +func (m *MockUtilsLib) BridgeConns(c1 io.ReadWriteCloser, c1Name string, c2 io.ReadWriteCloser, c2Name string, arg4 *logger.ReceptorLogger) { m.ctrl.T.Helper() - m.ctrl.Call(m, "BridgeConns", c1, c1Name, c2, c2Name, logger) + m.ctrl.Call(m, "BridgeConns", c1, c1Name, c2, c2Name, arg4) } // BridgeConns indicates an expected call of BridgeConns. -func (mr *MockUtilsLibMockRecorder) BridgeConns(c1, c1Name, c2, c2Name, logger any) *gomock.Call { +func (mr *MockUtilsLibMockRecorder) BridgeConns(c1, c1Name, c2, c2Name, arg4 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BridgeConns", reflect.TypeOf((*MockUtilsLib)(nil).BridgeConns), c1, c1Name, c2, c2Name, logger) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BridgeConns", reflect.TypeOf((*MockUtilsLib)(nil).BridgeConns), c1, c1Name, c2, c2Name, arg4) } // MockTCPConn is a mock of TCPConn interface. type MockTCPConn struct { ctrl *gomock.Controller recorder *MockTCPConnMockRecorder + isgomock struct{} } // MockTCPConnMockRecorder is the mock recorder for MockTCPConn. diff --git a/pkg/services/mock_services/udp_proxy.go b/pkg/services/mock_services/udp_proxy.go index 98d5dde4e..218681370 100644 --- a/pkg/services/mock_services/udp_proxy.go +++ b/pkg/services/mock_services/udp_proxy.go @@ -1,5 +1,10 @@ // Code generated by MockGen. DO NOT EDIT. // Source: pkg/services/udp_proxy.go +// +// Generated by this command: +// +// mockgen -source=pkg/services/udp_proxy.go -destination=pkg/services/mock_services/udp_proxy.go +// // Package mock_services is a generated GoMock package. package mock_services @@ -16,6 +21,7 @@ import ( type MockNetcForUDPProxy struct { ctrl *gomock.Controller recorder *MockNetcForUDPProxyMockRecorder + isgomock struct{} } // MockNetcForUDPProxyMockRecorder is the mock recorder for MockNetcForUDPProxy. @@ -59,7 +65,7 @@ func (m *MockNetcForUDPProxy) ListenPacket(service string) (netceptor.PacketConn } // ListenPacket indicates an expected call of ListenPacket. -func (mr *MockNetcForUDPProxyMockRecorder) ListenPacket(service interface{}) *gomock.Call { +func (mr *MockNetcForUDPProxyMockRecorder) ListenPacket(service any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListenPacket", reflect.TypeOf((*MockNetcForUDPProxy)(nil).ListenPacket), service) } @@ -74,7 +80,7 @@ func (m *MockNetcForUDPProxy) ListenPacketAndAdvertise(service string, tags map[ } // ListenPacketAndAdvertise indicates an expected call of ListenPacketAndAdvertise. -func (mr *MockNetcForUDPProxyMockRecorder) ListenPacketAndAdvertise(service, tags interface{}) *gomock.Call { +func (mr *MockNetcForUDPProxyMockRecorder) ListenPacketAndAdvertise(service, tags any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListenPacketAndAdvertise", reflect.TypeOf((*MockNetcForUDPProxy)(nil).ListenPacketAndAdvertise), service, tags) } @@ -88,7 +94,7 @@ func (m *MockNetcForUDPProxy) NewAddr(node, service string) netceptor.Addr { } // NewAddr indicates an expected call of NewAddr. -func (mr *MockNetcForUDPProxyMockRecorder) NewAddr(node, service interface{}) *gomock.Call { +func (mr *MockNetcForUDPProxyMockRecorder) NewAddr(node, service any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewAddr", reflect.TypeOf((*MockNetcForUDPProxy)(nil).NewAddr), node, service) } diff --git a/pkg/services/tcp_proxy.go b/pkg/services/tcp_proxy.go index f1179e838..27d195780 100644 --- a/pkg/services/tcp_proxy.go +++ b/pkg/services/tcp_proxy.go @@ -14,8 +14,6 @@ import ( "github.com/spf13/viper" ) -//go:generate mockgen -package mock_services -source=tcp_proxy.go -destination=mock_services/tcp_proxy.go - type NetcForTCPProxy interface { GetLogger() *logger.ReceptorLogger Dial(node string, service string, tlscfg *tls.Config) (*netceptor.Conn, error) diff --git a/pkg/controlsvc/mock_controlsvc/mock_net.go b/pkg/utils/mock_utils/net.go similarity index 53% rename from pkg/controlsvc/mock_controlsvc/mock_net.go rename to pkg/utils/mock_utils/net.go index cd69d03ba..ca388f991 100644 --- a/pkg/controlsvc/mock_controlsvc/mock_net.go +++ b/pkg/utils/mock_utils/net.go @@ -1,13 +1,13 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: net (interfaces: Listener,Conn,Addr) +// Source: pkg/utils/net.go // // Generated by this command: // -// mockgen net Listener,Conn,Addr +// mockgen -source=pkg/utils/net.go -destination=pkg/utils/mock_utils/net.go // -// Package mock_net is a generated GoMock package. -package mock_controlsvc +// Package mock_utils is a generated GoMock package. +package mock_utils import ( net "net" @@ -17,32 +17,32 @@ import ( gomock "go.uber.org/mock/gomock" ) -// MockListener is a mock of Listener interface. -type MockListener struct { +// MockNetListener is a mock of NetListener interface. +type MockNetListener struct { ctrl *gomock.Controller - recorder *MockListenerMockRecorder + recorder *MockNetListenerMockRecorder isgomock struct{} } -// MockListenerMockRecorder is the mock recorder for MockListener. -type MockListenerMockRecorder struct { - mock *MockListener +// MockNetListenerMockRecorder is the mock recorder for MockNetListener. +type MockNetListenerMockRecorder struct { + mock *MockNetListener } -// NewMockListener creates a new mock instance. -func NewMockListener(ctrl *gomock.Controller) *MockListener { - mock := &MockListener{ctrl: ctrl} - mock.recorder = &MockListenerMockRecorder{mock} +// NewMockNetListener creates a new mock instance. +func NewMockNetListener(ctrl *gomock.Controller) *MockNetListener { + mock := &MockNetListener{ctrl: ctrl} + mock.recorder = &MockNetListenerMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockListener) EXPECT() *MockListenerMockRecorder { +func (m *MockNetListener) EXPECT() *MockNetListenerMockRecorder { return m.recorder } // Accept mocks base method. -func (m *MockListener) Accept() (net.Conn, error) { +func (m *MockNetListener) Accept() (net.Conn, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Accept") ret0, _ := ret[0].(net.Conn) @@ -51,13 +51,13 @@ func (m *MockListener) Accept() (net.Conn, error) { } // Accept indicates an expected call of Accept. -func (mr *MockListenerMockRecorder) Accept() *gomock.Call { +func (mr *MockNetListenerMockRecorder) Accept() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Accept", reflect.TypeOf((*MockListener)(nil).Accept)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Accept", reflect.TypeOf((*MockNetListener)(nil).Accept)) } // Addr mocks base method. -func (m *MockListener) Addr() net.Addr { +func (m *MockNetListener) Addr() net.Addr { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Addr") ret0, _ := ret[0].(net.Addr) @@ -65,13 +65,13 @@ func (m *MockListener) Addr() net.Addr { } // Addr indicates an expected call of Addr. -func (mr *MockListenerMockRecorder) Addr() *gomock.Call { +func (mr *MockNetListenerMockRecorder) Addr() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Addr", reflect.TypeOf((*MockListener)(nil).Addr)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Addr", reflect.TypeOf((*MockNetListener)(nil).Addr)) } // Close mocks base method. -func (m *MockListener) Close() error { +func (m *MockNetListener) Close() error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Close") ret0, _ := ret[0].(error) @@ -79,37 +79,37 @@ func (m *MockListener) Close() error { } // Close indicates an expected call of Close. -func (mr *MockListenerMockRecorder) Close() *gomock.Call { +func (mr *MockNetListenerMockRecorder) Close() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockListener)(nil).Close)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockNetListener)(nil).Close)) } -// MockConn is a mock of Conn interface. -type MockConn struct { +// MockNetConn is a mock of NetConn interface. +type MockNetConn struct { ctrl *gomock.Controller - recorder *MockConnMockRecorder + recorder *MockNetConnMockRecorder isgomock struct{} } -// MockConnMockRecorder is the mock recorder for MockConn. -type MockConnMockRecorder struct { - mock *MockConn +// MockNetConnMockRecorder is the mock recorder for MockNetConn. +type MockNetConnMockRecorder struct { + mock *MockNetConn } -// NewMockConn creates a new mock instance. -func NewMockConn(ctrl *gomock.Controller) *MockConn { - mock := &MockConn{ctrl: ctrl} - mock.recorder = &MockConnMockRecorder{mock} +// NewMockNetConn creates a new mock instance. +func NewMockNetConn(ctrl *gomock.Controller) *MockNetConn { + mock := &MockNetConn{ctrl: ctrl} + mock.recorder = &MockNetConnMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockConn) EXPECT() *MockConnMockRecorder { +func (m *MockNetConn) EXPECT() *MockNetConnMockRecorder { return m.recorder } // Close mocks base method. -func (m *MockConn) Close() error { +func (m *MockNetConn) Close() error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Close") ret0, _ := ret[0].(error) @@ -117,13 +117,13 @@ func (m *MockConn) Close() error { } // Close indicates an expected call of Close. -func (mr *MockConnMockRecorder) Close() *gomock.Call { +func (mr *MockNetConnMockRecorder) Close() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockConn)(nil).Close)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockNetConn)(nil).Close)) } // LocalAddr mocks base method. -func (m *MockConn) LocalAddr() net.Addr { +func (m *MockNetConn) LocalAddr() net.Addr { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "LocalAddr") ret0, _ := ret[0].(net.Addr) @@ -131,13 +131,13 @@ func (m *MockConn) LocalAddr() net.Addr { } // LocalAddr indicates an expected call of LocalAddr. -func (mr *MockConnMockRecorder) LocalAddr() *gomock.Call { +func (mr *MockNetConnMockRecorder) LocalAddr() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LocalAddr", reflect.TypeOf((*MockConn)(nil).LocalAddr)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LocalAddr", reflect.TypeOf((*MockNetConn)(nil).LocalAddr)) } // Read mocks base method. -func (m *MockConn) Read(b []byte) (int, error) { +func (m *MockNetConn) Read(b []byte) (int, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Read", b) ret0, _ := ret[0].(int) @@ -146,13 +146,13 @@ func (m *MockConn) Read(b []byte) (int, error) { } // Read indicates an expected call of Read. -func (mr *MockConnMockRecorder) Read(b any) *gomock.Call { +func (mr *MockNetConnMockRecorder) Read(b any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockConn)(nil).Read), b) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockNetConn)(nil).Read), b) } // RemoteAddr mocks base method. -func (m *MockConn) RemoteAddr() net.Addr { +func (m *MockNetConn) RemoteAddr() net.Addr { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "RemoteAddr") ret0, _ := ret[0].(net.Addr) @@ -160,13 +160,13 @@ func (m *MockConn) RemoteAddr() net.Addr { } // RemoteAddr indicates an expected call of RemoteAddr. -func (mr *MockConnMockRecorder) RemoteAddr() *gomock.Call { +func (mr *MockNetConnMockRecorder) RemoteAddr() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoteAddr", reflect.TypeOf((*MockConn)(nil).RemoteAddr)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoteAddr", reflect.TypeOf((*MockNetConn)(nil).RemoteAddr)) } // SetDeadline mocks base method. -func (m *MockConn) SetDeadline(t time.Time) error { +func (m *MockNetConn) SetDeadline(t time.Time) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SetDeadline", t) ret0, _ := ret[0].(error) @@ -174,13 +174,13 @@ func (m *MockConn) SetDeadline(t time.Time) error { } // SetDeadline indicates an expected call of SetDeadline. -func (mr *MockConnMockRecorder) SetDeadline(t any) *gomock.Call { +func (mr *MockNetConnMockRecorder) SetDeadline(t any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDeadline", reflect.TypeOf((*MockConn)(nil).SetDeadline), t) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDeadline", reflect.TypeOf((*MockNetConn)(nil).SetDeadline), t) } // SetReadDeadline mocks base method. -func (m *MockConn) SetReadDeadline(t time.Time) error { +func (m *MockNetConn) SetReadDeadline(t time.Time) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SetReadDeadline", t) ret0, _ := ret[0].(error) @@ -188,13 +188,13 @@ func (m *MockConn) SetReadDeadline(t time.Time) error { } // SetReadDeadline indicates an expected call of SetReadDeadline. -func (mr *MockConnMockRecorder) SetReadDeadline(t any) *gomock.Call { +func (mr *MockNetConnMockRecorder) SetReadDeadline(t any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetReadDeadline", reflect.TypeOf((*MockConn)(nil).SetReadDeadline), t) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetReadDeadline", reflect.TypeOf((*MockNetConn)(nil).SetReadDeadline), t) } // SetWriteDeadline mocks base method. -func (m *MockConn) SetWriteDeadline(t time.Time) error { +func (m *MockNetConn) SetWriteDeadline(t time.Time) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SetWriteDeadline", t) ret0, _ := ret[0].(error) @@ -202,13 +202,13 @@ func (m *MockConn) SetWriteDeadline(t time.Time) error { } // SetWriteDeadline indicates an expected call of SetWriteDeadline. -func (mr *MockConnMockRecorder) SetWriteDeadline(t any) *gomock.Call { +func (mr *MockNetConnMockRecorder) SetWriteDeadline(t any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetWriteDeadline", reflect.TypeOf((*MockConn)(nil).SetWriteDeadline), t) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetWriteDeadline", reflect.TypeOf((*MockNetConn)(nil).SetWriteDeadline), t) } // Write mocks base method. -func (m *MockConn) Write(b []byte) (int, error) { +func (m *MockNetConn) Write(b []byte) (int, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Write", b) ret0, _ := ret[0].(int) @@ -217,37 +217,37 @@ func (m *MockConn) Write(b []byte) (int, error) { } // Write indicates an expected call of Write. -func (mr *MockConnMockRecorder) Write(b any) *gomock.Call { +func (mr *MockNetConnMockRecorder) Write(b any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockConn)(nil).Write), b) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockNetConn)(nil).Write), b) } -// MockAddr is a mock of Addr interface. -type MockAddr struct { +// MockNetAddr is a mock of NetAddr interface. +type MockNetAddr struct { ctrl *gomock.Controller - recorder *MockAddrMockRecorder + recorder *MockNetAddrMockRecorder isgomock struct{} } -// MockAddrMockRecorder is the mock recorder for MockAddr. -type MockAddrMockRecorder struct { - mock *MockAddr +// MockNetAddrMockRecorder is the mock recorder for MockNetAddr. +type MockNetAddrMockRecorder struct { + mock *MockNetAddr } -// NewMockAddr creates a new mock instance. -func NewMockAddr(ctrl *gomock.Controller) *MockAddr { - mock := &MockAddr{ctrl: ctrl} - mock.recorder = &MockAddrMockRecorder{mock} +// NewMockNetAddr creates a new mock instance. +func NewMockNetAddr(ctrl *gomock.Controller) *MockNetAddr { + mock := &MockNetAddr{ctrl: ctrl} + mock.recorder = &MockNetAddrMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockAddr) EXPECT() *MockAddrMockRecorder { +func (m *MockNetAddr) EXPECT() *MockNetAddrMockRecorder { return m.recorder } // Network mocks base method. -func (m *MockAddr) Network() string { +func (m *MockNetAddr) Network() string { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Network") ret0, _ := ret[0].(string) @@ -255,13 +255,13 @@ func (m *MockAddr) Network() string { } // Network indicates an expected call of Network. -func (mr *MockAddrMockRecorder) Network() *gomock.Call { +func (mr *MockNetAddrMockRecorder) Network() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Network", reflect.TypeOf((*MockAddr)(nil).Network)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Network", reflect.TypeOf((*MockNetAddr)(nil).Network)) } // String mocks base method. -func (m *MockAddr) String() string { +func (m *MockNetAddr) String() string { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "String") ret0, _ := ret[0].(string) @@ -269,7 +269,7 @@ func (m *MockAddr) String() string { } // String indicates an expected call of String. -func (mr *MockAddrMockRecorder) String() *gomock.Call { +func (mr *MockNetAddrMockRecorder) String() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "String", reflect.TypeOf((*MockAddr)(nil).String)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "String", reflect.TypeOf((*MockNetAddr)(nil).String)) } diff --git a/pkg/utils/net.go b/pkg/utils/net.go new file mode 100644 index 000000000..5157ecdba --- /dev/null +++ b/pkg/utils/net.go @@ -0,0 +1,15 @@ +package utils + +import "net" + +type NetListener interface { + net.Listener +} + +type NetConn interface { + net.Conn +} + +type NetAddr interface { + net.Addr +} diff --git a/pkg/workceptor/mock_workceptor/baseworkunit.go b/pkg/workceptor/mock_workceptor/command.go similarity index 99% rename from pkg/workceptor/mock_workceptor/baseworkunit.go rename to pkg/workceptor/mock_workceptor/command.go index 87c2c2297..e59952236 100644 --- a/pkg/workceptor/mock_workceptor/baseworkunit.go +++ b/pkg/workceptor/mock_workceptor/command.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/ansible/receptor/pkg/workceptor (interfaces: BaseWorkUnitForWorkUnit) +// Source: pkg/workceptor/command.go // // Generated by this command: // -// mockgen . BaseWorkUnitForWorkUnit +// mockgen -source=pkg/workceptor/command.go -destination=pkg/workceptor/mock_workceptor/command.go // // Package mock_workceptor is a generated GoMock package. From 175087548e366d375f0dc8d97b8c1c5217393dd5 Mon Sep 17 00:00:00 2001 From: Matthew Sandoval Date: Mon, 2 Jun 2025 07:42:32 -0700 Subject: [PATCH 12/21] Remove fsnotify from workunitbase (#1305) --- pkg/workceptor/command.go | 6 +- pkg/workceptor/command_test.go | 2 +- pkg/workceptor/json_test.go | 2 +- pkg/workceptor/kubernetes.go | 2 +- pkg/workceptor/kubernetes_test.go | 2 +- pkg/workceptor/mock_workceptor/command.go | 8 +- pkg/workceptor/python.go | 2 +- pkg/workceptor/python_test.go | 2 +- pkg/workceptor/remote_work.go | 2 +- pkg/workceptor/remote_work_test.go | 2 +- pkg/workceptor/workunitbase.go | 103 +--------------------- pkg/workceptor/workunitbase_test.go | 81 ++++------------- 12 files changed, 35 insertions(+), 179 deletions(-) diff --git a/pkg/workceptor/command.go b/pkg/workceptor/command.go index ff9df35be..dc932b2b9 100644 --- a/pkg/workceptor/command.go +++ b/pkg/workceptor/command.go @@ -27,7 +27,7 @@ import ( type BaseWorkUnitForWorkUnit interface { CancelContext() ID() string - Init(w *Workceptor, unitID string, workType string, fs FileSystemer, watcher WatcherWrapper) + Init(w *Workceptor, unitID string, workType string, fs FileSystemer) LastUpdateError() error Load() error MonitorLocalStatus() @@ -157,6 +157,7 @@ func commandRunner(command string, params string, unitdir string) error { } cmd.Stdout = stdout cmd.Stderr = stdout + err = cmd.Start() if err != nil { return err @@ -275,6 +276,7 @@ func (cw *commandUnit) runCommand(cmd *exec.Cmd) error { cw.done = false cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr + if err := cmd.Start(); err != nil { cw.UpdateBasicStatus(WorkStateFailed, fmt.Sprintf("Failed to start command runner: %s", err), 0) @@ -411,7 +413,7 @@ func (cfg CommandWorkerCfg) NewWorker(bwu BaseWorkUnitForWorkUnit, w *Workceptor baseParams: cfg.Params, allowRuntimeParams: cfg.AllowRuntimeParams, } - cw.BaseWorkUnitForWorkUnit.Init(w, unitID, workType, FileSystem{}, nil) + cw.BaseWorkUnitForWorkUnit.Init(w, unitID, workType, FileSystem{}) return cw } diff --git a/pkg/workceptor/command_test.go b/pkg/workceptor/command_test.go index b433b3973..1dd1478b2 100644 --- a/pkg/workceptor/command_test.go +++ b/pkg/workceptor/command_test.go @@ -37,7 +37,7 @@ func createCommandTestSetup(t *testing.T) (workceptor.WorkUnit, *mock_workceptor } cwc := &workceptor.CommandWorkerCfg{} - mockBaseWorkUnit.EXPECT().Init(w, "", "", workceptor.FileSystem{}, nil) + mockBaseWorkUnit.EXPECT().Init(w, "", "", workceptor.FileSystem{}) workUnit := cwc.NewWorker(mockBaseWorkUnit, w, "", "") return workUnit, mockBaseWorkUnit, mockNetceptor, w diff --git a/pkg/workceptor/json_test.go b/pkg/workceptor/json_test.go index f7d22b2c3..14b0e5919 100644 --- a/pkg/workceptor/json_test.go +++ b/pkg/workceptor/json_test.go @@ -22,7 +22,7 @@ func newCommandWorker(_ BaseWorkUnitForWorkUnit, w *Workceptor, unitID string, w baseParams: "foo", allowRuntimeParams: true, } - cw.BaseWorkUnitForWorkUnit.Init(w, unitID, workType, FileSystem{}, nil) + cw.BaseWorkUnitForWorkUnit.Init(w, unitID, workType, FileSystem{}) return cw } diff --git a/pkg/workceptor/kubernetes.go b/pkg/workceptor/kubernetes.go index 514fa80a7..54b225f5d 100644 --- a/pkg/workceptor/kubernetes.go +++ b/pkg/workceptor/kubernetes.go @@ -1627,7 +1627,7 @@ func (cfg KubeWorkerCfg) NewkubeWorker(bwu BaseWorkUnitForWorkUnit, w *Workcepto deletePodOnRestart: cfg.DeletePodOnRestart, namePrefix: fmt.Sprintf("%s-", strings.ToLower(cfg.WorkType)), } - ku.BaseWorkUnitForWorkUnit.Init(w, unitID, workType, FileSystem{}, nil) + ku.BaseWorkUnitForWorkUnit.Init(w, unitID, workType, FileSystem{}) return ku } diff --git a/pkg/workceptor/kubernetes_test.go b/pkg/workceptor/kubernetes_test.go index 4ce071e4e..66cbb84bc 100644 --- a/pkg/workceptor/kubernetes_test.go +++ b/pkg/workceptor/kubernetes_test.go @@ -213,7 +213,7 @@ func createKubernetesTestSetup(t *testing.T) (workceptor.WorkUnit, *mock_workcep t.Errorf("Error while creating Workceptor: %v", err) } - mockBaseWorkUnit.EXPECT().Init(w, "", "", workceptor.FileSystem{}, nil) + mockBaseWorkUnit.EXPECT().Init(w, "", "", workceptor.FileSystem{}) kubeConfig := workceptor.KubeWorkerCfg{AuthMethod: "incluster"} ku := kubeConfig.NewkubeWorker(mockBaseWorkUnit, w, "", "", mockKubeAPI) diff --git a/pkg/workceptor/mock_workceptor/command.go b/pkg/workceptor/mock_workceptor/command.go index e59952236..2de966e56 100644 --- a/pkg/workceptor/mock_workceptor/command.go +++ b/pkg/workceptor/mock_workceptor/command.go @@ -153,15 +153,15 @@ func (mr *MockBaseWorkUnitForWorkUnitMockRecorder) ID() *gomock.Call { } // Init mocks base method. -func (m *MockBaseWorkUnitForWorkUnit) Init(w *workceptor.Workceptor, unitID, workType string, fs workceptor.FileSystemer, watcher workceptor.WatcherWrapper) { +func (m *MockBaseWorkUnitForWorkUnit) Init(w *workceptor.Workceptor, unitID, workType string, fs workceptor.FileSystemer) { m.ctrl.T.Helper() - m.ctrl.Call(m, "Init", w, unitID, workType, fs, watcher) + m.ctrl.Call(m, "Init", w, unitID, workType, fs) } // Init indicates an expected call of Init. -func (mr *MockBaseWorkUnitForWorkUnitMockRecorder) Init(w, unitID, workType, fs, watcher any) *gomock.Call { +func (mr *MockBaseWorkUnitForWorkUnitMockRecorder) Init(w, unitID, workType, fs any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockBaseWorkUnitForWorkUnit)(nil).Init), w, unitID, workType, fs, watcher) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockBaseWorkUnitForWorkUnit)(nil).Init), w, unitID, workType, fs) } // LastUpdateError mocks base method. diff --git a/pkg/workceptor/python.go b/pkg/workceptor/python.go index d471f32e2..cb775c3d9 100644 --- a/pkg/workceptor/python.go +++ b/pkg/workceptor/python.go @@ -78,7 +78,7 @@ func (cfg WorkPythonCfg) NewWorker(_ BaseWorkUnitForWorkUnit, w *Workceptor, uni function: cfg.Function, config: cfg.Config, } - cw.BaseWorkUnitForWorkUnit.Init(w, unitID, workType, FileSystem{}, nil) + cw.BaseWorkUnitForWorkUnit.Init(w, unitID, workType, FileSystem{}) return cw } diff --git a/pkg/workceptor/python_test.go b/pkg/workceptor/python_test.go index d9d153e4c..e391900c7 100644 --- a/pkg/workceptor/python_test.go +++ b/pkg/workceptor/python_test.go @@ -30,7 +30,7 @@ func createPythonUnitTestSetup(t *testing.T) (workceptor.WorkUnit, *mock_workcep t.Errorf("Error while creating Workceptor: %v", err) } - mockBaseWorkUnit.EXPECT().Init(w, "", "", workceptor.FileSystem{}, nil) + mockBaseWorkUnit.EXPECT().Init(w, "", "", workceptor.FileSystem{}) mockBaseWorkUnit.EXPECT().SetStatusExtraData(gomock.Any()) workUnit := workceptor.NewRemoteWorker(mockBaseWorkUnit, w, "", "") diff --git a/pkg/workceptor/remote_work.go b/pkg/workceptor/remote_work.go index bf69680d5..fa844bcfc 100644 --- a/pkg/workceptor/remote_work.go +++ b/pkg/workceptor/remote_work.go @@ -723,7 +723,7 @@ func newRemoteWorker(bwu BaseWorkUnitForWorkUnit, w *Workceptor, unitID, workTyp BaseWorkUnitForWorkUnit: bwu, logger: w.nc.GetLogger(), } - rw.BaseWorkUnitForWorkUnit.Init(w, unitID, workType, FileSystem{}, nil) + rw.BaseWorkUnitForWorkUnit.Init(w, unitID, workType, FileSystem{}) red := &RemoteExtraData{} red.RemoteParams = make(map[string]string) rw.SetStatusExtraData(red) diff --git a/pkg/workceptor/remote_work_test.go b/pkg/workceptor/remote_work_test.go index de000e786..5803fbf2a 100644 --- a/pkg/workceptor/remote_work_test.go +++ b/pkg/workceptor/remote_work_test.go @@ -24,7 +24,7 @@ func createRemoteWorkTestSetup(t *testing.T) (workceptor.WorkUnit, *mock_workcep t.Errorf("Error while creating Workceptor: %v", err) } - mockBaseWorkUnit.EXPECT().Init(w, "", "", workceptor.FileSystem{}, nil) + mockBaseWorkUnit.EXPECT().Init(w, "", "", workceptor.FileSystem{}) mockBaseWorkUnit.EXPECT().SetStatusExtraData(gomock.Any()) workUnit := workceptor.NewRemoteWorker(mockBaseWorkUnit, w, "", "") diff --git a/pkg/workceptor/workunitbase.go b/pkg/workceptor/workunitbase.go index 0e9a17da2..09ba8b1ca 100644 --- a/pkg/workceptor/workunitbase.go +++ b/pkg/workceptor/workunitbase.go @@ -14,7 +14,6 @@ import ( "sync" "time" - "github.com/fsnotify/fsnotify" "github.com/rogpeppe/go-internal/lockedfile" ) @@ -33,39 +32,6 @@ const ( WorkStateCanceled = 4 ) -// WatcherWrapper is wrapping the fsnofity Watcher struct and exposing the Event chan within. -type WatcherWrapper interface { - Add(name string) error - Remove(path string) error - Close() error - ErrorChannel() chan error - EventChannel() chan fsnotify.Event -} - -type RealWatcher struct { - watcher *fsnotify.Watcher -} - -func (rw *RealWatcher) Add(name string) error { - return rw.watcher.Add(name) -} - -func (rw *RealWatcher) Remove(path string) error { - return rw.watcher.Remove(path) -} - -func (rw *RealWatcher) Close() error { - return rw.watcher.Close() -} - -func (rw *RealWatcher) ErrorChannel() chan error { - return rw.watcher.Errors -} - -func (rw *RealWatcher) EventChannel() chan fsnotify.Event { - return rw.watcher.Events -} - // IsComplete returns true if a given WorkState indicates the job is finished. func IsComplete(workState int) bool { return workState == WorkStateSucceeded || workState == WorkStateFailed @@ -111,11 +77,10 @@ type BaseWorkUnit struct { ctx context.Context cancel context.CancelFunc fs FileSystemer - watcher WatcherWrapper } // Init initializes the basic work unit data, in memory only. -func (bwu *BaseWorkUnit) Init(w *Workceptor, unitID string, workType string, fs FileSystemer, watcher WatcherWrapper) { +func (bwu *BaseWorkUnit) Init(w *Workceptor, unitID string, workType string, fs FileSystemer) { bwu.w = w bwu.status.State = WorkStatePending bwu.status.Detail = "Unit Created" @@ -129,17 +94,6 @@ func (bwu *BaseWorkUnit) Init(w *Workceptor, unitID string, workType string, fs bwu.lastUpdateErrorLock = &sync.RWMutex{} bwu.ctx, bwu.cancel = context.WithCancel(w.ctx) bwu.fs = fs - if watcher != nil { - bwu.watcher = watcher - } else { - watcher, err := fsnotify.NewWatcher() - if err == nil { - bwu.watcher = &RealWatcher{watcher: watcher} - } else { - bwu.w.nc.GetLogger().Info("fsnotify.NewWatcher returned %s", err) - bwu.watcher = nil - } - } } // Error logs message with unitID prepended. @@ -400,34 +354,6 @@ func (bwu *BaseWorkUnit) LastUpdateError() error { // MonitorLocalStatus watches a unit dir and keeps the in-memory workUnit up to date with status changes. func (bwu *BaseWorkUnit) MonitorLocalStatus() { statusFile := path.Join(bwu.UnitDir(), "status") - var watcherEvents chan fsnotify.Event - watcherEvents = make(chan fsnotify.Event) - - var watcherErrors chan error - watcherErrors = make(chan error) - - if bwu.watcher != nil { - bwu.statusLock.Lock() - err := bwu.watcher.Add(statusFile) - bwu.statusLock.Unlock() - if err == nil { - defer func() { - bwu.watcher.Remove(statusFile) - err = bwu.watcher.Close() - if err != nil { - bwu.w.nc.GetLogger().Error("Error closing watcher: %v", err) - } - }() - watcherEvents = bwu.watcher.EventChannel() - watcherErrors = bwu.watcher.ErrorChannel() - } else { - werr := bwu.watcher.Close() - if werr != nil { - bwu.w.nc.GetLogger().Error("Error closing %s: %s", statusFile, err) - } - bwu.watcher = nil - } - } fi, err := bwu.fs.Stat(statusFile) if err != nil { bwu.w.nc.GetLogger().Error("Error retrieving stat for %s: %s", statusFile, err) @@ -439,26 +365,6 @@ loop: select { case <-bwu.ctx.Done(): break loop - case event := <-watcherEvents: - switch { - case event.Has(fsnotify.Create): - bwu.w.nc.GetLogger().Debug("Watcher Event create of %s", statusFile) - case event.Op&fsnotify.Write == fsnotify.Write: - err = bwu.Load() - if err != nil { - bwu.w.nc.GetLogger().Error("Watcher Events Error reading %s: %s", statusFile, err) - } - case event.Op&fsnotify.Remove == fsnotify.Remove: - err = bwu.Load() - if err != nil { - bwu.w.nc.GetLogger().Debug("Watcher Events Remove reading %s: %s", statusFile, err) - } - case event.Op&fsnotify.Rename == fsnotify.Rename: - err = bwu.Load() - if err != nil { - bwu.w.nc.GetLogger().Debug("Watcher Events Rename reading %s: %s", statusFile, err) - } - } case <-time.After(time.Second): newFi, err := bwu.fs.Stat(statusFile) if err == nil && (fi == nil || fi.ModTime() != newFi.ModTime()) { @@ -468,11 +374,6 @@ loop: bwu.w.nc.GetLogger().Error("Work unit load Error reading %s: %s", statusFile, err) } } - case err, ok := <-watcherErrors: - if !ok { - return - } - bwu.w.nc.GetLogger().Error("fsnotify Error reading %s: %s", statusFile, err) } complete := IsComplete(bwu.Status().State) if complete { @@ -574,7 +475,7 @@ func (bwu *BaseWorkUnit) GetCancel() context.CancelFunc { func newUnknownWorker(w *Workceptor, unitID string, workType string) WorkUnit { uu := &unknownUnit{} - uu.BaseWorkUnit.Init(w, unitID, workType, FileSystem{}, nil) + uu.BaseWorkUnit.Init(w, unitID, workType, FileSystem{}) return uu } diff --git a/pkg/workceptor/workunitbase_test.go b/pkg/workceptor/workunitbase_test.go index 8f593eb02..5f0d12ce0 100644 --- a/pkg/workceptor/workunitbase_test.go +++ b/pkg/workceptor/workunitbase_test.go @@ -107,41 +107,41 @@ func setUp(t *testing.T) (*gomock.Controller, workceptor.BaseWorkUnit, *workcept func TestInit(t *testing.T) { ctrl, bwu, w, _, _ := setUp(t) - bwu.Init(w, "test", "test", workceptor.FileSystem{}, nil) + bwu.Init(w, "test", "test", workceptor.FileSystem{}) ctrl.Finish() } func TestErrorLog(t *testing.T) { ctrl, bwu, w, _, _ := setUp(t) - bwu.Init(w, "test", "test", workceptor.FileSystem{}, &workceptor.RealWatcher{}) + bwu.Init(w, "test", "test", workceptor.FileSystem{}) bwu.Error("test error") ctrl.Finish() } func TestWarningLog(t *testing.T) { ctrl, bwu, w, _, _ := setUp(t) - bwu.Init(w, "test", "test", workceptor.FileSystem{}, &workceptor.RealWatcher{}) + bwu.Init(w, "test", "test", workceptor.FileSystem{}) bwu.Warning("test warning") ctrl.Finish() } func TestInfoLog(t *testing.T) { ctrl, bwu, w, _, _ := setUp(t) - bwu.Init(w, "test", "test", workceptor.FileSystem{}, &workceptor.RealWatcher{}) + bwu.Init(w, "test", "test", workceptor.FileSystem{}) bwu.Info("test info") ctrl.Finish() } func TestDebugLog(t *testing.T) { ctrl, bwu, w, _, _ := setUp(t) - bwu.Init(w, "test", "test", workceptor.FileSystem{}, &workceptor.RealWatcher{}) + bwu.Init(w, "test", "test", workceptor.FileSystem{}) bwu.Error("test debug") ctrl.Finish() } func TestSetFromParams(t *testing.T) { ctrl, bwu, w, _, _ := setUp(t) - bwu.Init(w, "test", "test", workceptor.FileSystem{}, &workceptor.RealWatcher{}) + bwu.Init(w, "test", "test", workceptor.FileSystem{}) err := bwu.SetFromParams(nil) if err != nil { t.Errorf("SetFromParams should return nil: got %v", err) @@ -157,7 +157,7 @@ const ( func TestUnitDir(t *testing.T) { ctrl, bwu, w, _, _ := setUp(t) - bwu.Init(w, "test", "test", workceptor.FileSystem{}, &workceptor.RealWatcher{}) + bwu.Init(w, "test", "test", workceptor.FileSystem{}) expectedUnitDir := path.Join(rootDir, testDir) if unitDir := bwu.UnitDir(); unitDir != expectedUnitDir { t.Errorf("UnitDir returned wrong value: got %s, want %s", unitDir, expectedUnitDir) @@ -167,7 +167,7 @@ func TestUnitDir(t *testing.T) { func TestID(t *testing.T) { ctrl, bwu, w, _, _ := setUp(t) - bwu.Init(w, "test", "test", workceptor.FileSystem{}, &workceptor.RealWatcher{}) + bwu.Init(w, "test", "test", workceptor.FileSystem{}) if id := bwu.ID(); id != "test" { t.Errorf("ID returned wrong value: got %s, want %s", id, "test") } @@ -176,7 +176,7 @@ func TestID(t *testing.T) { func TestStatusFileName(t *testing.T) { ctrl, bwu, w, _, _ := setUp(t) - bwu.Init(w, "test", "", workceptor.FileSystem{}, &workceptor.RealWatcher{}) + bwu.Init(w, "test", "", workceptor.FileSystem{}) expectedUnitDir := path.Join(rootDir, testDir) expectedStatusFileName := path.Join(expectedUnitDir, "status") if statusFileName := bwu.StatusFileName(); statusFileName != expectedStatusFileName { @@ -187,7 +187,7 @@ func TestStatusFileName(t *testing.T) { func TestStdoutFileName(t *testing.T) { ctrl, bwu, w, _, _ := setUp(t) - bwu.Init(w, "test", "", workceptor.FileSystem{}, &workceptor.RealWatcher{}) + bwu.Init(w, "test", "", workceptor.FileSystem{}) expectedUnitDir := path.Join(rootDir, testDir) expectedStdoutFileName := path.Join(expectedUnitDir, "stdout") if stdoutFileName := bwu.StdoutFileName(); stdoutFileName != expectedStdoutFileName { @@ -198,7 +198,7 @@ func TestStdoutFileName(t *testing.T) { func TestBaseSave(t *testing.T) { ctrl, bwu, w, _, _ := setUp(t) - bwu.Init(w, "test", "", workceptor.FileSystem{}, &workceptor.RealWatcher{}) + bwu.Init(w, "test", "", workceptor.FileSystem{}) err := bwu.Save() if !strings.Contains(err.Error(), dirError) { t.Errorf("Base Work Unit Save, no such file or directory expected, instead %s", err.Error()) @@ -208,7 +208,7 @@ func TestBaseSave(t *testing.T) { func TestBaseLoad(t *testing.T) { ctrl, bwu, w, _, _ := setUp(t) - bwu.Init(w, "test", "", workceptor.FileSystem{}, &workceptor.RealWatcher{}) + bwu.Init(w, "test", "", workceptor.FileSystem{}) err := bwu.Load() if !strings.Contains(err.Error(), dirError) { t.Errorf("TestBaseLoad, no such file or directory expected, instead %s", err.Error()) @@ -218,7 +218,7 @@ func TestBaseLoad(t *testing.T) { func TestBaseUpdateFullStatus(t *testing.T) { ctrl, bwu, w, _, _ := setUp(t) - bwu.Init(w, "test", "", workceptor.FileSystem{}, &workceptor.RealWatcher{}) + bwu.Init(w, "test", "", workceptor.FileSystem{}) sf := func(sfd *workceptor.StatusFileData) { // Do nothing } @@ -232,7 +232,7 @@ func TestBaseUpdateFullStatus(t *testing.T) { func TestBaseUpdateBasicStatus(t *testing.T) { ctrl, bwu, w, _, _ := setUp(t) - bwu.Init(w, "test", "", workceptor.FileSystem{}, &workceptor.RealWatcher{}) + bwu.Init(w, "test", "", workceptor.FileSystem{}) bwu.UpdateBasicStatus(1, "Details", 0) err := bwu.LastUpdateError() if !strings.Contains(err.Error(), dirError) { @@ -243,7 +243,7 @@ func TestBaseUpdateBasicStatus(t *testing.T) { func TestBaseStatus(t *testing.T) { ctrl, bwu, w, _, _ := setUp(t) - bwu.Init(w, "test", "", workceptor.FileSystem{}, &workceptor.RealWatcher{}) + bwu.Init(w, "test", "", workceptor.FileSystem{}) status := bwu.Status() if status.State != workceptor.WorkStatePending { t.Errorf("TestBaseStatus, expected work state pending, received %d", status.State) @@ -254,7 +254,7 @@ func TestBaseStatus(t *testing.T) { func TestBaseRelease(t *testing.T) { ctrl, bwu, w, _, _ := setUp(t) mockFileSystem := mock_workceptor.NewMockFileSystemer(ctrl) - bwu.Init(w, "test12345", "", mockFileSystem, &workceptor.RealWatcher{}) + bwu.Init(w, "test12345", "", mockFileSystem) const removeError = "RemoveAll Error" testCases := []struct { @@ -307,15 +307,6 @@ func TestMonitorLocalStatus(t *testing.T) { logOutput string sleepDuration time.Duration }{ - { - name: "Handle Write Event", - statObj: NewInfo("test", 1, 0, time.Now()), - addWatcherErr: nil, - statErr: nil, - fsNotifyEvent: &fsnotify.Event{Op: fsnotify.Write}, - logOutput: "Watcher Events Error reading", - sleepDuration: 100 * time.Millisecond, - }, { name: "Error Adding Watcher", statObj: NewInfo("test", 1, 0, time.Now()), @@ -334,43 +325,6 @@ func TestMonitorLocalStatus(t *testing.T) { logOutput: "", sleepDuration: 100 * time.Millisecond, }, - { - name: "Handle Context Cancellation", - statObj: NewInfo("test", 1, 0, time.Now()), - addWatcherErr: nil, - statErr: nil, - fsNotifyEvent: &fsnotify.Event{Op: fsnotify.Write}, - logOutput: "Watcher Events Error reading", - sleepDuration: 100 * time.Millisecond, - }, - { - name: "Handle File Update Without Event", - statObj: NewInfo("test", 1, 0, time.Now()), - statObjLater: NewInfo("test", 1, 0, time.Now().Add(10*time.Second)), - addWatcherErr: nil, - statErr: nil, - fsNotifyEvent: &fsnotify.Event{Op: fsnotify.Write}, - logOutput: "Watcher Events Error reading", - sleepDuration: 500 * time.Millisecond, - }, - { - name: "Handle Remove Event", - statObj: NewInfo("test", 1, 0, time.Now()), - addWatcherErr: nil, - statErr: nil, - fsNotifyEvent: &fsnotify.Event{Op: fsnotify.Remove}, - logOutput: "Watcher Events Remove reading", - sleepDuration: 100 * time.Millisecond, - }, - { - name: "Handle Rename Event", - statObj: NewInfo("test", 1, 0, time.Now()), - addWatcherErr: nil, - statErr: nil, - fsNotifyEvent: &fsnotify.Event{Op: fsnotify.Rename}, - logOutput: "Watcher Events Rename reading", - sleepDuration: 100 * time.Millisecond, - }, } for _, tc := range tests { @@ -382,13 +336,12 @@ func TestMonitorLocalStatus(t *testing.T) { mockWatcher := mock_workceptor.NewMockWatcherWrapper(ctrl) mockFileSystem := mock_workceptor.NewMockFileSystemer(ctrl) - bwu.Init(w, "test", "", mockFileSystem, mockWatcher) + bwu.Init(w, "test", "", mockFileSystem) mockFileSystem.EXPECT().Stat(gomock.Any()).Return(tc.statObj, tc.statErr).AnyTimes() if tc.statObjLater != nil { mockFileSystem.EXPECT().Stat(gomock.Any()).Return(tc.statObjLater, nil).AnyTimes() } - mockWatcher.EXPECT().Add(gomock.Any()).Return(tc.addWatcherErr) mockWatcher.EXPECT().Remove(gomock.Any()).AnyTimes() mockWatcher.EXPECT().Close().AnyTimes() From e85ffcff42d62ac9559bcfb04886bd0194455b47 Mon Sep 17 00:00:00 2001 From: Dave Mulford <243049+davemulford@users.noreply.github.com> Date: Mon, 9 Jun 2025 09:10:24 -0400 Subject: [PATCH 13/21] Add tcp_proxy.go unit tests (#1330) Co-authored-by: Matthew Sandoval --- generate.go | 1 + pkg/controlsvc/mock_controlsvc/controlsvc.go | 8 +- pkg/controlsvc/mock_controlsvc/interfaces.go | 16 ++-- pkg/services/mock_services/tcp_proxy.go | 8 +- pkg/services/tcp_proxy_test.go | 48 +++++++++++ pkg/utils/mock_utils/io.go | 84 ++++++++++++++++++++ 6 files changed, 149 insertions(+), 16 deletions(-) create mode 100644 pkg/utils/mock_utils/io.go diff --git a/generate.go b/generate.go index b31ce1144..e3de8cca5 100644 --- a/generate.go +++ b/generate.go @@ -22,3 +22,4 @@ package main //go:generate mockgen -source=pkg/workceptor/stdio_utils.go -destination=pkg/workceptor/mock_workceptor/stdio_utils.go //go:generate mockgen -source=pkg/workceptor/workceptor.go -destination=pkg/workceptor/mock_workceptor/workceptor.go //go:generate mockgen -source=pkg/workceptor/workunitbase.go -destination=pkg/workceptor/mock_workceptor/workunitbase.go +//go:generate mockgen -package=mock_utils -destination=pkg/utils/mock_utils/io.go io ReadWriteCloser diff --git a/pkg/controlsvc/mock_controlsvc/controlsvc.go b/pkg/controlsvc/mock_controlsvc/controlsvc.go index 85e2811ed..a5d46b2e2 100644 --- a/pkg/controlsvc/mock_controlsvc/controlsvc.go +++ b/pkg/controlsvc/mock_controlsvc/controlsvc.go @@ -255,15 +255,15 @@ func (m *MockUtiler) EXPECT() *MockUtilerMockRecorder { } // BridgeConns mocks base method. -func (m *MockUtiler) BridgeConns(c1 io.ReadWriteCloser, c1Name string, c2 io.ReadWriteCloser, c2Name string, arg4 *logger.ReceptorLogger) { +func (m *MockUtiler) BridgeConns(c1 io.ReadWriteCloser, c1Name string, c2 io.ReadWriteCloser, c2Name string, logger *logger.ReceptorLogger) { m.ctrl.T.Helper() - m.ctrl.Call(m, "BridgeConns", c1, c1Name, c2, c2Name, arg4) + m.ctrl.Call(m, "BridgeConns", c1, c1Name, c2, c2Name, logger) } // BridgeConns indicates an expected call of BridgeConns. -func (mr *MockUtilerMockRecorder) BridgeConns(c1, c1Name, c2, c2Name, arg4 any) *gomock.Call { +func (mr *MockUtilerMockRecorder) BridgeConns(c1, c1Name, c2, c2Name, logger any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BridgeConns", reflect.TypeOf((*MockUtiler)(nil).BridgeConns), c1, c1Name, c2, c2Name, arg4) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BridgeConns", reflect.TypeOf((*MockUtiler)(nil).BridgeConns), c1, c1Name, c2, c2Name, logger) } // UnixSocketListen mocks base method. diff --git a/pkg/controlsvc/mock_controlsvc/interfaces.go b/pkg/controlsvc/mock_controlsvc/interfaces.go index 9fac5ee49..8ce371fac 100644 --- a/pkg/controlsvc/mock_controlsvc/interfaces.go +++ b/pkg/controlsvc/mock_controlsvc/interfaces.go @@ -293,17 +293,17 @@ func (m *MockControlFuncOperations) EXPECT() *MockControlFuncOperationsMockRecor } // BridgeConn mocks base method. -func (m *MockControlFuncOperations) BridgeConn(message string, bc io.ReadWriteCloser, bcName string, arg3 *logger.ReceptorLogger, utils controlsvc.Utiler) error { +func (m *MockControlFuncOperations) BridgeConn(message string, bc io.ReadWriteCloser, bcName string, logger *logger.ReceptorLogger, utils controlsvc.Utiler) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "BridgeConn", message, bc, bcName, arg3, utils) + ret := m.ctrl.Call(m, "BridgeConn", message, bc, bcName, logger, utils) ret0, _ := ret[0].(error) return ret0 } // BridgeConn indicates an expected call of BridgeConn. -func (mr *MockControlFuncOperationsMockRecorder) BridgeConn(message, bc, bcName, arg3, utils any) *gomock.Call { +func (mr *MockControlFuncOperationsMockRecorder) BridgeConn(message, bc, bcName, logger, utils any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BridgeConn", reflect.TypeOf((*MockControlFuncOperations)(nil).BridgeConn), message, bc, bcName, arg3, utils) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BridgeConn", reflect.TypeOf((*MockControlFuncOperations)(nil).BridgeConn), message, bc, bcName, logger, utils) } // Close mocks base method. @@ -321,17 +321,17 @@ func (mr *MockControlFuncOperationsMockRecorder) Close() *gomock.Call { } // ReadFromConn mocks base method. -func (m *MockControlFuncOperations) ReadFromConn(message string, out io.Writer, arg2 controlsvc.Copier) error { +func (m *MockControlFuncOperations) ReadFromConn(message string, out io.Writer, io controlsvc.Copier) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ReadFromConn", message, out, arg2) + ret := m.ctrl.Call(m, "ReadFromConn", message, out, io) ret0, _ := ret[0].(error) return ret0 } // ReadFromConn indicates an expected call of ReadFromConn. -func (mr *MockControlFuncOperationsMockRecorder) ReadFromConn(message, out, arg2 any) *gomock.Call { +func (mr *MockControlFuncOperationsMockRecorder) ReadFromConn(message, out, io any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadFromConn", reflect.TypeOf((*MockControlFuncOperations)(nil).ReadFromConn), message, out, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadFromConn", reflect.TypeOf((*MockControlFuncOperations)(nil).ReadFromConn), message, out, io) } // RemoteAddr mocks base method. diff --git a/pkg/services/mock_services/tcp_proxy.go b/pkg/services/mock_services/tcp_proxy.go index 5a8d311b3..2555a311e 100644 --- a/pkg/services/mock_services/tcp_proxy.go +++ b/pkg/services/mock_services/tcp_proxy.go @@ -288,15 +288,15 @@ func (m *MockUtilsLib) EXPECT() *MockUtilsLibMockRecorder { } // BridgeConns mocks base method. -func (m *MockUtilsLib) BridgeConns(c1 io.ReadWriteCloser, c1Name string, c2 io.ReadWriteCloser, c2Name string, arg4 *logger.ReceptorLogger) { +func (m *MockUtilsLib) BridgeConns(c1 io.ReadWriteCloser, c1Name string, c2 io.ReadWriteCloser, c2Name string, logger *logger.ReceptorLogger) { m.ctrl.T.Helper() - m.ctrl.Call(m, "BridgeConns", c1, c1Name, c2, c2Name, arg4) + m.ctrl.Call(m, "BridgeConns", c1, c1Name, c2, c2Name, logger) } // BridgeConns indicates an expected call of BridgeConns. -func (mr *MockUtilsLibMockRecorder) BridgeConns(c1, c1Name, c2, c2Name, arg4 any) *gomock.Call { +func (mr *MockUtilsLibMockRecorder) BridgeConns(c1, c1Name, c2, c2Name, logger any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BridgeConns", reflect.TypeOf((*MockUtilsLib)(nil).BridgeConns), c1, c1Name, c2, c2Name, arg4) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BridgeConns", reflect.TypeOf((*MockUtilsLib)(nil).BridgeConns), c1, c1Name, c2, c2Name, logger) } // MockTCPConn is a mock of TCPConn interface. diff --git a/pkg/services/tcp_proxy_test.go b/pkg/services/tcp_proxy_test.go index d880903c7..f1da82a21 100644 --- a/pkg/services/tcp_proxy_test.go +++ b/pkg/services/tcp_proxy_test.go @@ -9,6 +9,7 @@ import ( "github.com/ansible/receptor/pkg/logger" "github.com/ansible/receptor/pkg/netceptor" "github.com/ansible/receptor/pkg/services/mock_services" + "github.com/ansible/receptor/pkg/utils/mock_utils" "go.uber.org/mock/gomock" ) @@ -347,3 +348,50 @@ func TestTCPProxyOutboundCfgRun(t *testing.T) { }) } } + +func TestNetTCPWrapperDial(t *testing.T) { + w := &NetTCPWrapper{} + _, err := w.Dial("", "") + if err == nil { + t.Error("Expected an error to be returned.") + } +} + +func TestTLSTCPWrapperNewListener(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockNetListener := mock_services.NewMockNetListenerTCP(ctrl) + + w := &TLSTCPWrapper{} + x := w.NewListener(mockNetListener, &tls.Config{}) + + if x == nil { + t.Error("Unexpected nil value returned when creating a new listener.") + } +} + +func TestTLSTCPWrapperDial(t *testing.T) { + w := &TLSTCPWrapper{} + _, err := w.Dial("", "", &tls.Config{}) + + if err == nil { + t.Error("Expected error when dialing with incorrect parameters.") + } +} + +func TestUtilsTCPWrapperBridgeConns(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockReadWriteCloser1 := mock_utils.NewMockReadWriteCloser(ctrl) + mockReadWriteCloser2 := mock_utils.NewMockReadWriteCloser(ctrl) + + mockReadWriteCloser1.EXPECT().Read(gomock.Any()).Return(0, errors.New("EOF")).AnyTimes() + mockReadWriteCloser2.EXPECT().Read(gomock.Any()).Return(0, errors.New("EOF")).AnyTimes() + mockReadWriteCloser1.EXPECT().Close().Times(1) + mockReadWriteCloser2.EXPECT().Close().Times(1) + + w := &UtilsTCPWrapper{} + myLogger := logger.NewReceptorLogger("test") + w.BridgeConns(mockReadWriteCloser1, "", mockReadWriteCloser2, "", myLogger) +} diff --git a/pkg/utils/mock_utils/io.go b/pkg/utils/mock_utils/io.go new file mode 100644 index 000000000..d95e2482d --- /dev/null +++ b/pkg/utils/mock_utils/io.go @@ -0,0 +1,84 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: io (interfaces: ReadWriteCloser) +// +// Generated by this command: +// +// mockgen -package=mock_utils -destination=pkg/utils/mock_utils/io.go io ReadWriteCloser +// + +// Package mock_utils is a generated GoMock package. +package mock_utils + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockReadWriteCloser is a mock of ReadWriteCloser interface. +type MockReadWriteCloser struct { + ctrl *gomock.Controller + recorder *MockReadWriteCloserMockRecorder + isgomock struct{} +} + +// MockReadWriteCloserMockRecorder is the mock recorder for MockReadWriteCloser. +type MockReadWriteCloserMockRecorder struct { + mock *MockReadWriteCloser +} + +// NewMockReadWriteCloser creates a new mock instance. +func NewMockReadWriteCloser(ctrl *gomock.Controller) *MockReadWriteCloser { + mock := &MockReadWriteCloser{ctrl: ctrl} + mock.recorder = &MockReadWriteCloserMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockReadWriteCloser) EXPECT() *MockReadWriteCloserMockRecorder { + return m.recorder +} + +// Close mocks base method. +func (m *MockReadWriteCloser) Close() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Close") + ret0, _ := ret[0].(error) + return ret0 +} + +// Close indicates an expected call of Close. +func (mr *MockReadWriteCloserMockRecorder) Close() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockReadWriteCloser)(nil).Close)) +} + +// Read mocks base method. +func (m *MockReadWriteCloser) Read(p []byte) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Read", p) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Read indicates an expected call of Read. +func (mr *MockReadWriteCloserMockRecorder) Read(p any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockReadWriteCloser)(nil).Read), p) +} + +// Write mocks base method. +func (m *MockReadWriteCloser) Write(p []byte) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Write", p) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Write indicates an expected call of Write. +func (mr *MockReadWriteCloserMockRecorder) Write(p any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockReadWriteCloser)(nil).Write), p) +} From 3f5dfa030c4966ba2557467bf8bf8f3fa22e7f7e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 09:49:30 -0700 Subject: [PATCH 14/21] Bump golang.org/x/net from 0.35.0 to 0.41.0 (#1334) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 18 +++++++++--------- go.sum | 32 ++++++++++++++++---------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/go.mod b/go.mod index 9837f194b..b5ca912c9 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/ansible/receptor -go 1.22.7 +go 1.23.0 require ( github.com/creack/pty v1.1.24 @@ -24,8 +24,8 @@ require ( github.com/vishvananda/netlink v1.3.0 go.uber.org/goleak v1.3.0 go.uber.org/mock v0.5.0 - golang.org/x/net v0.35.0 - golang.org/x/sys v0.30.0 + golang.org/x/net v0.41.0 + golang.org/x/sys v0.33.0 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.31.3 k8s.io/apimachinery v0.31.3 @@ -82,15 +82,15 @@ require ( github.com/x448/float16 v0.8.4 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/crypto v0.33.0 // indirect + golang.org/x/crypto v0.39.0 // indirect golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect - golang.org/x/mod v0.22.0 // indirect + golang.org/x/mod v0.25.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect - golang.org/x/sync v0.11.0 // indirect - golang.org/x/term v0.29.0 // indirect - golang.org/x/text v0.22.0 // indirect + golang.org/x/sync v0.15.0 // indirect + golang.org/x/term v0.32.0 // indirect + golang.org/x/text v0.26.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.27.0 // indirect + golang.org/x/tools v0.33.0 // indirect google.golang.org/protobuf v1.35.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index 4d623ab45..aa31aea12 100644 --- a/go.sum +++ b/go.sum @@ -269,8 +269,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= -golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= @@ -279,8 +279,8 @@ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -293,8 +293,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -309,8 +309,8 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -322,15 +322,15 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= -golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= @@ -343,8 +343,8 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= -golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= +golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= +golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 8636a7958fbb917e333bf95918f8b5e8a4132adb Mon Sep 17 00:00:00 2001 From: Lisa Ranjbar Miller Date: Mon, 9 Jun 2025 12:47:35 -0700 Subject: [PATCH 15/21] Update to Golang 1.23.9 (#1335) --- .github/workflows/build_binary_from_ref.yml | 2 +- .github/workflows/coverage_reporting.yml | 2 +- .github/workflows/promote.yml | 2 +- .github/workflows/pull_request.yml | 6 +++--- .github/workflows/test-reporting.yml | 2 +- .readthedocs.yaml | 2 +- docs/source/installation.rst | 2 +- go.mod | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build_binary_from_ref.yml b/.github/workflows/build_binary_from_ref.yml index 10dce05c8..92132e972 100644 --- a/.github/workflows/build_binary_from_ref.yml +++ b/.github/workflows/build_binary_from_ref.yml @@ -26,7 +26,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: "1.22" + go-version: "1.23" - name: build-all target run: make receptor diff --git a/.github/workflows/coverage_reporting.yml b/.github/workflows/coverage_reporting.yml index f32f3020f..906aa2f6a 100644 --- a/.github/workflows/coverage_reporting.yml +++ b/.github/workflows/coverage_reporting.yml @@ -7,7 +7,7 @@ on: # yamllint disable-line rule:truthy branches: [devel] env: - DESIRED_GO_VERSION: '1.22' + DESIRED_GO_VERSION: '1.23' jobs: go_test_coverage: diff --git a/.github/workflows/promote.yml b/.github/workflows/promote.yml index aa358ce0d..c19b7bda3 100644 --- a/.github/workflows/promote.yml +++ b/.github/workflows/promote.yml @@ -86,7 +86,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: "1.22" + go-version: "1.23" - name: Build packages run: | diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 72463410a..17ec7e102 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -5,7 +5,7 @@ on: # yamllint disable-line rule:truthy pull_request: # yamllint disable-line rule:empty-values env: - DESIRED_GO_VERSION: '1.22' + DESIRED_GO_VERSION: '1.23' DESIRED_GOLANGCI_LINT_VERSION: 'v1.60' DESIRED_PYTHON_VERSION: '3.12' @@ -34,7 +34,7 @@ jobs: strategy: fail-fast: false matrix: - go-version: ["1.22"] + go-version: ["1.22","1.23"] steps: - name: Checkout uses: actions/checkout@v4 @@ -104,7 +104,7 @@ jobs: python-version: ${{ matrix.python-version }} session: tests-${{ matrix.python-version }} download-receptor: true - go-version: '1.22' + go-version: '1.23' lint-receptorctl: name: Lint receptorctl${{ '' }} # Nest jobs under the same sidebar category diff --git a/.github/workflows/test-reporting.yml b/.github/workflows/test-reporting.yml index 6d2788890..73f63a1d0 100644 --- a/.github/workflows/test-reporting.yml +++ b/.github/workflows/test-reporting.yml @@ -7,7 +7,7 @@ on: # yamllint disable-line rule:truthy branches: [devel] env: - DESIRED_GO_VERSION: '1.22' + DESIRED_GO_VERSION: '1.23' jobs: generate_junit_test_report: diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 740430fa0..70ad216a2 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -9,7 +9,7 @@ version: 2 build: os: ubuntu-lts-latest tools: - golang: "1.22" + golang: "1.23" python: "3.12" # You can also specify other tool versions: # nodejs: "20" diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 3e9a2f69e..b212ef412 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -6,7 +6,7 @@ Installation guide Download and extract precompiled binary for your OS and platform from `the releases page on GitHub `_ -Alternatively, you can compile Receptor from source code (Golang 1.22+ required) +Alternatively, you can compile Receptor from source code (Golang 1.23+ required) .. code-block:: bash diff --git a/go.mod b/go.mod index b5ca912c9..f2f3e299d 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/ansible/receptor -go 1.23.0 +go 1.23.9 require ( github.com/creack/pty v1.1.24 From 6aa8cf098427955b829b98384cf490ca33b97d1a Mon Sep 17 00:00:00 2001 From: Lisa Ranjbar Miller Date: Wed, 11 Jun 2025 01:36:39 -0700 Subject: [PATCH 16/21] Remove fsnotify from TestMonitorLocalStatus (#1340) --- generate.go | 1 - .../mock_workceptor/workunitbase.go | 111 ------------------ pkg/workceptor/workunitbase_test.go | 45 ++----- 3 files changed, 11 insertions(+), 146 deletions(-) delete mode 100644 pkg/workceptor/mock_workceptor/workunitbase.go diff --git a/generate.go b/generate.go index e3de8cca5..1d59b9197 100644 --- a/generate.go +++ b/generate.go @@ -21,5 +21,4 @@ package main //go:generate mockgen -source=pkg/workceptor/kubernetes.go -destination=pkg/workceptor/mock_workceptor/kubernetes.go //go:generate mockgen -source=pkg/workceptor/stdio_utils.go -destination=pkg/workceptor/mock_workceptor/stdio_utils.go //go:generate mockgen -source=pkg/workceptor/workceptor.go -destination=pkg/workceptor/mock_workceptor/workceptor.go -//go:generate mockgen -source=pkg/workceptor/workunitbase.go -destination=pkg/workceptor/mock_workceptor/workunitbase.go //go:generate mockgen -package=mock_utils -destination=pkg/utils/mock_utils/io.go io ReadWriteCloser diff --git a/pkg/workceptor/mock_workceptor/workunitbase.go b/pkg/workceptor/mock_workceptor/workunitbase.go deleted file mode 100644 index 4e72c5466..000000000 --- a/pkg/workceptor/mock_workceptor/workunitbase.go +++ /dev/null @@ -1,111 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: pkg/workceptor/workunitbase.go -// -// Generated by this command: -// -// mockgen -source=pkg/workceptor/workunitbase.go -destination=pkg/workceptor/mock_workceptor/workunitbase.go -// - -// Package mock_workceptor is a generated GoMock package. -package mock_workceptor - -import ( - reflect "reflect" - - fsnotify "github.com/fsnotify/fsnotify" - gomock "go.uber.org/mock/gomock" -) - -// MockWatcherWrapper is a mock of WatcherWrapper interface. -type MockWatcherWrapper struct { - ctrl *gomock.Controller - recorder *MockWatcherWrapperMockRecorder - isgomock struct{} -} - -// MockWatcherWrapperMockRecorder is the mock recorder for MockWatcherWrapper. -type MockWatcherWrapperMockRecorder struct { - mock *MockWatcherWrapper -} - -// NewMockWatcherWrapper creates a new mock instance. -func NewMockWatcherWrapper(ctrl *gomock.Controller) *MockWatcherWrapper { - mock := &MockWatcherWrapper{ctrl: ctrl} - mock.recorder = &MockWatcherWrapperMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockWatcherWrapper) EXPECT() *MockWatcherWrapperMockRecorder { - return m.recorder -} - -// Add mocks base method. -func (m *MockWatcherWrapper) Add(name string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Add", name) - ret0, _ := ret[0].(error) - return ret0 -} - -// Add indicates an expected call of Add. -func (mr *MockWatcherWrapperMockRecorder) Add(name any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockWatcherWrapper)(nil).Add), name) -} - -// Close mocks base method. -func (m *MockWatcherWrapper) Close() error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Close") - ret0, _ := ret[0].(error) - return ret0 -} - -// Close indicates an expected call of Close. -func (mr *MockWatcherWrapperMockRecorder) Close() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockWatcherWrapper)(nil).Close)) -} - -// ErrorChannel mocks base method. -func (m *MockWatcherWrapper) ErrorChannel() chan error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ErrorChannel") - ret0, _ := ret[0].(chan error) - return ret0 -} - -// ErrorChannel indicates an expected call of ErrorChannel. -func (mr *MockWatcherWrapperMockRecorder) ErrorChannel() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ErrorChannel", reflect.TypeOf((*MockWatcherWrapper)(nil).ErrorChannel)) -} - -// EventChannel mocks base method. -func (m *MockWatcherWrapper) EventChannel() chan fsnotify.Event { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "EventChannel") - ret0, _ := ret[0].(chan fsnotify.Event) - return ret0 -} - -// EventChannel indicates an expected call of EventChannel. -func (mr *MockWatcherWrapperMockRecorder) EventChannel() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EventChannel", reflect.TypeOf((*MockWatcherWrapper)(nil).EventChannel)) -} - -// Remove mocks base method. -func (m *MockWatcherWrapper) Remove(path string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Remove", path) - ret0, _ := ret[0].(error) - return ret0 -} - -// Remove indicates an expected call of Remove. -func (mr *MockWatcherWrapperMockRecorder) Remove(path any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockWatcherWrapper)(nil).Remove), path) -} diff --git a/pkg/workceptor/workunitbase_test.go b/pkg/workceptor/workunitbase_test.go index 5f0d12ce0..fb3d28d0b 100644 --- a/pkg/workceptor/workunitbase_test.go +++ b/pkg/workceptor/workunitbase_test.go @@ -307,15 +307,6 @@ func TestMonitorLocalStatus(t *testing.T) { logOutput string sleepDuration time.Duration }{ - { - name: "Error Adding Watcher", - statObj: NewInfo("test", 1, 0, time.Now()), - addWatcherErr: fmt.Errorf("error adding watcher"), - statErr: nil, - fsNotifyEvent: nil, - logOutput: "", - sleepDuration: 100 * time.Millisecond, - }, { name: "Error Reading Status", statObj: nil, @@ -334,7 +325,6 @@ func TestMonitorLocalStatus(t *testing.T) { randstring := randstr.RandomString(4) logFilePath := fmt.Sprintf("/tmp/monitorLocalStatusLog%s", randstring) - mockWatcher := mock_workceptor.NewMockWatcherWrapper(ctrl) mockFileSystem := mock_workceptor.NewMockFileSystemer(ctrl) bwu.Init(w, "test", "", mockFileSystem) @@ -342,34 +332,21 @@ func TestMonitorLocalStatus(t *testing.T) { if tc.statObjLater != nil { mockFileSystem.EXPECT().Stat(gomock.Any()).Return(tc.statObjLater, nil).AnyTimes() } - mockWatcher.EXPECT().Remove(gomock.Any()).AnyTimes() - mockWatcher.EXPECT().Close().AnyTimes() - - if tc.fsNotifyEvent != nil { - logFile, err := os.OpenFile(logFilePath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o600) - if err != nil { - t.Error("error creating monitorLocalStatusLog file") - } - l.SetOutput(logFile) - eventCh := make(chan fsnotify.Event, 1) - mockWatcher.EXPECT().EventChannel().Return(eventCh).AnyTimes() - go func() { eventCh <- *tc.fsNotifyEvent }() - - errorCh := make(chan error, 1) - mockWatcher.EXPECT().ErrorChannel().Return(errorCh).AnyTimes() + + logFile, err := os.OpenFile(logFilePath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o600) + if err != nil { + t.Error("error creating monitorLocalStatusLog file") } + l.SetOutput(logFile) go bwu.MonitorLocalStatus() time.Sleep(tc.sleepDuration) - - if tc.fsNotifyEvent != nil { - logOutput, err := os.ReadFile(logFilePath) - if err != nil && len(logOutput) == 0 { - t.Errorf("error reading %s file", logFilePath) - } - if !bytes.Contains(logOutput, []byte(tc.logOutput)) { - t.Errorf("expected log to be: %s, got %s", tc.logOutput, string(logOutput)) - } + logOutput, err := os.ReadFile(logFilePath) + if err != nil && len(logOutput) == 0 { + t.Errorf("error reading %s file", logFilePath) + } + if !bytes.Contains(logOutput, []byte(tc.logOutput)) { + t.Errorf("expected log to be: %s, got %s", tc.logOutput, string(logOutput)) } bwu.CancelContext() From 9b3457c637c323056a85fb9fdb92d9762657bf75 Mon Sep 17 00:00:00 2001 From: AaronH88 Date: Wed, 11 Jun 2025 17:30:39 +0100 Subject: [PATCH 17/21] Remove OSSF scorecard (#1338) --- .github/workflows/scorecard.yml | 73 --------------------------------- README.md | 3 +- 2 files changed, 1 insertion(+), 75 deletions(-) delete mode 100644 .github/workflows/scorecard.yml diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml deleted file mode 100644 index f21cd9759..000000000 --- a/.github/workflows/scorecard.yml +++ /dev/null @@ -1,73 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. They are provided -# by a third-party and are governed by separate terms of service, privacy -# policy, and support documentation. - -name: Scorecard supply-chain security -on: - # For Branch-Protection check. Only the default branch is supported. See - # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection - branch_protection_rule: - # To guarantee Maintained check is occasionally updated. See - # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained - schedule: - - cron: '41 15 * * 4' - push: - branches: ["devel"] - -# Declare default permissions as read only. -permissions: read-all - -jobs: - analysis: - name: Scorecard analysis - runs-on: ubuntu-latest - permissions: - # Needed to upload the results to code-scanning dashboard. - security-events: write - # Needed to publish results and get a badge (see publish_results below). - id-token: write - # Uncomment the permissions below if installing in a private repository. - # contents: read - # actions: read - - steps: - - name: "Checkout code" - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - with: - persist-credentials: false - - - name: "Run analysis" - uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 - with: - results_file: results.sarif - results_format: sarif - # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: - # - you want to enable the Branch-Protection check on a *public* repository, or - # - you are installing Scorecard on a *private* repository - # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional. - # repo_token: ${{ secrets.SCORECARD_TOKEN }} - - # Public repositories: - # - Publish results to OpenSSF REST API for easy access by consumers - # - Allows the repository to include the Scorecard badge. - # - See https://github.com/ossf/scorecard-action#publishing-results. - # For private repositories: - # - `publish_results` will always be set to `false`, regardless - # of the value entered here. - publish_results: true - - # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF - # format to the repository Actions tab. - - name: "Upload artifact" - uses: actions/upload-artifact@184d73b71b93c222403b2e7f1ffebe4508014249 # v4.4.2 - with: - name: SARIF file - path: results.sarif - retention-days: 5 - - # Upload the results to GitHub's code scanning dashboard (optional). - # Commenting out will disable upload of results to your repo's Code Scanning dashboard - - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@v3 - with: - sarif_file: results.sarif diff --git a/README.md b/README.md index 3ff05357d..fed0ddaa2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # Receptor -[![codecov](https://codecov.io/gh/ansible/receptor/branch/devel/graph/badge.svg?token=RAW5Bvh3hM)](https://codecov.io/gh/ansible/receptor)[![OpenSSF -Scorecard](https://api.securityscorecards.dev/projects/github.com/ansible/receptor/badge)](https://api.securityscorecards.dev/projects/github.com/ansible/receptor) +[![codecov](https://codecov.io/gh/ansible/receptor/branch/devel/graph/badge.svg?token=RAW5Bvh3hM)](https://codecov.io/gh/ansible/receptor) Receptor is an overlay network intended to ease the distribution of work across a large and dispersed collection of workers. Receptor nodes establish peer-to-peer connections with each other via existing networks. Once connected, the Receptor mesh provides datagram (UDP-like) and stream (TCP-like) capabilities to applications, as well as robust unit-of-work handling with resiliency against transient network failures. From fa01dfd0951c00316343f7b815d8121c6205ccf2 Mon Sep 17 00:00:00 2001 From: AaronH88 Date: Mon, 16 Jun 2025 15:56:40 +0100 Subject: [PATCH 18/21] Add kube tests for kube api wrapper (#1327) --- .golangci.yml | 1 + pkg/workceptor/kubernetes.go | 13 +- pkg/workceptor/kubernetes_test.go | 694 +++++++++++++++++++++++++++++- 3 files changed, 703 insertions(+), 5 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 09764a029..1cdd51e98 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -94,6 +94,7 @@ linters-settings: - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" + - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/selection" - "k8s.io/apimachinery/pkg/watch" - "k8s.io/client-go/kubernetes" diff --git a/pkg/workceptor/kubernetes.go b/pkg/workceptor/kubernetes.go index 54b225f5d..15539ef45 100644 --- a/pkg/workceptor/kubernetes.go +++ b/pkg/workceptor/kubernetes.go @@ -1356,7 +1356,9 @@ func (kw *KubeUnit) connectToKube() error { return nil } -func readFileToString(filename string) (string, error) { +// ReadFileToString reads a file and returns its contents as a string. +// If filename is empty, it returns an empty string. +func ReadFileToString(filename string) (string, error) { // If filename is "", the function returns "" if filename == "" { return "", nil @@ -1387,11 +1389,11 @@ func (kw *KubeUnit) SetFromParams(params map[string]string) error { return ssf } var err error - ked.KubePod, err = readFileToString(ked.KubePod) + ked.KubePod, err = ReadFileToString(ked.KubePod) if err != nil { return fmt.Errorf("could not read pod: %s", err) } - ked.KubeConfig, err = readFileToString(ked.KubeConfig) + ked.KubeConfig, err = ReadFileToString(ked.KubeConfig) if err != nil { return fmt.Errorf("could not read kubeconfig: %s", err) } @@ -1469,6 +1471,11 @@ func (kw *KubeUnit) Status() *StatusFileData { return status } +// SetClientset sets the clientset for testing purposes. +func (kw *KubeUnit) SetClientset(clientset *kubernetes.Clientset) { + kw.clientset = clientset +} + // Status returns a copy of the status currently loaded in memory. func (kw *KubeUnit) UnredactedStatus() *StatusFileData { kw.GetStatusLock().RLock() diff --git a/pkg/workceptor/kubernetes_test.go b/pkg/workceptor/kubernetes_test.go index 66cbb84bc..e8ea91c26 100644 --- a/pkg/workceptor/kubernetes_test.go +++ b/pkg/workceptor/kubernetes_test.go @@ -1,7 +1,11 @@ +//go:build !no_workceptor +// +build !no_workceptor + package workceptor_test import ( "context" + "errors" "io" "net/http" "os" @@ -15,11 +19,13 @@ import ( "github.com/ansible/receptor/pkg/netceptor" "github.com/ansible/receptor/pkg/workceptor" "github.com/ansible/receptor/pkg/workceptor/mock_workceptor" + "github.com/stretchr/testify/assert" "go.uber.org/mock/gomock" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/selection" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/kubernetes" @@ -199,7 +205,7 @@ func TestParseTime(t *testing.T) { } } -func createKubernetesTestSetup(t *testing.T) (workceptor.WorkUnit, *mock_workceptor.MockBaseWorkUnitForWorkUnit, *mock_workceptor.MockNetceptorForWorkceptor, *workceptor.Workceptor, *mock_workceptor.MockKubeAPIer, *gomock.Controller, context.Context) { +func createKubernetesTestSetup(t *testing.T, options ...string) (workceptor.WorkUnit, *mock_workceptor.MockBaseWorkUnitForWorkUnit, *mock_workceptor.MockNetceptorForWorkceptor, *workceptor.Workceptor, *mock_workceptor.MockKubeAPIer, *gomock.Controller, context.Context) { ctrl := gomock.NewController(t) ctx := context.Background() @@ -214,7 +220,24 @@ func createKubernetesTestSetup(t *testing.T) (workceptor.WorkUnit, *mock_workcep } mockBaseWorkUnit.EXPECT().Init(w, "", "", workceptor.FileSystem{}) - kubeConfig := workceptor.KubeWorkerCfg{AuthMethod: "incluster"} + + // Default configuration + kubeConfig := workceptor.KubeWorkerCfg{ + AuthMethod: "incluster", + StreamMethod: "logger", + DeletePodOnRestart: false, + } + + // Apply options + for _, option := range options { + if strings.HasPrefix(option, "streamMethod=") { + kubeConfig.StreamMethod = strings.TrimPrefix(option, "streamMethod=") + } else if strings.HasPrefix(option, "deletePodOnRestart=") { + deletePodOnRestart := strings.TrimPrefix(option, "deletePodOnRestart=") + kubeConfig.DeletePodOnRestart = (deletePodOnRestart == "true") + } + } + ku := kubeConfig.NewkubeWorker(mockBaseWorkUnit, w, "", "", mockKubeAPI) return ku, mockBaseWorkUnit, mockNetceptor, w, mockKubeAPI, ctrl, ctx @@ -562,3 +585,670 @@ func TestKubeLoggingWithReconnect(t *testing.T) { }) } } + +// TestKubeAPIWrapper tests the KubeAPIWrapper methods. +func TestKubeAPIWrapper(t *testing.T) { + // Create a KubeAPIWrapper instance + wrapper := workceptor.KubeAPIWrapper{} + + // Test NewNotFound + t.Run("NewNotFound", func(t *testing.T) { + gr := schema.GroupResource{Group: "test", Resource: "test"} + err := wrapper.NewNotFound(gr, "test-name") + assert.NotNil(t, err) + assert.True(t, apierrors.IsNotFound(err)) + }) + + // Test OneTermEqualSelector + t.Run("OneTermEqualSelector", func(t *testing.T) { + selector := wrapper.OneTermEqualSelector("key", "value") + assert.NotNil(t, selector) + requirements := selector.Requirements() + assert.Equal(t, 1, len(requirements)) + assert.Equal(t, "key", requirements[0].Field) + assert.Equal(t, "value", requirements[0].Value) + }) + + // Test NewFakeNeverRateLimiter + t.Run("NewFakeNeverRateLimiter", func(t *testing.T) { + limiter := wrapper.NewFakeNeverRateLimiter() + assert.NotNil(t, limiter) + // This should never wait + assert.Equal(t, false, limiter.TryAccept()) + }) + + // Test NewFakeAlwaysRateLimiter + t.Run("NewFakeAlwaysRateLimiter", func(t *testing.T) { + limiter := wrapper.NewFakeAlwaysRateLimiter() + assert.NotNil(t, limiter) + // This should always wait + assert.Equal(t, true, limiter.TryAccept()) + }) +} + +// TestKubeAPIWrapperExtended tests the remaining KubeAPIWrapper methods. +func TestKubeAPIWrapperExtended(t *testing.T) { + // Create a KubeAPIWrapper instance + wrapper := workceptor.KubeAPIWrapper{} + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // Test NewForConfig + t.Run("NewForConfig", func(t *testing.T) { + // Verify NewForConfig method exists with correct signature + methodType := reflect.TypeOf(wrapper.NewForConfig) + assert.Equal(t, "func(*rest.Config) (*kubernetes.Clientset, error)", methodType.String()) + }) + + // Test GetLogs + t.Run("GetLogs", func(t *testing.T) { + // Create a mock clientset + clientset := kubernetes.NewForConfigOrDie(&rest.Config{Host: "https://localhost:8443"}) + // Call the method + req := wrapper.GetLogs(clientset, "default", "test-pod", &corev1.PodLogOptions{}) + // Verify the request is created correctly + assert.NotNil(t, req) + assert.Contains(t, req.URL().Path, "pods") + assert.Contains(t, req.URL().Path, "test-pod") + assert.Contains(t, req.URL().Path, "log") + }) + + // Test SubResource + t.Run("SubResource", func(t *testing.T) { + // Create a mock clientset + clientset := kubernetes.NewForConfigOrDie(&rest.Config{Host: "https://localhost:8443"}) + // Call the method + req := wrapper.SubResource(clientset, "test-pod", "default") + // Verify the request is created correctly + assert.NotNil(t, req) + assert.Contains(t, req.URL().Path, "pods") + assert.Contains(t, req.URL().Path, "test-pod") + assert.Contains(t, req.URL().Path, "attach") + }) + + // Test NewDefaultClientConfigLoadingRules + t.Run("NewDefaultClientConfigLoadingRules", func(t *testing.T) { + // Call the method + rules := wrapper.NewDefaultClientConfigLoadingRules() + // Verify the rules are created correctly + assert.NotNil(t, rules) + }) + + // Test BuildConfigFromFlags + t.Run("BuildConfigFromFlags", func(t *testing.T) { + _, err := wrapper.BuildConfigFromFlags("", "") + assert.Error(t, err) + }) + + // Test NewClientConfigFromBytes + t.Run("NewClientConfigFromBytes", func(t *testing.T) { + // Create a minimal kubeconfig + kubeconfig := ` +apiVersion: v1 +kind: Config +clusters: +- cluster: + server: https://localhost:8443 + name: test-cluster +contexts: +- context: + cluster: test-cluster + user: test-user + name: test-context +current-context: test-context +users: +- name: test-user + user: + token: test-token +` + // Call the method + config, err := wrapper.NewClientConfigFromBytes([]byte(kubeconfig)) + // Verify the config is created correctly + assert.NoError(t, err) + assert.NotNil(t, config) + }) + + // Test Get, Create, List, Watch, Delete + t.Run("Pod CRUD Operations", func(t *testing.T) { + clientset := kubernetes.NewForConfigOrDie(&rest.Config{Host: "https://localhost:8443"}) + ctx := context.Background() + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pod", + Namespace: "default", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "test-container", + Image: "test-image", + }, + }, + }, + } + + _, err := wrapper.Get(ctx, clientset, "default", "test-pod", metav1.GetOptions{}) + assert.Error(t, err) // We expect an error here since we're not connecting to a real API server + + _, err = wrapper.Create(ctx, clientset, "default", pod, metav1.CreateOptions{}) + assert.Error(t, err) + + _, err = wrapper.List(ctx, clientset, "default", metav1.ListOptions{}) + assert.Error(t, err) + + _, err = wrapper.Watch(ctx, clientset, "default", metav1.ListOptions{}) + assert.Error(t, err) + + err = wrapper.Delete(ctx, clientset, "default", "test-pod", metav1.DeleteOptions{}) + assert.Error(t, err) + }) + + // Test InClusterConfig + t.Run("InClusterConfig", func(t *testing.T) { + // This will fail because we're not running inside a Kubernetes cluster + // but it will exercise the code path + _, err := wrapper.InClusterConfig() + assert.Error(t, err) // We expect an error here + }) + + // Test NewSPDYExecutor and StreamWithContext + t.Run("SPDY Operations", func(t *testing.T) { + // Verify NewSPDYExecutor method exists with correct signature + methodType := reflect.TypeOf(wrapper.NewSPDYExecutor) + assert.Equal(t, "func(*rest.Config, string, *url.URL) (remotecommand.Executor, error)", methodType.String()) + + // Verify StreamWithContext method exists with correct signature + methodType = reflect.TypeOf(wrapper.StreamWithContext) + assert.Equal(t, "func(context.Context, remotecommand.Executor, remotecommand.StreamOptions) error", methodType.String()) + }) + + // Test UntilWithSync + t.Run("UntilWithSync", func(t *testing.T) { + // Verify UntilWithSync method exists with correct signature + methodType := reflect.TypeOf(wrapper.UntilWithSync) + // Just check that it's a function that returns the right types + assert.Contains(t, methodType.String(), "func(context.Context, cache.ListerWatcher, runtime.Object") + assert.Contains(t, methodType.String(), "(*watch.Event, error)") + }) +} + +// TestReadFileToString tests the ReadFileToString function. +func TestReadFileToString(t *testing.T) { + // Create a temporary file for testing + content := "test content" + tmpfile, err := os.CreateTemp("", "test") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpfile.Name()) + + // Write content to the file + if _, err := tmpfile.Write([]byte(content)); err != nil { + t.Fatal(err) + } + if err := tmpfile.Close(); err != nil { + t.Fatal(err) + } + + // Test with empty filename + t.Run("Empty filename", func(t *testing.T) { + result, err := workceptor.ReadFileToString("") + assert.NoError(t, err) + assert.Equal(t, "", result) + }) + + // Test with valid file + t.Run("Valid file", func(t *testing.T) { + result, err := workceptor.ReadFileToString(tmpfile.Name()) + assert.NoError(t, err) + assert.Equal(t, content, result) + }) + + // Test with non-existent file + t.Run("Non-existent file", func(t *testing.T) { + result, err := workceptor.ReadFileToString("/non/existent/file") + assert.Error(t, err) + assert.Equal(t, "", result) + }) +} + +// TestParseTimeExtended tests the ParseTime function with more cases. +func TestParseTimeExtended(t *testing.T) { + tests := []struct { + name string + input string + expected bool // true if we expect a non-nil result + }{ + { + name: "RFC3339", + input: "2024-01-17T00:00:00Z", + expected: true, + }, + { + name: "RFC3339Nano", + input: "2024-01-17T00:00:00.123456789Z", + expected: true, + }, + { + name: "Invalid format", + input: "2024-01-17", + expected: false, + }, + { + name: "Empty string", + input: "", + expected: false, + }, + { + name: "Random string", + input: "not a time", + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := workceptor.ParseTime(tt.input) + if tt.expected { + assert.NotNil(t, result) + } else { + assert.Nil(t, result) + } + }) + } +} + +// TestIsCompatibleK8SExtended tests the IsCompatibleK8S function with more cases. +func TestIsCompatibleK8SExtended(t *testing.T) { + kw, err := startNetceptorNodeWithWorkceptor() + if err != nil { + t.Fatal(err) + } + + tests := []struct { + name string + versionStr string + want bool + }{ + { + name: "Empty version", + versionStr: "", + want: false, + }, + { + name: "Invalid version format", + versionStr: "invalid", + want: false, + }, + { + name: "Version with only major", + versionStr: "v1", + want: false, + }, + { + name: "Version with major and minor", + versionStr: "v1.26", + want: false, + }, + { + name: "Version 1.27.0", + versionStr: "v1.27.0", + want: true, + }, + { + name: "Version 1.28.0", + versionStr: "v1.28.0", + want: true, + }, + { + name: "Version 1.29.0", + versionStr: "v1.29.0", + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := workceptor.IsCompatibleK8S(kw, tt.versionStr); got != tt.want { + t.Errorf("IsCompatibleK8S() = %v, want %v", got, tt.want) + } + }) + } +} + +// TestGetTimeoutOpenLogstreamExtended tests the GetTimeoutOpenLogstream function with more cases. +func TestGetTimeoutOpenLogstreamExtended(t *testing.T) { + const envVariable string = "RECEPTOR_OPEN_LOGSTREAM_TIMEOUT" + + kw, err := startNetceptorNodeWithWorkceptor() + if err != nil { + t.Fatal(err) + } + + tests := []struct { + name string + envValue string + want int + }{ + { + name: "Large value", + envValue: "100", + want: 100, + }, + { + name: "Zero value", + envValue: "0", + want: 1, // Should default to 1 + }, + { + name: "Negative value", + envValue: "-10", + want: 1, // Should default to 1 + }, + { + name: "Non-integer value", + envValue: "abc", + want: 1, // Should default to 1 + }, + { + name: "Float value", + envValue: "1.5", + want: 1, // Should default to 1 + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.envValue != "" { + os.Setenv(envVariable, tt.envValue) + defer os.Unsetenv(envVariable) + } else { + os.Unsetenv(envVariable) + } + + if got := workceptor.GetTimeoutOpenLogstream(kw); got != tt.want { + t.Errorf("GetTimeoutOpenLogstream() = %v, want %v", got, tt.want) + } + }) + } +} + +// TestKubeLoggingWithReconnectSimple tests the KubeLoggingWithReconnect function with a simple success case. +func TestKubeLoggingWithReconnectSimple(t *testing.T) { + // We'll test just the success case for now to avoid mock complexity + var stdinErr error + var stdoutErr error + _, mockBaseWorkUnit, mockNetceptor, w, mockKubeAPI, ctrl, ctx := createKubernetesTestSetup(t) + defer ctrl.Finish() + + pod := &corev1.Pod{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{Name: "Test_Name", Namespace: "Test_Namespace"}, + Spec: corev1.PodSpec{}, + Status: corev1.PodStatus{Phase: corev1.PodRunning}, + } + + kw := &workceptor.KubeUnit{ + BaseWorkUnitForWorkUnit: mockBaseWorkUnit, + KubeAPIWrapperInstance: mockKubeAPI, + Pod: pod, + } + + // Set up expectations + mockBaseWorkUnit.EXPECT().GetWorkceptor().Return(w).AnyTimes() + mockBaseWorkUnit.EXPECT().GetContext().Return(ctx).AnyTimes() + mockKubeAPI.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(pod, nil) + logger := logger.NewReceptorLogger("") + mockNetceptor.EXPECT().GetLogger().Return(logger).AnyTimes() + + // Set up the fake REST client + req := fakerest.RESTClient{ + Client: fakerest.CreateHTTPClient(func(request *http.Request) (*http.Response, error) { + resp := &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader("2024-12-09T00:31:18.823849250Z Log line with timestamp\n")), + } + + return resp, nil + }), + NegotiatedSerializer: scheme.Codecs.WithoutConversion(), + } + mockKubeAPI.EXPECT().GetLogs(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(req.Request()) + + wg := &sync.WaitGroup{} + wg.Add(1) + mockfilesystemer := mock_workceptor.NewMockFileSystemer(ctrl) + mockfilesystemer.EXPECT().OpenFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(&os.File{}, nil) + stdout, _ := workceptor.NewStdoutWriter(mockfilesystemer, "") + mockFileWC := mock_workceptor.NewMockFileWriteCloser(ctrl) + stdout.SetWriter(mockFileWC) + mockFileWC.EXPECT().Write(gomock.Any()).Return(0, nil).AnyTimes() + + kw.KubeLoggingWithReconnect(wg, stdout, &stdinErr, &stdoutErr) + + assert.NoError(t, stdoutErr) +} + +// TestKubeUnitCancel tests the Cancel method of KubeUnit. +func TestKubeUnitCancel(t *testing.T) { + // Create a test setup + _, mockBaseWorkUnit, mockNetceptor, w, mockKubeAPI, ctrl, _ := createKubernetesTestSetup(t) + defer ctrl.Finish() + + // Create a pod for testing + pod := &corev1.Pod{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{Name: "Test_Name", Namespace: "Test_Namespace"}, + Spec: corev1.PodSpec{}, + Status: corev1.PodStatus{Phase: corev1.PodRunning}, + } + + // Create a clientset + clientset := &kubernetes.Clientset{} + + // Create a KubeUnit for testing + kw := &workceptor.KubeUnit{ + BaseWorkUnitForWorkUnit: mockBaseWorkUnit, + KubeAPIWrapperInstance: mockKubeAPI, + Pod: pod, + } + + // Set the clientset + kw.SetClientset(clientset) + + // Set up expectations + mockBaseWorkUnit.EXPECT().CancelContext().Times(2) + mockBaseWorkUnit.EXPECT().UpdateBasicStatus(workceptor.WorkStateCanceled, "Canceled", int64(-1)) + mockBaseWorkUnit.EXPECT().GetCancel().Return(func() {}) + mockBaseWorkUnit.EXPECT().GetWorkceptor().Return(w).AnyTimes() + mockNetceptor.EXPECT().NodeID().Return("NodeID").AnyTimes() + logger := logger.NewReceptorLogger("") + mockNetceptor.EXPECT().GetLogger().Return(logger).AnyTimes() + + // Mock the Delete method + mockKubeAPI.EXPECT().Delete( + gomock.Any(), + gomock.Eq(clientset), + gomock.Eq(pod.Namespace), + gomock.Eq(pod.Name), + gomock.Any(), + ).Return(nil) + + // Call the method being tested + err := kw.Cancel() + + // Verify the results + assert.NoError(t, err) +} + +// TestKubeUnitRelease tests the Release method of KubeUnit. +func TestKubeUnitRelease(t *testing.T) { + // Create a test setup + _, mockBaseWorkUnit, mockNetceptor, w, mockKubeAPI, ctrl, _ := createKubernetesTestSetup(t) + defer ctrl.Finish() + + // Create a pod for testing + pod := &corev1.Pod{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{Name: "Test_Name", Namespace: "Test_Namespace"}, + Spec: corev1.PodSpec{}, + Status: corev1.PodStatus{Phase: corev1.PodRunning}, + } + + // Create a clientset + clientset := &kubernetes.Clientset{} + + // Create a KubeUnit for testing + kw := &workceptor.KubeUnit{ + BaseWorkUnitForWorkUnit: mockBaseWorkUnit, + KubeAPIWrapperInstance: mockKubeAPI, + Pod: pod, + } + + // Set the clientset + kw.SetClientset(clientset) + + // Set up expectations for Cancel + mockBaseWorkUnit.EXPECT().CancelContext().Times(2) + mockBaseWorkUnit.EXPECT().UpdateBasicStatus(workceptor.WorkStateCanceled, "Canceled", int64(-1)) + mockBaseWorkUnit.EXPECT().GetCancel().Return(func() {}) + mockBaseWorkUnit.EXPECT().GetWorkceptor().Return(w).AnyTimes() + mockNetceptor.EXPECT().NodeID().Return("NodeID").AnyTimes() + logger := logger.NewReceptorLogger("") + mockNetceptor.EXPECT().GetLogger().Return(logger).AnyTimes() + + // Mock the Delete method + mockKubeAPI.EXPECT().Delete( + gomock.Any(), + gomock.Eq(clientset), + gomock.Eq(pod.Namespace), + gomock.Eq(pod.Name), + gomock.Any(), + ).Return(nil) + + // Set up expectations for Release + mockBaseWorkUnit.EXPECT().Release(false).Return(nil) + + // Call the method being tested + err := kw.Release(false) + + // Verify the results + assert.NoError(t, err) +} + +// TestKubeUnitReleaseWithForce tests the Release method of KubeUnit with force=true. +func TestKubeUnitReleaseWithForce(t *testing.T) { + // Create a test setup + _, mockBaseWorkUnit, mockNetceptor, w, mockKubeAPI, ctrl, _ := createKubernetesTestSetup(t) + defer ctrl.Finish() + + // Create a pod for testing + pod := &corev1.Pod{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{Name: "Test_Name", Namespace: "Test_Namespace"}, + Spec: corev1.PodSpec{}, + Status: corev1.PodStatus{Phase: corev1.PodRunning}, + } + + // Create a clientset + clientset := &kubernetes.Clientset{} + + // Create a KubeUnit for testing + kw := &workceptor.KubeUnit{ + BaseWorkUnitForWorkUnit: mockBaseWorkUnit, + KubeAPIWrapperInstance: mockKubeAPI, + Pod: pod, + } + + // Set the clientset + kw.SetClientset(clientset) + + // Set up expectations for Cancel (with error) + mockBaseWorkUnit.EXPECT().CancelContext().Times(2) + mockBaseWorkUnit.EXPECT().UpdateBasicStatus(workceptor.WorkStateCanceled, "Canceled", int64(-1)) + mockBaseWorkUnit.EXPECT().GetCancel().Return(func() {}) + mockBaseWorkUnit.EXPECT().GetWorkceptor().Return(w).AnyTimes() + mockNetceptor.EXPECT().NodeID().Return("NodeID").AnyTimes() + logger := logger.NewReceptorLogger("") + mockNetceptor.EXPECT().GetLogger().Return(logger).AnyTimes() + + // Mock the Delete method with an error + mockKubeAPI.EXPECT().Delete( + gomock.Any(), + gomock.Eq(clientset), + gomock.Eq(pod.Namespace), + gomock.Eq(pod.Name), + gomock.Any(), + ).Return(errors.New("delete error")) + + // Set up expectations for Release + mockBaseWorkUnit.EXPECT().Release(true).Return(nil) + + // Call the method being tested + err := kw.Release(true) + + // Verify the results + assert.NoError(t, err) +} + +// TestKubeUnitRestart tests the Restart method of KubeUnit. +func TestKubeUnitRestart(t *testing.T) { + t.Run("Complete state", func(t *testing.T) { + // Create a test setup + _, mockBaseWorkUnit, _, _, _, ctrl, _ := createKubernetesTestSetup(t) + defer ctrl.Finish() + + // Create a KubeUnit for testing + kw := &workceptor.KubeUnit{ + BaseWorkUnitForWorkUnit: mockBaseWorkUnit, + } + + // Set up expectations + status := &workceptor.StatusFileData{ + State: workceptor.WorkStateSucceeded, + ExtraData: &workceptor.KubeExtraData{}, + } + mockBaseWorkUnit.EXPECT().Status().Return(status).AnyTimes() + + // Mock other necessary methods + lock := &sync.RWMutex{} + mockBaseWorkUnit.EXPECT().GetStatusLock().Return(lock).AnyTimes() + mockBaseWorkUnit.EXPECT().GetStatusWithoutExtraData().Return(status).AnyTimes() + mockBaseWorkUnit.EXPECT().GetStatusCopy().Return(*status).AnyTimes() + + // Call the method being tested + err := kw.Restart() + + // Verify the results + assert.NoError(t, err) + }) + + t.Run("Running state with TCP", func(t *testing.T) { + // Create a test setup with tcp stream method + ku, mockBaseWorkUnit, _, _, _, ctrl, _ := createKubernetesTestSetup(t, "streamMethod=tcp") + defer ctrl.Finish() + + // Use the KubeUnit from createKubernetesTestSetup + kw := ku.(*workceptor.KubeUnit) + + // Set up expectations + status := &workceptor.StatusFileData{ + State: workceptor.WorkStateRunning, + ExtraData: &workceptor.KubeExtraData{}, + } + mockBaseWorkUnit.EXPECT().Status().Return(status).AnyTimes() + + // Mock other necessary methods + lock := &sync.RWMutex{} + mockBaseWorkUnit.EXPECT().GetStatusLock().Return(lock).AnyTimes() + mockBaseWorkUnit.EXPECT().GetStatusWithoutExtraData().Return(status).AnyTimes() + mockBaseWorkUnit.EXPECT().GetStatusCopy().Return(*status).AnyTimes() + + // Call the method being tested + err := kw.Restart() + + // Verify the results + assert.Error(t, err) + assert.Contains(t, err.Error(), "restart not implemented for streammethod tcp") + }) +} From 456954e9addc56b99f8b3d0241f6c21b9c5cab65 Mon Sep 17 00:00:00 2001 From: Dan Leehr Date: Tue, 17 Jun 2025 10:29:02 -0400 Subject: [PATCH 19/21] Additional unit tests for pkg/netceptor/conn.go (#1341) --- pkg/netceptor/conn_internal_test.go | 252 ++++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 pkg/netceptor/conn_internal_test.go diff --git a/pkg/netceptor/conn_internal_test.go b/pkg/netceptor/conn_internal_test.go new file mode 100644 index 000000000..c11eddcc9 --- /dev/null +++ b/pkg/netceptor/conn_internal_test.go @@ -0,0 +1,252 @@ +package netceptor + +import ( + "context" + "crypto/x509" + "strings" + "testing" +) + +// TestServerTLSConfig tests the GenerateServerTLSConfig function. +func TestServerTLSConfig(t *testing.T) { + // Call the function + config := generateServerTLSConfig() + + // Verify the result + if config == nil { + t.Fatal("Expected config to be non-nil") + } + if len(config.NextProtos) != 1 || config.NextProtos[0] != "netceptor" { + t.Errorf("Expected NextProtos to be ['netceptor'], got %v", config.NextProtos) + } + if len(config.Certificates) != 1 { + t.Errorf("Expected 1 certificate, got %d", len(config.Certificates)) + } + + // Verify the certificate + cert, err := x509.ParseCertificate(config.Certificates[0].Certificate[0]) + if err != nil { + t.Fatalf("Failed to parse certificate: %v", err) + } + if cert.Subject.CommonName != "netceptor-insecure-common-name" { + t.Errorf("Expected CommonName to be 'netceptor-insecure-common-name', got '%s'", cert.Subject.CommonName) + } +} + +// TestServerCertVerification tests the VerifyServerCertificate function. +func TestServerCertVerification(t *testing.T) { + // Generate a server TLS config to get a valid certificate + config := generateServerTLSConfig() + rawCert := config.Certificates[0].Certificate[0] + + tests := []struct { + name string + rawCerts [][]byte + expectError bool + }{ + { + name: "Valid certificate", + rawCerts: [][]byte{rawCert}, + expectError: false, + }, + { + name: "No certificates", + rawCerts: [][]byte{}, + expectError: true, + }, + { + name: "Invalid certificate data", + rawCerts: [][]byte{{1, 2, 3, 4}}, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := verifyServerCertificate(tt.rawCerts, nil) + if tt.expectError { + if err == nil { + t.Errorf("Expected error for test case '%s', but got nil", tt.name) + } + } else { + if err != nil { + t.Errorf("Expected no error for test case '%s', but got: %v", tt.name, err) + } + } + }) + } +} + +// TestClientTLSConfig tests the GenerateClientTLSConfig function. +func TestClientTLSConfig(t *testing.T) { + // Call the function + host := "test-host" + config := generateClientTLSConfig(host) + + // Verify the result + if config == nil { + t.Fatal("Expected config to be non-nil") + } + if !config.InsecureSkipVerify { + t.Error("Expected InsecureSkipVerify to be true") + } + if config.VerifyPeerCertificate == nil { + t.Error("Expected VerifyPeerCertificate to be non-nil") + } + if len(config.NextProtos) != 1 || config.NextProtos[0] != "netceptor" { + t.Errorf("Expected NextProtos to be ['netceptor'], got %v", config.NextProtos) + } + if config.ServerName != host { + t.Errorf("Expected ServerName to be '%s', got '%s'", host, config.ServerName) + } +} + +// TestNetceptorListen tests the Listen method functionality. +func TestNetceptorListen(t *testing.T) { + tests := []struct { + name string + serviceName string + expectError bool + expectedErrorSubstr string + needsCleanup bool + }{ + { + name: "Valid service name", + serviceName: "abcd", // 4 characters, within the 8-character limit + expectError: false, + needsCleanup: true, + }, + { + name: "Service name too long", + serviceName: "service-name-too-long", // 22 characters, exceeds 8-character limit + expectError: true, + expectedErrorSubstr: "service name service-name-too-long too long", + needsCleanup: false, + }, + { + name: "Empty service name gets ephemeral", + serviceName: "", // Empty service name should get an ephemeral service + expectError: false, + needsCleanup: true, + }, + { + name: "Maximum length service name", + serviceName: "abcd1234", // 8 characters, maximum allowed length + expectError: false, + needsCleanup: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a Netceptor instance for each test case + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + s := New(ctx, "test-node") + + // Call Listen + listener, err := s.Listen(tt.serviceName, nil) + + if tt.expectError { + if err == nil { + t.Errorf("Expected error for test case '%s', but got nil", tt.name) + } + if listener != nil { + t.Errorf("Expected listener to be nil for test case '%s', but got non-nil", tt.name) + } + if tt.expectedErrorSubstr != "" && err != nil { + if !strings.Contains(err.Error(), tt.expectedErrorSubstr) { + t.Errorf("Expected error to contain '%s', but got '%s'", tt.expectedErrorSubstr, err.Error()) + } + } + } else { + if err != nil { + t.Errorf("Expected no error for test case '%s', but got: %v", tt.name, err) + } + if listener == nil { + t.Errorf("Expected listener to be non-nil for test case '%s'", tt.name) + } + + if tt.needsCleanup && listener != nil { + err = listener.Close() + if err != nil { + t.Errorf("Failed to close listener for test case '%s': %v", tt.name, err) + } + } + } + }) + } +} + +// TestNetceptorListenAndAdvertise tests basic functionality of the ListenAndAdvertise method. +func TestNetceptorListenAndAdvertise(t *testing.T) { + // Create a Netceptor instance + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + s := New(ctx, "test-node") + + serviceName := "test-svc" + + // Call ListenAndAdvertise + tags := map[string]string{"tag1": "value1"} + listener, err := s.ListenAndAdvertise(serviceName, nil, tags) + if err != nil { + t.Fatalf("Failed to listen and advertise: %v", err) + } + + // Verify the listener + if listener == nil { + t.Fatal("Expected listener to be non-nil") + } + + // Clean up + err = listener.Close() + if err != nil { + t.Errorf("Failed to close listener: %v", err) + } +} + +// TestNetceptorDialInvalidService tests that Dial returns an error for invalid services. +func TestNetceptorDialInvalidService(t *testing.T) { + // Create a Netceptor instance + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + s := New(ctx, "test-node") + + // Call Dial with a non-existent node and service + conn, err := s.Dial("non-existent-node", "non-existent-service", nil) + + // Verify the result - we expect an error because the node doesn't exist + if err == nil { + t.Error("Expected error when dialing non-existent service, but got nil") + } + if conn != nil { + t.Error("Expected conn to be nil when dialing non-existent service, but got non-nil") + } +} + +// TestNetceptorDialContextCanceled tests that DialContext returns an error when the context is canceled. +func TestNetceptorDialContextCanceled(t *testing.T) { + // Create a Netceptor instance + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + s := New(ctx, "test-node") + + // Create a canceled context + canceledCtx, cancelFunc := context.WithCancel(context.Background()) + cancelFunc() + + // Call DialContext with the canceled context + conn, err := s.DialContext(canceledCtx, "non-existent-node", "non-existent-service", nil) + + // Verify the result - we expect a context canceled error + if err == nil { + t.Error("Expected error when dialing with canceled context, but got nil") + } + if conn != nil { + t.Error("Expected conn to be nil when dialing with canceled context, but got non-nil") + } + if err != nil && !strings.Contains(err.Error(), "context canceled") { + t.Errorf("Expected error to contain 'context canceled', but got '%s'", err.Error()) + } +} From 334b7241ffebdd8c9189b3310b3134dfe6618626 Mon Sep 17 00:00:00 2001 From: Dan Leehr Date: Tue, 17 Jun 2025 15:06:28 -0400 Subject: [PATCH 20/21] Fix a test that fails on go 1.24 due to crypto/rsa changes (#1344) --- pkg/certificates/ca_test.go | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/pkg/certificates/ca_test.go b/pkg/certificates/ca_test.go index 13b752a8c..14851d886 100644 --- a/pkg/certificates/ca_test.go +++ b/pkg/certificates/ca_test.go @@ -13,6 +13,7 @@ import ( "io" "net" "reflect" + "slices" "testing" "time" @@ -1039,27 +1040,30 @@ func TestCreateCertReqWithKeyNegative(t *testing.T) { } tests := []struct { - name string - args args - want *x509.CertificateRequest - want1 *rsa.PrivateKey - wantErr error + name string + args args + want *x509.CertificateRequest + want1 *rsa.PrivateKey + wantErrs []string }{ { name: "Negative test for Bits", args: args{ opts: &badCertOptions, }, - want: nil, - want1: nil, - wantErr: fmt.Errorf("crypto/rsa: too few primes of given length to generate an RSA key"), + want: nil, + want1: nil, + wantErrs: []string{ + "crypto/rsa: too few primes of given length to generate an RSA key", + "rsa: key too small", + }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, _, gotErr := certificates.CreateCertReqWithKey(tt.args.opts) - if gotErr == nil || gotErr.Error() != tt.wantErr.Error() { - t.Errorf("CreateCertReqWithKey() error = %v, wantErr = %v", gotErr, tt.wantErr) + if gotErr == nil || !slices.Contains(tt.wantErrs, gotErr.Error()) { + t.Errorf("CreateCertReqWithKey() error = %v, wantErr = %v", gotErr, tt.wantErrs) } }) } From c078fc3acc472fe4ad29fbd76008e5e304407c3b Mon Sep 17 00:00:00 2001 From: AaronH88 Date: Thu, 19 Jun 2025 21:59:21 +0100 Subject: [PATCH 21/21] =?UTF-8?q?Add=20tests=20in=20the=20runProtocol=20to?= =?UTF-8?q?=20prove=20that=20the=20connection=20is=20not=20remo=E2=80=A6?= =?UTF-8?q?=20(#1348)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/netceptor/netceptor.go | 5 + pkg/netceptor/netceptor_test.go | 522 ++++++++++++++++++++++++++++++++ 2 files changed, 527 insertions(+) diff --git a/pkg/netceptor/netceptor.go b/pkg/netceptor/netceptor.go index 304c1da0e..6ec6ab1ac 100644 --- a/pkg/netceptor/netceptor.go +++ b/pkg/netceptor/netceptor.go @@ -2028,6 +2028,11 @@ func (s *Netceptor) runProtocol(ctx context.Context, sess BackendSession, bi *Ba } if ok && connError != nil { s.Logger.Error("Context for existing connection error: %s", connError) + s.connLock.Unlock() + // Remove the canceled connection to prevent resource leak + s.removeConnection(remoteNodeID) + s.connLock.Lock() + remoteNodeAccepted = true // Allow the new connection to proceed } if !remoteNodeAccepted { diff --git a/pkg/netceptor/netceptor_test.go b/pkg/netceptor/netceptor_test.go index 1b89509cd..0b885ee0c 100644 --- a/pkg/netceptor/netceptor_test.go +++ b/pkg/netceptor/netceptor_test.go @@ -894,3 +894,525 @@ func TestTracerDoesNotReturnsNewConnectionTracer(t *testing.T) { t.Fatalf("tracer should return nil when QLOGDIR environment variable is not defined but got %v", trace) } } + +// TestRunProtocolExistingConnWithCanceledContext tests the condition in runProtocol +// where an existing connection has a canceled context by calling the real runProtocol function. +func TestRunProtocolExistingConnWithCanceledContext(t *testing.T) { + // Set up using helper. + ctx := context.Background() + s, logCapture := createNetceptorWithLogCapture("test-node") + remoteNodeID := "existing-node" + + // Create canceled context and existing connection using helper. + canceledCtx, cancel := context.WithCancel(context.Background()) + cancel() // Cancel it immediately + createExistingConnectionWithContext(s, remoteNodeID, canceledCtx, cancel) + + // Create mock session using helper. + mockSession, bi := createMockSessionAndBackendInfo(t, s, remoteNodeID) + + // Call runProtocol. + go func() { + err := s.runProtocol(ctx, mockSession, bi) + if err != nil { + t.Logf("runProtocol finished with: %v", err) + } + }() + + // Wait for processing. + time.Sleep(200 * time.Millisecond) + + // Verify new connection was established. + s.connLock.RLock() + newConn, exists := s.connections[remoteNodeID] + s.connLock.RUnlock() + + if !exists { + t.Error("Expected new connection to be established after removing canceled connection") + } + if exists && newConn.Context.Err() != nil { + t.Error("Expected new connection to have valid context") + } + + // Verify context error was logged using helper. + if !checkLogForMessage(logCapture, "Context for existing connection error", "context canceled") { + t.Error("Expected error message about context for existing connection was not logged") + } +} + +// TestRunProtocolLogsContextErrorForExistingConnection specifically tests that +// the "Context for existing connection error" message is logged when an existing +// connection has a context error. +func TestRunProtocolLogsContextErrorForExistingConnection(t *testing.T) { + // Test different types of context errors using table-driven approach + testCases := []struct { + name string + createCtx func() (context.Context, context.CancelFunc) + expectedMsg string + }{ + { + name: "canceled context", + createCtx: func() (context.Context, context.CancelFunc) { + ctx, cancel := context.WithCancel(context.Background()) + cancel() // Cancel immediately + + return ctx, cancel + }, + expectedMsg: "context canceled", + }, + { + name: "timeout context", + createCtx: func() (context.Context, context.CancelFunc) { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond) + time.Sleep(2 * time.Millisecond) // Ensure it times out + + return ctx, cancel + }, + expectedMsg: "context deadline exceeded", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Set up using helper (fresh instance for each subtest). + ctx := context.Background() + s, logCapture := createNetceptorWithLogCapture("test-node") + remoteNodeID := "error-node-" + tc.name + + // Create context with error. + errorCtx, cancel := tc.createCtx() + defer cancel() + + // Create existing connection using helper. + createExistingConnectionWithContext(s, remoteNodeID, errorCtx, cancel) + + // Create mock session using helper. + mockSession, bi := createMockSessionAndBackendInfo(t, s, remoteNodeID) + + // Call runProtocol. + go func() { + err := s.runProtocol(ctx, mockSession, bi) + if err != nil { + t.Logf("runProtocol finished with: %v", err) + } + }() + + // Wait for processing. + time.Sleep(200 * time.Millisecond) + + // Verify new connection was established. + s.connLock.RLock() + newConn, exists := s.connections[remoteNodeID] + s.connLock.RUnlock() + + if !exists { + t.Error("Expected new connection to be established after removing connection with context error") + } + if exists && newConn.Context.Err() != nil { + t.Error("Expected new connection to have valid context") + } + + // Verify the specific error message was logged using helper. + if !checkLogForMessage(logCapture, "Context for existing connection error", tc.expectedMsg) { + t.Errorf("Expected to find log message containing 'Context for existing connection error' and '%s'", tc.expectedMsg) + } + }) + } +} + +// TestRunProtocolLogsConnectionRemoval tests that connections are properly removed +// when a connection context is canceled (functionality test rather than log message test). +func TestRunProtocolLogsConnectionRemoval(t *testing.T) { + // Set up a Netceptor instance. + ctx := context.Background() + s := New(ctx, "test-node") + + remoteNodeID := "removal-test-node" + + // Create mock session with routing update. + mockSession := &mockBackendSession{ + sendData: make(chan []byte, 10), + recvData: make(chan []byte, 10), + closed: make(chan struct{}), + } + + // Prepare initial routing update message. + routingUpdate := &routingUpdate{ + NodeID: remoteNodeID, + ForwardingNode: remoteNodeID, + UpdateEpoch: 1, + UpdateSequence: 1, + Connections: make(map[string]float64), + UpdateID: "removal-test-update", + SuspectedDuplicate: 0, + } + + msgBytes, err := s.translateStructToNetwork(MsgTypeRoute, routingUpdate) + if err != nil { + t.Fatalf("Failed to create routing update message: %v", err) + } + + // Send the routing update message to establish connection. + mockSession.recvData <- msgBytes + + bi := &BackendInfo{ + connectionCost: 1.0, + nodeCost: make(map[string]float64), + allowedPeers: []string{remoteNodeID}, // Allow the remote node. + } + + // Create a cancelable context for this test. + testCtx, cancel := context.WithCancel(ctx) + + // Start runProtocol in a goroutine. + errChan := make(chan error, 1) + go func() { + err := s.runProtocol(testCtx, mockSession, bi) + errChan <- err + }() + + // Wait a moment for the connection to be established. + time.Sleep(50 * time.Millisecond) + + // Verify connection was established. + s.connLock.RLock() + _, connectionExists := s.connections[remoteNodeID] + s.connLock.RUnlock() + if !connectionExists { + t.Fatal("Expected connection to be established") + } + + // Cancel the context to trigger the connection removal. + cancel() + + // Wait for runProtocol to complete. + select { + case err := <-errChan: + // We expect no error (nil) when context is canceled normally. + if err != nil { + t.Logf("runProtocol returned error (this may be expected): %v", err) + } + case <-time.After(1 * time.Second): + t.Fatal("runProtocol did not complete within timeout") + } + + // Wait for cleanup to complete. + time.Sleep(100 * time.Millisecond) + + // Verify the connection was properly removed. + s.connLock.RLock() + _, connectionStillExists := s.connections[remoteNodeID] + s.connLock.RUnlock() + + if connectionStillExists { + t.Error("Expected connection to be removed after context cancellation") + } +} + +// TestRunProtocolRemovesExistingConnectionWithCanceledContext verifies that when +// runProtocol detects an existing connection with a canceled context, it properly +// removes that connection from s.connections and allows the new connection to proceed. +func TestRunProtocolRemovesExistingConnectionWithCanceledContext(t *testing.T) { + // Set up a Netceptor instance. + ctx := context.Background() + s := New(ctx, "test-node") + + remoteNodeID := "leak-test-node" + + // Create a canceled context for the existing connection. + canceledCtx, cancel := context.WithCancel(context.Background()) + cancel() // Cancel it immediately + + // Create an existing connection with the canceled context. + existingConn := &connInfo{ + Context: canceledCtx, + CancelFunc: cancel, + ReadChan: make(chan []byte), + WriteChan: make(chan []byte), + Cost: 1.0, + lastReceivedLock: &sync.RWMutex{}, + logger: s.Logger, + } + + // Add the existing connection to the connections map. + s.connLock.Lock() + if s.connections == nil { + s.connections = make(map[string]*connInfo) + } + s.connections[remoteNodeID] = existingConn + initialConnectionCount := len(s.connections) + s.connLock.Unlock() + + // Verify the existing connection is there and has a canceled context. + s.connLock.RLock() + storedConn, exists := s.connections[remoteNodeID] + s.connLock.RUnlock() + + if !exists { + t.Fatal("Existing connection should be in the connections map") + } + if storedConn.Context.Err() == nil { + t.Fatal("Existing connection context should be canceled") + } + + // Create a mock session that will try to connect with the same remoteNodeID. + mockSession := &mockBackendSession{ + sendData: make(chan []byte, 10), + recvData: make(chan []byte, 10), + closed: make(chan struct{}), + } + + // Prepare the routing update message. + routingUpdate := &routingUpdate{ + NodeID: remoteNodeID, + ForwardingNode: remoteNodeID, + UpdateEpoch: 1, + UpdateSequence: 1, + Connections: make(map[string]float64), + UpdateID: "leak-test-update", + SuspectedDuplicate: 0, + } + + msgBytes, err := s.translateStructToNetwork(MsgTypeRoute, routingUpdate) + if err != nil { + t.Fatalf("Failed to create routing update message: %v", err) + } + + // Send the routing update message to the mock session. + mockSession.recvData <- msgBytes + + bi := &BackendInfo{ + connectionCost: 1.0, + nodeCost: make(map[string]float64), + allowedPeers: []string{remoteNodeID}, + } + + // Call runProtocol - this should detect the existing connection with canceled context and replace it. + go func() { + err = s.runProtocol(ctx, mockSession, bi) + if err != nil { + t.Logf("runProtocol finished with: %v", err) + } + }() + + // Wait for the connection to be processed. + time.Sleep(100 * time.Millisecond) + + // THE KEY TEST: Verify that the canceled connection was removed and replaced. + s.connLock.RLock() + finalConnectionCount := len(s.connections) + stillExists := false + var replacementConn *connInfo + if conn, ok := s.connections[remoteNodeID]; ok { + stillExists = true + replacementConn = conn + } + s.connLock.RUnlock() + + // Verify the connection was replaced, not leaked. + if !stillExists { + t.Error("Expected a new connection to be established after removing canceled connection") + } + if finalConnectionCount != initialConnectionCount { + t.Errorf("Expected connection count to remain the same (%d), but got %d", initialConnectionCount, finalConnectionCount) + } + if stillExists && replacementConn.Context.Err() != nil { + t.Error("The replacement connection should have a valid (non-canceled) context") + } + if stillExists && replacementConn == existingConn { + t.Error("The replacement connection should be a different connection object than the original") + } + + // Additional verification: try to connect again with the same node ID. + // This should now fail normally because there's a valid existing connection. + mockSession2 := &mockBackendSession{ + sendData: make(chan []byte, 10), + recvData: make(chan []byte, 10), + closed: make(chan struct{}), + } + mockSession2.recvData <- msgBytes + + err2 := s.runProtocol(ctx, mockSession2, bi) + if err2 == nil { + t.Error("Expected second connection attempt to fail due to existing valid connection") + } + if err2 != nil && !strings.Contains(err2.Error(), "it connected using a node ID we are already connected to") { + t.Errorf("Expected second connection to be rejected due to existing connection, got: %v", err2) + } + + // Final verification: should still have exactly one connection. + s.connLock.RLock() + finalFinalCount := len(s.connections) + finalConn, finalExists := s.connections[remoteNodeID] + s.connLock.RUnlock() + + if finalFinalCount != 1 { + t.Errorf("Expected exactly 1 connection after cleanup, but got %d", finalFinalCount) + } + if finalExists && finalConn.Context.Err() != nil { + t.Error("Final connection should have valid context") + } + + t.Logf("SUCCESS: Canceled connection was properly removed and replaced with valid connection") +} + +// logCapture is a simple writer that captures log messages for testing. +type logCapture struct { + messages []string + mutex sync.Mutex +} + +func (lc *logCapture) Write(p []byte) (n int, err error) { + lc.mutex.Lock() + defer lc.mutex.Unlock() + lc.messages = append(lc.messages, string(p)) + + return len(p), nil +} + +// Helper functions for test setup to reduce code duplication. + +// createNetceptorWithLogCapture creates a Netceptor instance with log capture setup. +func createNetceptorWithLogCapture(nodeID string) (*Netceptor, *logCapture) { + ctx := context.Background() + s := New(ctx, nodeID) + logCapture := &logCapture{messages: make([]string, 0)} + s.Logger.SetOutput(logCapture) + + return s, logCapture +} + +// createMockSessionAndBackendInfo creates a mock session and backend info for testing. +func createMockSessionAndBackendInfo(t *testing.T, s *Netceptor, remoteNodeID string) (*mockBackendSession, *BackendInfo) { + mockSession := &mockBackendSession{ + sendData: make(chan []byte, 10), + recvData: make(chan []byte, 10), + closed: make(chan struct{}), + } + + // Create routing update message. + routingUpdate := &routingUpdate{ + NodeID: remoteNodeID, + ForwardingNode: remoteNodeID, + UpdateEpoch: 1, + UpdateSequence: 1, + Connections: make(map[string]float64), + UpdateID: "test-update-" + remoteNodeID, + SuspectedDuplicate: 0, + } + + msgBytes, err := s.translateStructToNetwork(MsgTypeRoute, routingUpdate) + if err != nil { + t.Fatalf("Failed to create routing update message: %v", err) + } + + mockSession.recvData <- msgBytes + + bi := &BackendInfo{ + connectionCost: 1.0, + nodeCost: make(map[string]float64), + allowedPeers: []string{remoteNodeID}, + } + + return mockSession, bi +} + +// createExistingConnectionWithContext creates an existing connection with the given context. +func createExistingConnectionWithContext(s *Netceptor, remoteNodeID string, ctx context.Context, cancel context.CancelFunc) { + existingConn := &connInfo{ + Context: ctx, + CancelFunc: cancel, + ReadChan: make(chan []byte), + WriteChan: make(chan []byte), + Cost: 1.0, + lastReceivedLock: &sync.RWMutex{}, + logger: s.Logger, + } + + s.connLock.Lock() + if s.connections == nil { + s.connections = make(map[string]*connInfo) + } + s.connections[remoteNodeID] = existingConn + s.connLock.Unlock() +} + +// checkLogForMessage checks if the log contains a message with the given substrings. +func checkLogForMessage(logCapture *logCapture, substrings ...string) bool { + logCapture.mutex.Lock() + defer logCapture.mutex.Unlock() + + for _, msg := range logCapture.messages { + allFound := true + for _, substr := range substrings { + if !strings.Contains(msg, substr) { + allFound = false + + break + } + } + if allFound { + return true + } + } + + return false +} + +// mockBackendSession implements BackendSession for testing. +type mockBackendSession struct { + sendData chan []byte + recvData chan []byte + closed chan struct{} + mutex sync.Mutex + isClosed bool +} + +func (m *mockBackendSession) Send(data []byte) error { + m.mutex.Lock() + defer m.mutex.Unlock() + if m.isClosed { + return fmt.Errorf("session closed") + } + select { + case m.sendData <- data: + + return nil + default: + + return fmt.Errorf("send buffer full") + } +} + +func (m *mockBackendSession) Recv(timeout time.Duration) ([]byte, error) { + m.mutex.Lock() + if m.isClosed { + m.mutex.Unlock() + + return nil, fmt.Errorf("session closed") + } + m.mutex.Unlock() + + select { + case data := <-m.recvData: + + return data, nil + case <-time.After(timeout): + + return nil, ErrTimeout + case <-m.closed: + + return nil, fmt.Errorf("session closed") + } +} + +func (m *mockBackendSession) Close() error { + m.mutex.Lock() + defer m.mutex.Unlock() + if !m.isClosed { + m.isClosed = true + close(m.closed) + } + + return nil +}