first commit
This commit is contained in:
@@ -0,0 +1,264 @@
|
||||
package pkgmgr
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestDistributeExtensionStringBin verifies a package with a string "bin"
|
||||
// installs a launcher + payload tree and reports created files.
|
||||
func TestDistributeExtensionStringBin(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("extension install not supported on windows")
|
||||
}
|
||||
home := t.TempDir()
|
||||
t.Setenv("PIGO_HOME", home)
|
||||
|
||||
pkg := writePkg(t, `{"name":"pi-demo","version":"1.0.0","bin":"./cli.js"}`, map[string]string{
|
||||
"cli.js": "#!/usr/bin/env node\nconsole.log('hi')\n",
|
||||
"lib/x.js": "module.exports=1\n",
|
||||
})
|
||||
|
||||
files, err := DistributeExtension(pkg, "pi-demo")
|
||||
if err != nil {
|
||||
t.Fatalf("DistributeExtension: %v", err)
|
||||
}
|
||||
|
||||
launcher := filepath.Join(home, "plugins", "pi-demo")
|
||||
info, err := os.Stat(launcher)
|
||||
if err != nil {
|
||||
t.Fatalf("stat launcher: %v", err)
|
||||
}
|
||||
if info.Mode()&0o111 == 0 {
|
||||
t.Errorf("launcher not executable: mode %v", info.Mode())
|
||||
}
|
||||
|
||||
// The payload bin must exist and be executable.
|
||||
binAbs := filepath.Join(home, "plugins", "pi-demo.pkg", "cli.js")
|
||||
bi, err := os.Stat(binAbs)
|
||||
if err != nil {
|
||||
t.Fatalf("stat payload bin: %v", err)
|
||||
}
|
||||
if bi.Mode()&0o111 == 0 {
|
||||
t.Errorf("payload bin not executable: mode %v", bi.Mode())
|
||||
}
|
||||
|
||||
// Sibling files copied.
|
||||
if _, err := os.Stat(filepath.Join(home, "plugins", "pi-demo.pkg", "lib", "x.js")); err != nil {
|
||||
t.Errorf("sibling file not copied: %v", err)
|
||||
}
|
||||
|
||||
// created list includes launcher and payload dir.
|
||||
var sawLauncher bool
|
||||
for _, f := range files {
|
||||
if f == launcher {
|
||||
sawLauncher = true
|
||||
}
|
||||
}
|
||||
if !sawLauncher {
|
||||
t.Errorf("created files %v missing launcher", files)
|
||||
}
|
||||
|
||||
// A ".js" bin is a pi extension, so the launcher runs the Node host and a
|
||||
// .pihost.mjs is dropped beside the payload.
|
||||
host := filepath.Join(home, "plugins", "pi-demo.pkg", ".pihost.mjs")
|
||||
if _, err := os.Stat(host); err != nil {
|
||||
t.Errorf("expected embedded pi host at %q: %v", host, err)
|
||||
}
|
||||
script, _ := os.ReadFile(launcher)
|
||||
if !contains(string(script), "exec node ") || !contains(string(script), host) {
|
||||
t.Errorf("launcher script = %q, want node host exec of %q", script, host)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDistributeExtensionObjectBin verifies the {command: path} bin form,
|
||||
// preferring the entry keyed by package name.
|
||||
func TestDistributeExtensionObjectBin(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("extension install not supported on windows")
|
||||
}
|
||||
home := t.TempDir()
|
||||
t.Setenv("PIGO_HOME", home)
|
||||
|
||||
pkg := writePkg(t, `{"name":"pi-adapter","version":"2.0.0","bin":{"pi-adapter":"./main.js","other":"./other.js"}}`, map[string]string{
|
||||
"main.js": "#!/usr/bin/env node\n",
|
||||
"other.js": "#!/usr/bin/env node\n",
|
||||
})
|
||||
|
||||
if _, err := DistributeExtension(pkg, "pi-adapter"); err != nil {
|
||||
t.Fatalf("DistributeExtension: %v", err)
|
||||
}
|
||||
|
||||
binAbs := filepath.Join(home, "plugins", "pi-adapter.pkg", "main.js")
|
||||
if bi, err := os.Stat(binAbs); err != nil || bi.Mode()&0o111 == 0 {
|
||||
t.Errorf("expected main.js executable, err=%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDistributeExtensionReinstallReplaces verifies a second install clears the
|
||||
// stale payload rather than merging it.
|
||||
func TestDistributeExtensionReinstallReplaces(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("extension install not supported on windows")
|
||||
}
|
||||
home := t.TempDir()
|
||||
t.Setenv("PIGO_HOME", home)
|
||||
|
||||
pkg1 := writePkg(t, `{"name":"pi-x","version":"1.0.0","bin":"./a.js"}`, map[string]string{
|
||||
"a.js": "#!/usr/bin/env node\n",
|
||||
"gone.js": "old\n",
|
||||
})
|
||||
if _, err := DistributeExtension(pkg1, "pi-x"); err != nil {
|
||||
t.Fatalf("first install: %v", err)
|
||||
}
|
||||
|
||||
pkg2 := writePkg(t, `{"name":"pi-x","version":"2.0.0","bin":"./a.js"}`, map[string]string{
|
||||
"a.js": "#!/usr/bin/env node\n",
|
||||
})
|
||||
if _, err := DistributeExtension(pkg2, "pi-x"); err != nil {
|
||||
t.Fatalf("reinstall: %v", err)
|
||||
}
|
||||
|
||||
// The file only present in the first install must be gone.
|
||||
if _, err := os.Stat(filepath.Join(home, "plugins", "pi-x.pkg", "gone.js")); !os.IsNotExist(err) {
|
||||
t.Errorf("stale file survived reinstall: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDistributeExtensionPiExtensionsEntry verifies a pi extension with no npm
|
||||
// "bin" resolves its entrypoint from pi.extensions (the pi-simplify shape).
|
||||
func TestDistributeExtensionPiExtensionsEntry(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("extension install not supported on windows")
|
||||
}
|
||||
home := t.TempDir()
|
||||
t.Setenv("PIGO_HOME", home)
|
||||
|
||||
pkg := writePkg(t, `{"name":"pi-simplify","version":"0.2.3","pi":{"extensions":["dist/index.js"]}}`, map[string]string{
|
||||
"dist/index.js": "#!/usr/bin/env node\nconsole.log('hi')\n",
|
||||
})
|
||||
|
||||
files, err := DistributeExtension(pkg, "pi-simplify")
|
||||
if err != nil {
|
||||
t.Fatalf("DistributeExtension: %v", err)
|
||||
}
|
||||
|
||||
binAbs := filepath.Join(home, "plugins", "pi-simplify.pkg", "dist", "index.js")
|
||||
bi, err := os.Stat(binAbs)
|
||||
if err != nil {
|
||||
t.Fatalf("stat payload bin: %v", err)
|
||||
}
|
||||
if bi.Mode()&0o111 == 0 {
|
||||
t.Errorf("payload bin not executable: mode %v", bi.Mode())
|
||||
}
|
||||
|
||||
// A pi.extensions package must run under the Node host, dropping .pihost.mjs
|
||||
// and pointing the launcher at `node <host> <pkgDir>`.
|
||||
launcher := filepath.Join(home, "plugins", "pi-simplify")
|
||||
host := filepath.Join(home, "plugins", "pi-simplify.pkg", ".pihost.mjs")
|
||||
pkgDir := filepath.Join(home, "plugins", "pi-simplify.pkg")
|
||||
if _, err := os.Stat(host); err != nil {
|
||||
t.Errorf("expected embedded pi host at %q: %v", host, err)
|
||||
}
|
||||
script, _ := os.ReadFile(launcher)
|
||||
if !contains(string(script), "exec node ") || !contains(string(script), host) || !contains(string(script), pkgDir) {
|
||||
t.Errorf("launcher script = %q, want node host exec of %q with pkgDir %q", script, host, pkgDir)
|
||||
}
|
||||
if !contains(string(script), "node not found") {
|
||||
t.Errorf("launcher script = %q, missing node-absent guard", script)
|
||||
}
|
||||
|
||||
// Both the host and the launcher must be recorded for uninstall.
|
||||
var sawHost, sawLauncher bool
|
||||
for _, f := range files {
|
||||
switch f {
|
||||
case host:
|
||||
sawHost = true
|
||||
case launcher:
|
||||
sawLauncher = true
|
||||
}
|
||||
}
|
||||
if !sawHost || !sawLauncher {
|
||||
t.Errorf("created files %v missing host or launcher", files)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDistributeExtensionBinaryBinDirectExec verifies a native binary bin (no
|
||||
// pi.extensions, non-JS extension) keeps the historical direct-exec launcher —
|
||||
// no Node host, no .pihost.mjs.
|
||||
func TestDistributeExtensionBinaryBinDirectExec(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("extension install not supported on windows")
|
||||
}
|
||||
home := t.TempDir()
|
||||
t.Setenv("PIGO_HOME", home)
|
||||
|
||||
pkg := writePkg(t, `{"name":"pi-native","version":"1.0.0","bin":"./server"}`, map[string]string{
|
||||
"server": "#!/usr/bin/env node\n",
|
||||
})
|
||||
|
||||
files, err := DistributeExtension(pkg, "pi-native")
|
||||
if err != nil {
|
||||
t.Fatalf("DistributeExtension: %v", err)
|
||||
}
|
||||
|
||||
launcher := filepath.Join(home, "plugins", "pi-native")
|
||||
binAbs := filepath.Join(home, "plugins", "pi-native.pkg", "server")
|
||||
script, _ := os.ReadFile(launcher)
|
||||
if !contains(string(script), binAbs) {
|
||||
t.Errorf("launcher script = %q, want direct exec of %q", script, binAbs)
|
||||
}
|
||||
if contains(string(script), "exec node ") {
|
||||
t.Errorf("binary bin launcher = %q, should not run the Node host", script)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(home, "plugins", "pi-native.pkg", ".pihost.mjs")); !os.IsNotExist(err) {
|
||||
t.Errorf("binary bin should not drop .pihost.mjs, err=%v", err)
|
||||
}
|
||||
|
||||
// The launcher and payload dir must be recorded; the host must not be.
|
||||
var sawLauncher bool
|
||||
for _, f := range files {
|
||||
if f == launcher {
|
||||
sawLauncher = true
|
||||
}
|
||||
}
|
||||
if !sawLauncher {
|
||||
t.Errorf("created files %v missing launcher", files)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDistributeExtensionMainEntry verifies the "main" field is used as a
|
||||
// last-resort entrypoint when neither bin nor pi.extensions is present.
|
||||
func TestDistributeExtensionMainEntry(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("extension install not supported on windows")
|
||||
}
|
||||
home := t.TempDir()
|
||||
t.Setenv("PIGO_HOME", home)
|
||||
|
||||
pkg := writePkg(t, `{"name":"pi-main","version":"1.0.0","main":"./dist/index.js"}`, map[string]string{
|
||||
"dist/index.js": "#!/usr/bin/env node\n",
|
||||
})
|
||||
|
||||
if _, err := DistributeExtension(pkg, "pi-main"); err != nil {
|
||||
t.Fatalf("DistributeExtension: %v", err)
|
||||
}
|
||||
binAbs := filepath.Join(home, "plugins", "pi-main.pkg", "dist", "index.js")
|
||||
if bi, err := os.Stat(binAbs); err != nil || bi.Mode()&0o111 == 0 {
|
||||
t.Errorf("expected main entrypoint executable, err=%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDistributeExtensionNoBin verifies a package without a bin errors.
|
||||
func TestDistributeExtensionNoBin(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("extension install not supported on windows")
|
||||
}
|
||||
t.Setenv("PIGO_HOME", t.TempDir())
|
||||
pkg := writePkg(t, `{"name":"pi-nobin","version":"1.0.0"}`, nil)
|
||||
if _, err := DistributeExtension(pkg, "pi-nobin"); err == nil {
|
||||
t.Fatal("DistributeExtension without bin = nil error, want error")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user