diff --git a/CHANGELOG.md b/CHANGELOG.md index 5110b08..0355a48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 `MachineType` equivalents). The conventional import alias is now `engineTypes`. Closes [#90](https://github.com/robbyt/go-polyscript/issues/90). ([#137](https://github.com/robbyt/go-polyscript/pull/137)) +- **Breaking:** Unexported the six fields on `platform/script.ExecutableUnit` + (`ID`, `CreatedAt`, `ScriptLoader`, `Compiler`, `Content`, `DataProvider`). + External callers must use the existing getters (`GetID()`, `GetCreatedAt()`, + `GetLoader()`, `GetCompiler()`, `GetContent()`, `GetDataProvider()`) for + reads and construct only via `NewExecutableUnit(...)`. Mid-flight mutation + of `DataProvider` (and the other fields) is no longer possible from outside + the package. Closes [#91](https://github.com/robbyt/go-polyscript/issues/91). ### Deprecated - The twelve legacy top-level constructors (`FromRisorFile`, diff --git a/engines/extism/evaluator/evaluator_test.go b/engines/extism/evaluator/evaluator_test.go index 22fdff5..cc57600 100644 --- a/engines/extism/evaluator/evaluator_test.go +++ b/engines/extism/evaluator/evaluator_test.go @@ -175,11 +175,7 @@ func TestEvaluator_Evaluate(t *testing.T) { content := createMockExecutable(mockPlugin, "main") // Create a mock executable - exe := &script.ExecutableUnit{ - ID: "test-json-success", - DataProvider: ctxProvider, - Content: content, - } + exe := newExe(t, "test-json-success", content, ctxProvider) evaluator := New(handler, exe) @@ -214,11 +210,7 @@ func TestEvaluator_Evaluate(t *testing.T) { mockPlugin.On("Close", mock.Anything).Return(nil) content := createMockExecutable(mockPlugin, "main") - exe := &script.ExecutableUnit{ - ID: "test-string-success", - DataProvider: ctxProvider, - Content: content, - } + exe := newExe(t, "test-string-success", content, ctxProvider) evaluator := New(handler, exe) ctx := t.Context() @@ -267,9 +259,7 @@ func TestEvaluator_Evaluate(t *testing.T) { t.Run(tt.name, func(t *testing.T) { handler := slog.NewTextHandler(os.Stdout, nil) ctxProvider := data.NewContextProvider(constants.EvalData) - dummyExe := &script.ExecutableUnit{ - DataProvider: ctxProvider, - } + dummyExe := newExe(t, "load-input-data", nil, ctxProvider) evaluator := New(handler, dummyExe) ctx := t.Context() @@ -340,11 +330,7 @@ func TestEvaluator_Evaluate(t *testing.T) { handler := slog.NewTextHandler(os.Stdout, nil) ctxProvider := data.NewContextProvider(constants.EvalData) - exe := &script.ExecutableUnit{ - ID: "test-case", - Content: mockContent, - DataProvider: ctxProvider, - } + exe := newExe(t, "test-case", mockContent, ctxProvider) evaluator := New(handler, exe) @@ -366,11 +352,7 @@ func TestEvaluator_Evaluate(t *testing.T) { handler := slog.NewTextHandler(os.Stdout, nil) ctxProvider := data.NewContextProvider(constants.EvalData) - exe := &script.ExecutableUnit{ - ID: "test-case", - Content: mockContent, - DataProvider: ctxProvider, - } + exe := newExe(t, "test-case", mockContent, ctxProvider) evaluator := New(handler, exe) @@ -403,11 +385,7 @@ func TestEvaluator_Evaluate(t *testing.T) { // Create executor unit handler := slog.NewTextHandler(os.Stdout, nil) - execUnit := &script.ExecutableUnit{ - ID: "test-cancel", - Content: content, - DataProvider: data.NewContextProvider(constants.EvalData), - } + execUnit := newExe(t, "test-cancel", content, data.NewContextProvider(constants.EvalData)) evaluator := New(handler, execUnit) @@ -442,11 +420,7 @@ func TestEvaluator_Evaluate(t *testing.T) { mockPlugin.On("Close", mock.Anything).Return(nil) content := createMockExecutable(mockPlugin, "main") - exe := &script.ExecutableUnit{ - ID: "test-error-exit", - DataProvider: ctxProvider, - Content: content, - } + exe := newExe(t, "test-error-exit", content, ctxProvider) evaluator := New(handler, exe) ctx := t.Context() @@ -480,11 +454,7 @@ func TestEvaluator_Evaluate(t *testing.T) { mockPlugin.On("Close", mock.Anything).Return(nil) content := createMockExecutable(mockPlugin, "main") - exe := &script.ExecutableUnit{ - ID: "test-error-exit-uncapped", - DataProvider: ctxProvider, - Content: content, - } + exe := newExe(t, "test-error-exit-uncapped", content, ctxProvider) evaluator := New(handler, exe, WithExitOutputMaxBytes(-1)) _, err := evaluator.Eval(t.Context()) @@ -508,11 +478,7 @@ func TestEvaluator_Evaluate(t *testing.T) { mockPlugin.On("Close", mock.Anything).Return(nil) content := createMockExecutable(mockPlugin, "main") - exe := &script.ExecutableUnit{ - ID: "test-instance-error", - DataProvider: data.NewContextProvider(constants.EvalData), - Content: content, - } + exe := newExe(t, "test-instance-error", content, data.NewContextProvider(constants.EvalData)) evaluator := New(handler, exe) ctx := t.Context() @@ -529,11 +495,7 @@ func TestEvaluator_Evaluate(t *testing.T) { mockPlugin := new(MockCompiledPlugin) content := createMockExecutable(mockPlugin, "main") - exe := &script.ExecutableUnit{ - ID: "test-load-input-error", - DataProvider: &mockErrProvider{err: errors.New("provider boom")}, - Content: content, - } + exe := newExe(t, "test-load-input-error", content, &mockErrProvider{err: errors.New("provider boom")}) evaluator := New(handler, exe) _, err := evaluator.Eval(t.Context()) @@ -551,13 +513,9 @@ func TestEvaluator_Evaluate(t *testing.T) { mockPlugin := new(MockCompiledPlugin) content := createMockExecutable(mockPlugin, "main") - exe := &script.ExecutableUnit{ - ID: "test-convert-format-error", - DataProvider: &mockMapProvider{data: map[string]any{ - "bad": make(chan int), - }}, - Content: content, - } + exe := newExe(t, "test-convert-format-error", content, &mockMapProvider{data: map[string]any{ + "bad": make(chan int), + }}) evaluator := New(handler, exe) _, err := evaluator.Eval(t.Context()) @@ -578,11 +536,7 @@ func TestEvaluator_Evaluate(t *testing.T) { // Create a real compiler.Executable with our mock plugin content := createMockExecutable(mockPlugin, "main") - exe := &script.ExecutableUnit{ - ID: "test-nil-handler", - DataProvider: data.NewContextProvider(constants.EvalData), - Content: content, - } + exe := newExe(t, "test-nil-handler", content, data.NewContextProvider(constants.EvalData)) // Create with nil handler evaluator := New(nil, exe) @@ -809,11 +763,7 @@ func TestEvaluator_AddDataToContext(t *testing.T) { mockPlugin.On("Close", mock.Anything).Return(nil) content := createMockExecutable(mockPlugin, "main") - return &script.ExecutableUnit{ - ID: "test-nil-provider", - DataProvider: nil, - Content: content, - } + return newExe(t, "test-nil-provider", content, nil) }, inputs: []map[string]any{{"test": "data"}}, wantError: true, @@ -828,11 +778,7 @@ func TestEvaluator_AddDataToContext(t *testing.T) { mockPlugin.On("Close", mock.Anything).Return(nil) content := createMockExecutable(mockPlugin, "main") - return &script.ExecutableUnit{ - ID: "test-valid-data", - DataProvider: data.NewContextProvider(constants.EvalData), - Content: content, - } + return newExe(t, "test-valid-data", content, data.NewContextProvider(constants.EvalData)) }, inputs: []map[string]any{{"test": "data"}}, wantError: false, @@ -846,11 +792,7 @@ func TestEvaluator_AddDataToContext(t *testing.T) { mockPlugin.On("Close", mock.Anything).Return(nil) content := createMockExecutable(mockPlugin, "main") - return &script.ExecutableUnit{ - ID: "test-empty-input", - DataProvider: data.NewContextProvider(constants.EvalData), - Content: content, - } + return newExe(t, "test-empty-input", content, data.NewContextProvider(constants.EvalData)) }, inputs: []map[string]any{{}}, wantError: false, @@ -877,11 +819,7 @@ func TestEvaluator_AddDataToContext(t *testing.T) { mockProvider := &mockErrProvider{ err: errors.New("provider error"), } - return &script.ExecutableUnit{ - ID: "test-err-provider", - DataProvider: mockProvider, - Content: content, - } + return newExe(t, "test-err-provider", content, mockProvider) }, inputs: []map[string]any{{"test": "data"}}, wantError: true, diff --git a/engines/extism/evaluator/exec_helpers_test.go b/engines/extism/evaluator/exec_helpers_test.go new file mode 100644 index 0000000..9fe4ca5 --- /dev/null +++ b/engines/extism/evaluator/exec_helpers_test.go @@ -0,0 +1,71 @@ +package evaluator + +import ( + "io" + "net/url" + "strings" + "testing" + + "github.com/robbyt/go-polyscript/platform/data" + "github.com/robbyt/go-polyscript/platform/script" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +// loaderMock satisfies script/loader.Loader; only GetReader is exercised +// by NewExecutableUnit. +type loaderMock struct{ mock.Mock } + +func (m *loaderMock) GetReader() (io.ReadCloser, error) { + args := m.Called() + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(io.ReadCloser), args.Error(1) +} + +func (m *loaderMock) GetSourceURL() *url.URL { + args := m.Called() + if args.Get(0) == nil { + return nil + } + return args.Get(0).(*url.URL) +} + +// compilerMock satisfies script.Compiler and returns a preconfigured +// ExecutableContent (which may be nil). +type compilerMock struct{ mock.Mock } + +func (m *compilerMock) Compile(r io.ReadCloser) (script.ExecutableContent, error) { + args := m.Called(r) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(script.ExecutableContent), args.Error(1) +} + +func (m *compilerMock) String() string { return "compilerMock" } + +// newExe builds an ExecutableUnit by routing through script.NewExecutableUnit +// with a mock loader+compiler that produce the supplied content. Use a +// non-empty id when content is nil, since NewExecutableUnit otherwise tries +// to derive the id from content.GetSource() and panics on nil. +func newExe( + t *testing.T, + id string, + content script.ExecutableContent, + provider data.Provider, +) *script.ExecutableUnit { + t.Helper() + if id == "" { + id = t.Name() + } + ldr := new(loaderMock) + ldr.On("GetReader"). + Return(io.NopCloser(strings.NewReader("dummy")), nil) + cmp := new(compilerMock) + cmp.On("Compile", mock.Anything).Return(content, nil) + exe, err := script.NewExecutableUnit(nil, id, ldr, cmp, provider) + require.NoError(t, err) + return exe +} diff --git a/engines/risor/evaluator/evaluator_test.go b/engines/risor/evaluator/evaluator_test.go index 61e2cfa..09cb744 100644 --- a/engines/risor/evaluator/evaluator_test.go +++ b/engines/risor/evaluator/evaluator_test.go @@ -12,7 +12,6 @@ import ( "testing" "time" - "github.com/deepnoodle-ai/risor/v2/pkg/bytecode" "github.com/robbyt/go-polyscript/engines/risor/compiler" "github.com/robbyt/go-polyscript/engines/types" "github.com/robbyt/go-polyscript/internal/helpers" @@ -94,22 +93,7 @@ func createTestExecutable( if err != nil { return nil, fmt.Errorf("failed to create compiler: %w", err) } - - reader, err := ld.GetReader() - if err != nil { - return nil, err - } - - content, err := c.Compile(reader) - if err != nil { - return nil, err - } - - return &script.ExecutableUnit{ - ID: "test-id", - Content: content, - DataProvider: provider, - }, nil + return script.NewExecutableUnit(handler, "test-id", ld, c, provider) } // TestEvaluator_Evaluate tests evaluating Risor scripts @@ -243,36 +227,14 @@ func TestEvaluator_Evaluate(t *testing.T) { { name: "nil bytecode", setupExe: func() *script.ExecutableUnit { - return &script.ExecutableUnit{ - ID: "test-id", - Content: &MockContent{ - Content: nil, - }, - } + return newExe(t, "test-id", &MockContent{Content: nil}, nil) }, errorMessage: "bytecode is nil", }, - { - name: "empty execution id", - setupExe: func() *script.ExecutableUnit { - return &script.ExecutableUnit{ - ID: "", - Content: &MockContent{ - Content: bytecode.NewCode(bytecode.CodeParams{}), - }, - } - }, - errorMessage: "exeID is empty", - }, { name: "wrong bytecode type", setupExe: func() *script.ExecutableUnit { - return &script.ExecutableUnit{ - ID: "test-id", - Content: &MockContent{ - Content: "not a risor bytecode", - }, - } + return newExe(t, "test-id", &MockContent{Content: "not a risor bytecode"}, nil) }, errorMessage: "unable to type assert bytecode", }, @@ -327,9 +289,7 @@ func TestEvaluator_Evaluate(t *testing.T) { expectedErr := fmt.Errorf("provider error") mockProvider.On("GetData", mock.Anything).Return(nil, expectedErr) - return &script.ExecutableUnit{ - DataProvider: mockProvider, - } + return newExe(t, "provider-error", nil, mockProvider) }, setupCtx: func() context.Context { return t.Context() @@ -345,9 +305,7 @@ func TestEvaluator_Evaluate(t *testing.T) { emptyData := map[string]any{} mockProvider.On("GetData", mock.Anything).Return(emptyData, nil) - return &script.ExecutableUnit{ - DataProvider: mockProvider, - } + return newExe(t, "empty-data", nil, mockProvider) }, setupCtx: func() context.Context { return t.Context() @@ -362,9 +320,7 @@ func TestEvaluator_Evaluate(t *testing.T) { validData := map[string]any{"test": "data"} mockProvider.On("GetData", mock.Anything).Return(validData, nil) - return &script.ExecutableUnit{ - DataProvider: mockProvider, - } + return newExe(t, "valid-data", nil, mockProvider) }, setupCtx: func() context.Context { return t.Context() @@ -405,8 +361,8 @@ func TestEvaluator_Evaluate(t *testing.T) { } // Verify mock expectations if we have a mockProvider - if exe != nil && exe.DataProvider != nil { - if mockProvider, ok := exe.DataProvider.(*MockProvider); ok { + if exe != nil && exe.GetDataProvider() != nil { + if mockProvider, ok := exe.GetDataProvider().(*MockProvider); ok { mockProvider.AssertExpectations(t) } } @@ -442,7 +398,7 @@ func TestEvaluator_Evaluate(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - exe := &script.ExecutableUnit{} + exe := newExe(t, "new-test", nil, nil) evaluator := New(tt.handler, exe) require.NotNil(t, evaluator) @@ -484,7 +440,7 @@ func TestEvaluator_AddDataToContext(t *testing.T) { mockProvider.On("AddDataToContext", mock.Anything, mock.Anything). Return(enrichedCtx, nil) - return &script.ExecutableUnit{DataProvider: mockProvider} + return newExe(t, "with-provider", nil, mockProvider) }, inputs: []map[string]any{{"test": "data"}}, wantError: false, @@ -499,7 +455,7 @@ func TestEvaluator_AddDataToContext(t *testing.T) { mockProvider.On("AddDataToContext", mock.Anything, mock.Anything). Return(nil, expectedErr) - return &script.ExecutableUnit{DataProvider: mockProvider} + return newExe(t, "with-provider-error", nil, mockProvider) }, inputs: []map[string]any{{"test": "data"}}, wantError: true, @@ -509,7 +465,7 @@ func TestEvaluator_AddDataToContext(t *testing.T) { name: "nil provider", setupExe: func(t *testing.T) *script.ExecutableUnit { t.Helper() - return &script.ExecutableUnit{DataProvider: nil} + return newExe(t, "nil-provider", nil, nil) }, inputs: []map[string]any{{"test": "data"}}, wantError: true, @@ -554,8 +510,8 @@ func TestEvaluator_AddDataToContext(t *testing.T) { } // If using mocks, verify expectations - if exe != nil && exe.DataProvider != nil { - if mockProvider, ok := exe.DataProvider.(*MockProvider); ok { + if exe != nil && exe.GetDataProvider() != nil { + if mockProvider, ok := exe.GetDataProvider().(*MockProvider); ok { mockProvider.AssertExpectations(t) } } diff --git a/engines/risor/evaluator/exec_helpers_test.go b/engines/risor/evaluator/exec_helpers_test.go new file mode 100644 index 0000000..087af8d --- /dev/null +++ b/engines/risor/evaluator/exec_helpers_test.go @@ -0,0 +1,72 @@ +package evaluator + +import ( + "io" + "net/url" + "strings" + "testing" + + "github.com/robbyt/go-polyscript/platform/data" + "github.com/robbyt/go-polyscript/platform/script" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +// unitLoaderMock satisfies script/loader.Loader; only GetReader is exercised +// by NewExecutableUnit. Named to avoid clashing with the file-local MockLoader +// in evaluator_test.go (which has a non-matching GetSourceURL signature). +type unitLoaderMock struct{ mock.Mock } + +func (m *unitLoaderMock) GetReader() (io.ReadCloser, error) { + args := m.Called() + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(io.ReadCloser), args.Error(1) +} + +func (m *unitLoaderMock) GetSourceURL() *url.URL { + args := m.Called() + if args.Get(0) == nil { + return nil + } + return args.Get(0).(*url.URL) +} + +// compilerMock satisfies script.Compiler and returns a preconfigured +// ExecutableContent (which may be nil). +type compilerMock struct{ mock.Mock } + +func (m *compilerMock) Compile(r io.ReadCloser) (script.ExecutableContent, error) { + args := m.Called(r) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(script.ExecutableContent), args.Error(1) +} + +func (m *compilerMock) String() string { return "compilerMock" } + +// newExe builds an ExecutableUnit by routing through script.NewExecutableUnit +// with a mock loader+compiler that produce the supplied content. Use a +// non-empty id when content is nil, since NewExecutableUnit otherwise tries +// to derive the id from content.GetSource() and panics on nil. +func newExe( + t *testing.T, + id string, + content script.ExecutableContent, + provider data.Provider, +) *script.ExecutableUnit { + t.Helper() + if id == "" { + id = t.Name() + } + ldr := new(unitLoaderMock) + ldr.On("GetReader"). + Return(io.NopCloser(strings.NewReader("dummy")), nil) + cmp := new(compilerMock) + cmp.On("Compile", mock.Anything).Return(content, nil) + exe, err := script.NewExecutableUnit(nil, id, ldr, cmp, provider) + require.NoError(t, err) + return exe +} diff --git a/engines/starlark/evaluator/evaluator_test.go b/engines/starlark/evaluator/evaluator_test.go index b12c208..5ef5419 100644 --- a/engines/starlark/evaluator/evaluator_test.go +++ b/engines/starlark/evaluator/evaluator_test.go @@ -179,10 +179,7 @@ _ = request_handler(ctx.get("request")) // Test content nil t.Run("content nil", func(t *testing.T) { handler := slog.NewTextHandler(os.Stdout, nil) - exe := &script.ExecutableUnit{ - ID: "test-nil-content", - Content: nil, // Deliberately nil content - } + exe := newExe(t, "test-nil-content", nil, nil) evaluator := New(handler, exe) response, err := evaluator.Eval(t.Context()) @@ -268,7 +265,7 @@ func TestEvaluator_AddDataToContext(t *testing.T) { mockProvider.On("AddDataToContext", mock.Anything, mock.Anything). Return(enrichedCtx, nil) - return &script.ExecutableUnit{DataProvider: mockProvider} + return newExe(t, "with provider", nil, mockProvider) }, inputs: []map[string]any{{"test": "data"}}, wantError: false, @@ -283,7 +280,7 @@ func TestEvaluator_AddDataToContext(t *testing.T) { mockProvider.On("AddDataToContext", mock.Anything, mock.Anything). Return(nil, expectedErr) - return &script.ExecutableUnit{DataProvider: mockProvider} + return newExe(t, "with provider error", nil, mockProvider) }, inputs: []map[string]any{{"test": "data"}}, wantError: true, @@ -293,7 +290,7 @@ func TestEvaluator_AddDataToContext(t *testing.T) { name: "nil provider", setupExe: func(t *testing.T) *script.ExecutableUnit { t.Helper() - return &script.ExecutableUnit{DataProvider: nil} + return newExe(t, "nil provider", nil, nil) }, inputs: []map[string]any{{"test": "data"}}, wantError: true, @@ -333,8 +330,8 @@ func TestEvaluator_AddDataToContext(t *testing.T) { } // If using mocks, verify expectations - if exe != nil && exe.DataProvider != nil { - if mockProvider, ok := exe.DataProvider.(*MockProvider); ok { + if exe != nil && exe.GetDataProvider() != nil { + if mockProvider, ok := exe.GetDataProvider().(*MockProvider); ok { mockProvider.AssertExpectations(t) } } diff --git a/engines/starlark/evaluator/exec_helpers_test.go b/engines/starlark/evaluator/exec_helpers_test.go new file mode 100644 index 0000000..b61f285 --- /dev/null +++ b/engines/starlark/evaluator/exec_helpers_test.go @@ -0,0 +1,71 @@ +package evaluator + +import ( + "io" + "net/url" + "strings" + "testing" + + "github.com/robbyt/go-polyscript/platform/data" + "github.com/robbyt/go-polyscript/platform/script" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +// loaderMock satisfies script/loader.Loader for tests that don't care about +// the loader's behavior, only that NewExecutableUnit can call GetReader(). +type loaderMock struct{ mock.Mock } + +func (m *loaderMock) GetReader() (io.ReadCloser, error) { + args := m.Called() + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(io.ReadCloser), args.Error(1) +} + +func (m *loaderMock) GetSourceURL() *url.URL { + args := m.Called() + if args.Get(0) == nil { + return nil + } + return args.Get(0).(*url.URL) +} + +// compilerMock satisfies script.Compiler and returns a preconfigured +// ExecutableContent (which may be nil). +type compilerMock struct{ mock.Mock } + +func (m *compilerMock) Compile(r io.ReadCloser) (script.ExecutableContent, error) { + args := m.Called(r) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(script.ExecutableContent), args.Error(1) +} + +func (m *compilerMock) String() string { return "compilerMock" } + +// newExe builds an ExecutableUnit by routing through script.NewExecutableUnit +// with a mock loader+compiler that produce the supplied content. Use a +// non-empty id when content is nil, since NewExecutableUnit otherwise tries +// to derive the id from content.GetSource() and panics on nil. +func newExe( + t *testing.T, + id string, + content script.ExecutableContent, + provider data.Provider, +) *script.ExecutableUnit { + t.Helper() + if id == "" { + id = t.Name() + } + ldr := new(loaderMock) + ldr.On("GetReader"). + Return(io.NopCloser(strings.NewReader("dummy")), nil) + cmp := new(compilerMock) + cmp.On("Compile", mock.Anything).Return(content, nil) + exe, err := script.NewExecutableUnit(nil, id, ldr, cmp, provider) + require.NoError(t, err) + return exe +} diff --git a/platform/script/executableUnit.go b/platform/script/executableUnit.go index b89de55..7e6479a 100644 --- a/platform/script/executableUnit.go +++ b/platform/script/executableUnit.go @@ -16,29 +16,15 @@ const checksumLength = 12 // ExecutableUnit represents a specific version of a script, including its content and creation time. // It holds the compiled script content and provides access to evaluation facilities. +// Fields are unexported; construct via NewExecutableUnit and read via the getter methods. type ExecutableUnit struct { - // ID is a unique identifier for this executable unit, typically derived from a hash of the script content. - ID string + id string + createdAt time.Time + scriptLoader loader.Loader + compiler Compiler + content ExecutableContent + dataProvider data.Provider - // CreatedAt records when this executable unit was instantiated. - CreatedAt time.Time - - // ScriptLoader loads the script content to local memory from various places (file, string, etc.). - ScriptLoader loader.Loader - - // Compiler is the script language-specific compiler that was used to compile this unit. - Compiler Compiler - - // Content holds the compiled bytecode and source representation of the script. - Content ExecutableContent - - // DataProvider provides access to both static compile-time data and variable runtime data - // during script evaluation. Enabling the "compile once, run many times" design. - // When created with NewExecutableUnit, this is typically a CompositeProvider containing - // a StaticProvider (for compile-time data) and another provider (for runtime data). - DataProvider data.Provider - - // Logging components logHandler slog.Handler logger *slog.Logger } @@ -77,12 +63,12 @@ func NewExecutableUnit( } return &ExecutableUnit{ - ID: versionID, - CreatedAt: time.Now(), - ScriptLoader: scriptLoader, - Content: exe, - Compiler: compiler, - DataProvider: dataProvider, + id: versionID, + createdAt: time.Now(), + scriptLoader: scriptLoader, + content: exe, + compiler: compiler, + dataProvider: dataProvider, logHandler: handler, logger: logger.With("ID", versionID), }, nil @@ -90,40 +76,40 @@ func NewExecutableUnit( func (exe *ExecutableUnit) String() string { return fmt.Sprintf("ExecutableUnit{ID: %s, CreatedAt: %s, Compiler: %s, Loader: %s}", - exe.ID, exe.CreatedAt, exe.Compiler, exe.ScriptLoader) + exe.id, exe.createdAt, exe.compiler, exe.scriptLoader) } // GetID returns the unique identifier (version number, or name) for this script version. func (exe *ExecutableUnit) GetID() string { - return exe.ID + return exe.id } // GetContent returns the validated & compiled script content as ExecutableContent func (exe *ExecutableUnit) GetContent() ExecutableContent { - return exe.Content + return exe.content } -// CreatedAt returns the timestamp when the version was created. +// GetCreatedAt returns the timestamp when the version was created. func (exe *ExecutableUnit) GetCreatedAt() time.Time { - return exe.CreatedAt + return exe.createdAt } // EngineType returns the engine type this script is intended to run on. func (exe *ExecutableUnit) EngineType() engineTypes.Type { - return exe.Content.EngineType() + return exe.content.EngineType() } // GetCompiler returns the compiler used to validate the script and convert it into runnable bytecode. func (exe *ExecutableUnit) GetCompiler() Compiler { - return exe.Compiler + return exe.compiler } // GetLoader returns the loader used to load the script. func (exe *ExecutableUnit) GetLoader() loader.Loader { - return exe.ScriptLoader + return exe.scriptLoader } // GetDataProvider returns the data provider for this executable unit. func (exe *ExecutableUnit) GetDataProvider() data.Provider { - return exe.DataProvider + return exe.dataProvider } diff --git a/platform/script/executableUnit_test.go b/platform/script/executableUnit_test.go index fddfb35..667c578 100644 --- a/platform/script/executableUnit_test.go +++ b/platform/script/executableUnit_test.go @@ -55,7 +55,7 @@ func TestVersionMethods(t *testing.T) { mockContent.On("EngineType").Return(expectedType) exe := &ExecutableUnit{ - Content: mockContent, + content: mockContent, } engineType := exe.EngineType() @@ -66,7 +66,7 @@ func TestVersionMethods(t *testing.T) { t.Run("GetCompiler", func(t *testing.T) { mockCompiler := new(MockCompiler) exe := &ExecutableUnit{ - Compiler: mockCompiler, + compiler: mockCompiler, } compiler := exe.GetCompiler() @@ -77,7 +77,7 @@ func TestVersionMethods(t *testing.T) { mockContent := new(MockExecutableContent) exe := &ExecutableUnit{ - Content: mockContent, + content: mockContent, } content := exe.GetContent() @@ -88,7 +88,7 @@ func TestVersionMethods(t *testing.T) { t.Run("GetCreatedAt", func(t *testing.T) { createdAt := time.Now() exe := &ExecutableUnit{ - CreatedAt: createdAt, + createdAt: createdAt, } timestamp := exe.GetCreatedAt() @@ -357,19 +357,19 @@ func TestExecutableUnit_String(t *testing.T) { mockContent := new(MockExecutableContent) exe := &ExecutableUnit{ - ID: "testID", - CreatedAt: time.Now(), - ScriptLoader: mockLoader, - Content: mockContent, - Compiler: mockCompiler, + id: "testID", + createdAt: time.Now(), + scriptLoader: mockLoader, + content: mockContent, + compiler: mockCompiler, } expectedString := fmt.Sprintf( "ExecutableUnit{ID: %s, CreatedAt: %s, Compiler: %s, Loader: %s}", - exe.ID, - exe.CreatedAt, - exe.Compiler, - exe.ScriptLoader, + exe.id, + exe.createdAt, + exe.compiler, + exe.scriptLoader, ) require.Equal(t, expectedString, exe.String(), "Expected string representation to match") @@ -419,7 +419,7 @@ func TestNewVersionWithScriptData(t *testing.T) { require.NoError(t, err, "Expected no error creating executable unit") require.NotNil(t, exe, "Expected executable unit to be non-nil") - dataFromProvider, err := exe.DataProvider.GetData(t.Context()) + dataFromProvider, err := exe.dataProvider.GetData(t.Context()) require.NoError(t, err, "Expected no error when getting data from provider") require.Equal(t, scriptData, dataFromProvider, "Expected script data to match") @@ -454,7 +454,7 @@ func TestNewVersionWithScriptData(t *testing.T) { ) require.NotNil(t, exe, "Expected version to be non-nil") - dataFromProvider, err := exe.DataProvider.GetData(t.Context()) + dataFromProvider, err := exe.dataProvider.GetData(t.Context()) require.NoError(t, err, "Expected no error when getting data from provider") require.Equal( t,