Files
navidrome-meilisearch/plugins/runtime_test.go
Dongho Kim c251f174ed
Some checks failed
Pipeline: Test, Lint, Build / Get version info (push) Has been cancelled
Pipeline: Test, Lint, Build / Lint Go code (push) Has been cancelled
Pipeline: Test, Lint, Build / Test Go code (push) Has been cancelled
Pipeline: Test, Lint, Build / Test JS code (push) Has been cancelled
Pipeline: Test, Lint, Build / Lint i18n files (push) Has been cancelled
Pipeline: Test, Lint, Build / Check Docker configuration (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (darwin/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (darwin/arm64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/386) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v5) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v6) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v7) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (windows/386) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (windows/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Push to GHCR (push) Has been cancelled
Pipeline: Test, Lint, Build / Push to Docker Hub (push) Has been cancelled
Pipeline: Test, Lint, Build / Cleanup digest artifacts (push) Has been cancelled
Pipeline: Test, Lint, Build / Build Windows installers (push) Has been cancelled
Pipeline: Test, Lint, Build / Package/Release (push) Has been cancelled
Pipeline: Test, Lint, Build / Upload Linux PKG (push) Has been cancelled
Close stale issues and PRs / stale (push) Has been cancelled
POEditor import / update-translations (push) Has been cancelled
update
2025-12-08 16:16:23 +01:00

174 lines
4.8 KiB
Go

package plugins
import (
"context"
"fmt"
"os"
"path/filepath"
"time"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/core/metrics"
"github.com/navidrome/navidrome/plugins/schema"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/tetratelabs/wazero"
)
var _ = Describe("Runtime", func() {
Describe("pluginCompilationTimeout", func() {
It("should use DevPluginCompilationTimeout config for plugin compilation timeout", func() {
originalTimeout := conf.Server.DevPluginCompilationTimeout
DeferCleanup(func() {
conf.Server.DevPluginCompilationTimeout = originalTimeout
})
conf.Server.DevPluginCompilationTimeout = 123 * time.Second
Expect(pluginCompilationTimeout()).To(Equal(123 * time.Second))
conf.Server.DevPluginCompilationTimeout = 0
Expect(pluginCompilationTimeout()).To(Equal(time.Minute))
})
})
})
var _ = Describe("CachingRuntime", func() {
var (
ctx context.Context
mgr *managerImpl
plugin *wasmScrobblerPlugin
)
BeforeEach(func() {
ctx = GinkgoT().Context()
mgr = createManager(nil, metrics.NewNoopInstance())
// Add permissions for the test plugin using typed struct
permissions := schema.PluginManifestPermissions{
Http: &schema.PluginManifestPermissionsHttp{
Reason: "For testing HTTP functionality",
AllowedUrls: map[string][]schema.PluginManifestPermissionsHttpAllowedUrlsValueElem{
"*": {schema.PluginManifestPermissionsHttpAllowedUrlsValueElemWildcard},
},
AllowLocalNetwork: false,
},
Config: &schema.PluginManifestPermissionsConfig{
Reason: "For testing config functionality",
},
}
rtFunc := mgr.createRuntime("fake_scrobbler", permissions)
plugin = newWasmScrobblerPlugin(
filepath.Join(testDataDir, "fake_scrobbler", "plugin.wasm"),
"fake_scrobbler",
mgr,
rtFunc,
wazero.NewModuleConfig().WithStartFunctions("_initialize"),
).(*wasmScrobblerPlugin)
// runtime will be created on first plugin load
})
It("reuses module instances across calls", func() {
// First call to create the runtime and pool
_, done, err := plugin.getInstance(ctx, "first")
Expect(err).ToNot(HaveOccurred())
done()
val, ok := runtimePool.Load("fake_scrobbler")
Expect(ok).To(BeTrue())
cachingRT := val.(*cachingRuntime)
// Verify the pool exists and is initialized
Expect(cachingRT.pool).ToNot(BeNil())
// Test that multiple calls work without error (indicating pool reuse)
for i := 0; i < 5; i++ {
inst, done, err := plugin.getInstance(ctx, fmt.Sprintf("call_%d", i))
Expect(err).ToNot(HaveOccurred())
Expect(inst).ToNot(BeNil())
done()
}
// Test concurrent access to verify pool handles concurrency
const numGoroutines = 3
errChan := make(chan error, numGoroutines)
for i := 0; i < numGoroutines; i++ {
go func(id int) {
inst, done, err := plugin.getInstance(ctx, fmt.Sprintf("concurrent_%d", id))
if err != nil {
errChan <- err
return
}
defer done()
// Verify we got a valid instance
if inst == nil {
errChan <- fmt.Errorf("got nil instance")
return
}
errChan <- nil
}(i)
}
// Check all goroutines succeeded
for i := 0; i < numGoroutines; i++ {
err := <-errChan
Expect(err).To(BeNil())
}
})
})
var _ = Describe("purgeCacheBySize", func() {
var tmpDir string
BeforeEach(func() {
var err error
tmpDir, err = os.MkdirTemp("", "cache_test")
Expect(err).ToNot(HaveOccurred())
DeferCleanup(os.RemoveAll, tmpDir)
})
It("removes oldest entries when above the size limit", func() {
oldDir := filepath.Join(tmpDir, "d1")
newDir := filepath.Join(tmpDir, "d2")
Expect(os.Mkdir(oldDir, 0700)).To(Succeed())
Expect(os.Mkdir(newDir, 0700)).To(Succeed())
oldFile := filepath.Join(oldDir, "old")
newFile := filepath.Join(newDir, "new")
Expect(os.WriteFile(oldFile, []byte("xx"), 0600)).To(Succeed())
Expect(os.WriteFile(newFile, []byte("xx"), 0600)).To(Succeed())
oldTime := time.Now().Add(-2 * time.Hour)
Expect(os.Chtimes(oldFile, oldTime, oldTime)).To(Succeed())
purgeCacheBySize(tmpDir, "3")
_, err := os.Stat(oldFile)
Expect(os.IsNotExist(err)).To(BeTrue())
_, err = os.Stat(oldDir)
Expect(os.IsNotExist(err)).To(BeTrue())
_, err = os.Stat(newFile)
Expect(err).ToNot(HaveOccurred())
})
It("does nothing when below the size limit", func() {
dir1 := filepath.Join(tmpDir, "a")
dir2 := filepath.Join(tmpDir, "b")
Expect(os.Mkdir(dir1, 0700)).To(Succeed())
Expect(os.Mkdir(dir2, 0700)).To(Succeed())
file1 := filepath.Join(dir1, "f1")
file2 := filepath.Join(dir2, "f2")
Expect(os.WriteFile(file1, []byte("x"), 0600)).To(Succeed())
Expect(os.WriteFile(file2, []byte("x"), 0600)).To(Succeed())
purgeCacheBySize(tmpDir, "10MB")
_, err := os.Stat(file1)
Expect(err).ToNot(HaveOccurred())
_, err = os.Stat(file2)
Expect(err).ToNot(HaveOccurred())
})
})