Files
cocos/agent/algorithm/binary/binary_test.go
T
Sammy Kerata Oina b44780df95
CI / lint (push) Has been cancelled
CI / test (agent) (push) Has been cancelled
CI / test (cli) (push) Has been cancelled
CI / test (cmd) (push) Has been cancelled
CI / test (internal) (push) Has been cancelled
CI / test (manager, true) (push) Has been cancelled
CI / test (pkg) (push) Has been cancelled
CI / upload-coverage (push) Has been cancelled
NOISSUE - Enhance OCI image extraction to return algorithm and requirements paths, and add deferred cleanup for temporary files (#586)
* feat: Enhance OCI image extraction to return algorithm and requirements paths, and add deferred cleanup for temporary files.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: implement deterministic zipping and enhance checksum verification for resources

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: Update component build sources, add gRPC health checks to the CVM server, and refine algorithm argument handling and documentation.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* docs: Update remote resources testing guide with `sudo` for KBS, algorithm result saving, `requirements.txt`, and `algo-args` for RVPS.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* refactor: Explicitly ignore `stderr.Write` return values and add minor whitespace in tests.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* test: add comprehensive error path and edge case tests for file, zip, OCI, and agent components.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: Add mutexes for thread-safe algorithm execution and expand recognized data file extensions to include common archive formats.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* feat: Add OCI extraction tests for Python algorithms and multi-layer datasets, refactor algorithm execution for testability, and enhance algorithm stop and error handling tests.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* test: Add error assertions to OCI extraction test helpers and remove an unused mock exec command.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* test: Improve error handling test coverage for algorithm execution and OCI resource extraction.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* fix: Improve algorithm process termination, enhance computation error handling, and add concurrency safety to agent service.

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2026-03-27 14:23:52 +01:00

171 lines
3.9 KiB
Go

// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package binary
import (
"bytes"
"io"
"log/slog"
"os"
"os/exec"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/ultravioletrs/cocos/agent/algorithm/logging"
"github.com/ultravioletrs/cocos/agent/events/mocks"
)
func TestNewAlgorithm(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
eventsSvc := new(mocks.Service)
algoFile := "/path/to/algo"
args := []string{"arg1", "arg2"}
algo := NewAlgorithm(logger, eventsSvc, algoFile, args, "")
b, ok := algo.(*binary)
if !ok {
t.Fatalf("NewAlgorithm did not return a *binary")
}
if b.algoFile != algoFile {
t.Errorf("Expected algoFile to be %s, got %s", algoFile, b.algoFile)
}
if len(b.args) != len(args) {
t.Errorf("Expected %d args, got %d", len(args), len(b.args))
}
for i, arg := range args {
if b.args[i] != arg {
t.Errorf("Expected arg %d to be %s, got %s", i, arg, b.args[i])
}
}
if _, ok := b.stderr.(*logging.Stderr); !ok {
t.Errorf("Expected stderr to be *algorithm.Stderr")
}
if _, ok := b.stdout.(*logging.Stdout); !ok {
t.Errorf("Expected stdout to be *algorithm.Stdout")
}
}
func TestBinaryRun(t *testing.T) {
tests := []struct {
name string
algoFile string
args []string
expectedError bool
}{
{
name: "Successful execution",
algoFile: "echo",
args: []string{"Hello, World!"},
expectedError: false,
},
{
name: "Non-existent binary",
algoFile: "non_existent_binary",
args: []string{},
expectedError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
eventsSvc := new(mocks.Service)
eventsSvc.On("SendEvent", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return().Maybe()
b := NewAlgorithm(logger, eventsSvc, tt.algoFile, tt.args, "").(*binary)
var stdout, stderr bytes.Buffer
b.stdout = &stdout
b.stderr = &stderr
err := b.Run()
if tt.expectedError && err == nil {
t.Errorf("Expected an error, but got none")
}
if !tt.expectedError && err != nil {
t.Errorf("Unexpected error: %v", err)
}
if !tt.expectedError {
if stdout.Len() == 0 {
t.Errorf("Expected non-empty stdout")
}
}
})
}
}
func TestStop(t *testing.T) {
t.Run("stop nil cmd", func(t *testing.T) {
b := &binary{}
err := b.Stop()
assert.NoError(t, err)
})
t.Run("stop with running process", func(t *testing.T) {
b := &binary{
algoFile: "sleep",
args: []string{"10"},
}
if err := b.Run(); err != nil {
t.Fatalf("Failed to start command: %v", err)
}
err := b.Stop()
assert.NoError(t, err)
// Verify it actually stopped
_ = b.cmd.Wait()
})
t.Run("stop already exited", func(t *testing.T) {
b := &binary{
algoFile: "echo",
args: []string{"test"},
stdout: io.Discard,
stderr: io.Discard,
}
if err := b.Run(); err != nil {
t.Fatal(err)
}
err := b.Stop()
assert.NoError(t, err)
})
}
func TestRunError(t *testing.T) {
// Mock execCommand to return an error on Start
oldExecCommand := execCommand
execCommand = mockExecCommandError
defer func() { execCommand = oldExecCommand }()
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
eventsSvc := new(mocks.Service)
eventsSvc.On("SendEvent", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return().Maybe()
b := NewAlgorithm(logger, eventsSvc, "test", nil, "").(*binary)
err := b.Run()
assert.Error(t, err)
}
func mockExecCommandError(command string, args ...string) *exec.Cmd {
// This will make Start() fail if we use a non-existent binary
return exec.Command("non_existent_binary_for_sure_12345")
}
func TestHelperProcess(t *testing.T) {
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
return
}
os.Exit(0)
}