mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-16 02:15:37 +00:00
241 lines
6.9 KiB
Go
241 lines
6.9 KiB
Go
package skills
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// fixturePip3Path returns the absolute path to the fixture pip3 script.
|
|
// Uses runtime.Caller so the path is correct regardless of test working directory.
|
|
func fixturePip3Path(t *testing.T) string {
|
|
t.Helper()
|
|
_, file, _, ok := runtime.Caller(0)
|
|
if !ok {
|
|
t.Fatal("runtime.Caller failed")
|
|
}
|
|
p := filepath.Join(filepath.Dir(file), "testdata", "pip", "bin", "pip3")
|
|
if runtime.GOOS == "windows" {
|
|
p += ".cmd"
|
|
}
|
|
return p
|
|
}
|
|
|
|
// setupFixturePip overrides pipBinary and pipLookPath to use the bundled fixture script.
|
|
func setupFixturePip(t *testing.T) {
|
|
t.Helper()
|
|
origBinary := pipBinary
|
|
origLookPath := pipLookPath
|
|
pipBinary = fixturePip3Path(t)
|
|
pipLookPath = func(string) (string, error) { return pipBinary, nil }
|
|
t.Cleanup(func() {
|
|
pipBinary = origBinary
|
|
pipLookPath = origLookPath
|
|
})
|
|
}
|
|
|
|
// writeExecScript writes a shell script to path and makes it executable.
|
|
func writeExecScript(t *testing.T, path, content string) {
|
|
t.Helper()
|
|
if runtime.GOOS == "windows" {
|
|
if strings.Contains(content, "internal error") {
|
|
content = "@echo off\r\necho internal error 1>&2\r\nexit /b 1\r\n"
|
|
} else {
|
|
content = "@echo off\r\necho []\r\nexit /b 0\r\n"
|
|
}
|
|
}
|
|
if err := os.WriteFile(path, []byte(content), 0o755); err != nil {
|
|
t.Fatalf("writeExecScript: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestPipChecker_LookPathMiss verifies that a missing pip3 binary returns
|
|
// Available:false with nil Err and empty Updates — not an error condition.
|
|
func TestPipChecker_LookPathMiss(t *testing.T) {
|
|
origLookPath := pipLookPath
|
|
pipLookPath = func(string) (string, error) { return "", exec.ErrNotFound }
|
|
t.Cleanup(func() { pipLookPath = origLookPath })
|
|
|
|
c := NewPipUpdateChecker()
|
|
res := c.Check(context.Background(), nil)
|
|
|
|
if res.Source != "pip" {
|
|
t.Fatalf("Source = %q, want %q", res.Source, "pip")
|
|
}
|
|
if res.Available {
|
|
t.Fatal("Available = true, want false when pip3 not found")
|
|
}
|
|
if res.Err != nil {
|
|
t.Fatalf("Err = %v, want nil", res.Err)
|
|
}
|
|
if len(res.Updates) != 0 {
|
|
t.Fatalf("Updates len = %d, want 0", len(res.Updates))
|
|
}
|
|
}
|
|
|
|
// TestPipChecker_ParseFixture verifies that the checker correctly parses the
|
|
// outdated-23.3.json fixture (3 packages, one with a pre-release current version).
|
|
func TestPipChecker_ParseFixture(t *testing.T) {
|
|
setupFixturePip(t)
|
|
|
|
c := NewPipUpdateChecker()
|
|
res := c.Check(context.Background(), nil)
|
|
|
|
if !res.Available {
|
|
t.Fatal("Available = false, want true")
|
|
}
|
|
if res.Err != nil {
|
|
t.Fatalf("unexpected Err: %v", res.Err)
|
|
}
|
|
if len(res.Updates) != 3 {
|
|
t.Fatalf("Updates len = %d, want 3", len(res.Updates))
|
|
}
|
|
|
|
// Build lookup map for assertions.
|
|
byName := make(map[string]UpdateInfo, len(res.Updates))
|
|
for _, u := range res.Updates {
|
|
byName[u.Name] = u
|
|
}
|
|
|
|
// setuptools: stable current version — no preRelease flag.
|
|
st, ok := byName["setuptools"]
|
|
if !ok {
|
|
t.Fatal("missing 'setuptools' in Updates")
|
|
}
|
|
if st.Source != "pip" {
|
|
t.Errorf("setuptools Source = %q, want %q", st.Source, "pip")
|
|
}
|
|
if st.CurrentVersion != "65.5.0" {
|
|
t.Errorf("setuptools CurrentVersion = %q, want %q", st.CurrentVersion, "65.5.0")
|
|
}
|
|
if st.LatestVersion != "68.2.2" {
|
|
t.Errorf("setuptools LatestVersion = %q, want %q", st.LatestVersion, "68.2.2")
|
|
}
|
|
if v, _ := st.Meta["preRelease"].(bool); v {
|
|
t.Error("setuptools should NOT have preRelease=true")
|
|
}
|
|
if ft, _ := st.Meta["filetype"].(string); ft != "wheel" {
|
|
t.Errorf("setuptools filetype = %q, want %q", ft, "wheel")
|
|
}
|
|
|
|
// pip package: stable current version.
|
|
pipPkg, ok := byName["pip"]
|
|
if !ok {
|
|
t.Fatal("missing 'pip' in Updates")
|
|
}
|
|
if pipPkg.LatestVersion != "23.3.1" {
|
|
t.Errorf("pip LatestVersion = %q, want %q", pipPkg.LatestVersion, "23.3.1")
|
|
}
|
|
|
|
// torch: current version is pre-release (2.0.0rc1) → preRelease=true in Meta.
|
|
torch, ok := byName["torch"]
|
|
if !ok {
|
|
t.Fatal("missing 'torch' in Updates")
|
|
}
|
|
if torch.CurrentVersion != "2.0.0rc1" {
|
|
t.Errorf("torch CurrentVersion = %q, want %q", torch.CurrentVersion, "2.0.0rc1")
|
|
}
|
|
preRel, _ := torch.Meta["preRelease"].(bool)
|
|
if !preRel {
|
|
t.Error("torch should have preRelease=true because current version is rc1")
|
|
}
|
|
}
|
|
|
|
// TestPipChecker_EmptyResult verifies that zero outdated packages is valid
|
|
// (Available:true, empty Updates, nil Err).
|
|
func TestPipChecker_EmptyResult(t *testing.T) {
|
|
origBinary := pipBinary
|
|
origLookPath := pipLookPath
|
|
|
|
script := filepath.Join(t.TempDir(), "pip3")
|
|
if runtime.GOOS == "windows" {
|
|
script += ".cmd"
|
|
}
|
|
writeExecScript(t, script, "#!/bin/sh\necho '[]'\n")
|
|
pipBinary = script
|
|
pipLookPath = func(string) (string, error) { return script, nil }
|
|
t.Cleanup(func() {
|
|
pipBinary = origBinary
|
|
pipLookPath = origLookPath
|
|
})
|
|
|
|
c := NewPipUpdateChecker()
|
|
res := c.Check(context.Background(), nil)
|
|
|
|
if !res.Available {
|
|
t.Fatal("Available = false, want true for empty-but-successful check")
|
|
}
|
|
if res.Err != nil {
|
|
t.Fatalf("unexpected Err: %v", res.Err)
|
|
}
|
|
if len(res.Updates) != 0 {
|
|
t.Fatalf("Updates len = %d, want 0", len(res.Updates))
|
|
}
|
|
}
|
|
|
|
// TestPipChecker_ExecError verifies that a non-zero pip exit sets Err and
|
|
// keeps Available:true (source is reachable, command failed transiently).
|
|
func TestPipChecker_ExecError(t *testing.T) {
|
|
origBinary := pipBinary
|
|
origLookPath := pipLookPath
|
|
|
|
script := filepath.Join(t.TempDir(), "pip3")
|
|
if runtime.GOOS == "windows" {
|
|
script += ".cmd"
|
|
}
|
|
writeExecScript(t, script, "#!/bin/sh\necho 'internal error' >&2\nexit 1\n")
|
|
pipBinary = script
|
|
pipLookPath = func(string) (string, error) { return script, nil }
|
|
t.Cleanup(func() {
|
|
pipBinary = origBinary
|
|
pipLookPath = origLookPath
|
|
})
|
|
|
|
c := NewPipUpdateChecker()
|
|
res := c.Check(context.Background(), nil)
|
|
|
|
if !res.Available {
|
|
t.Fatal("Available = false, want true (source exists but errored)")
|
|
}
|
|
if res.Err == nil {
|
|
t.Fatal("Err = nil, want non-nil on exec failure")
|
|
}
|
|
}
|
|
|
|
// TestMergePipResults verifies union-by-name and higher-latest-version preference.
|
|
func TestMergePipResults(t *testing.T) {
|
|
primary := []pipOutdatedEntry{
|
|
{Name: "requests", Version: "2.28.0", LatestVersion: "2.31.0", LatestFiletype: "wheel"},
|
|
{Name: "numpy", Version: "1.24.0", LatestVersion: "1.25.0", LatestFiletype: "wheel"},
|
|
}
|
|
secondary := []pipOutdatedEntry{
|
|
{Name: "requests", Version: "2.28.0", LatestVersion: "2.32.0rc1", LatestFiletype: "wheel"},
|
|
{Name: "scipy", Version: "1.10.0", LatestVersion: "1.11.0", LatestFiletype: "wheel"},
|
|
}
|
|
|
|
merged := mergePipResults(primary, secondary)
|
|
|
|
if len(merged) != 3 {
|
|
t.Fatalf("merged len = %d, want 3", len(merged))
|
|
}
|
|
byName := make(map[string]pipOutdatedEntry, len(merged))
|
|
for _, e := range merged {
|
|
byName[e.Name] = e
|
|
}
|
|
|
|
// requests: secondary has higher latest_version string.
|
|
if req := byName["requests"]; req.LatestVersion != "2.32.0rc1" {
|
|
t.Errorf("requests LatestVersion = %q, want %q", req.LatestVersion, "2.32.0rc1")
|
|
}
|
|
if _, ok := byName["numpy"]; !ok {
|
|
t.Error("numpy missing from merge result")
|
|
}
|
|
if _, ok := byName["scipy"]; !ok {
|
|
t.Error("scipy missing from merge result")
|
|
}
|
|
}
|