mirror of
https://github.com/ultravioletrs/cocos.git
synced 2026-06-23 04:10:25 +00:00
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
* 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>
171 lines
3.9 KiB
Go
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)
|
|
}
|