tests: every test that runs plugins on the bun on PATH runs it once first, through testenv.Bun, so a bun that has never run here doesn't time out internal/provider's and internal/plugin's plugin tests (#1306)

65dcc573 (ggbdpq's patch 1) warmed the bun on PATH before the root
package's one-minute plugin contexts. The same lookup of the bun on PATH
is in 40 more places in internal/provider (28), internal/gateway (8),
internal/plugin (3) and internal/gui (1), and in the root package's
TestPluginListSaysClash. Many of them then open a one- or two-minute
context (internal/provider's sign-in, fetch and move tests,
internal/plugin's sandbox), and there macOS's first-exec check of a
just-installed or upgraded bun landed inside the deadline. The rest
are covered the same way, so no test has to remember which is which.

testenv.Bun now does what the root package's pathBun did: it finds the
bun on PATH (skipping the test when there is none) and runs
`bun --version` before the test opens its context, once per bun per test
binary. pathBun is gone; every `exec.LookPath("bun")` + skip block in
the tests uses testenv.Bun instead. Three tests reach for the bun some
other way and are left as they were: TestHostRunsAsMagpieBun (7ddea2c2
covers its copy), the plugin_git test's realBun and netproxy's
TestBunThroughBridge, none of which runs the plugin host under a
deadline on the PATH bun. migrate_test.go needed two `err` declarations
that the removed block used to provide.

What was run (macOS arm64, bun 1.4.2), with a wrapper bun first on PATH
whose first exec sleeps 61s (marker-gated, then execs the real bun):
- Red, old code:
  - internal/provider TestPluginSignInTakesAPastedCallback: FAIL at
    61.1s, "pluginsignin_paste_test.go:36: starting plugins: context
    deadline exceeded".
  - internal/plugin TestFakePlugin: FAIL at 62.0s, "plugin_test.go:66:
    Plugins = [], starting plugins: context deadline exceeded".
  - internal/gateway TestPluginFailover passed on the old code too (its
    calls have no deadline that short), so the gateway is not shown red.
- Green, this change, same cold wrapper: the three tests passed (61.1s,
  62.9s, 61.2s).
- go test -tags nogui -count=1 for the root package and for
  internal/testenv, gateway, gui, plugin and provider, under a temp HOME
  with GOMODCACHE, GOCACHE and GOPATH pinned: all ok.
- gofmt -l on the changed files: clean. go vet of those packages for
  darwin (-tags nogui), GOOS=linux and GOOS=windows (-tags nogui), and
  GOOS=windows without the tag: clean.

Not run: a really cold bun on a loaded machine; the sleep stands in for
the check. Not run on Linux or Windows, where the warm-up is one extra
`bun --version` per test binary.
This commit is contained in:
yetone committed 2026-10-10 10:22:59 +08:00
1 parent ba0e2a8aa9
commit 2da8fbe488
30 files changed
+97 -208

No files matched your search

+2 -1
View File
@@ -10,12 +10,13 @@ import (
"github.com/yetone/magpie/internal/plugin"
"github.com/yetone/magpie/internal/provider"
"github.com/yetone/magpie/internal/testenv"
)
// magpie accounts lists, switches and forgets a plugin's accounts by its
// provider's id, as a built-in subscription's.
func TestAccountsOfAPlugin(t *testing.T) {
bun := pathBun(t)
bun := testenv.Bun(t)
home := t.TempDir()
t.Setenv("HOME", home)
t.Setenv("USERPROFILE", home)
+3 -9
View File
@@ -6,7 +6,6 @@ import (
"io"
"net/http"
"net/http/httptest"
"os/exec"
"path/filepath"
"strings"
"sync"
@@ -15,6 +14,7 @@ import (
"github.com/yetone/magpie/internal/plugin"
"github.com/yetone/magpie/internal/provider"
"github.com/yetone/magpie/internal/testenv"
)
// A plugin's provider with several accounts fails over as a built-in's
@@ -24,10 +24,7 @@ import (
// and a routing group goes on to its next member when every account of
// the plugin is out.
func TestPluginFailover(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
fresh(t)
t.Setenv("MAGPIE_BUN", bun)
t.Cleanup(plugin.Settle)
@@ -255,10 +252,7 @@ func contains(xs []string, x string) bool {
// lapsed, as the built-in's did; a 401 the built-in didn't take for a
// refused sign-in leaves it be. The agent never sees the header.
func TestPluginSaysSignIn(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
fresh(t)
t.Setenv("MAGPIE_BUN", bun)
t.Cleanup(plugin.Settle)
+2 -5
View File
@@ -7,7 +7,6 @@ import (
"io"
"net/http"
"net/http/httptest"
"os/exec"
"path/filepath"
"strings"
"sync"
@@ -16,6 +15,7 @@ import (
"github.com/yetone/magpie/internal/plugin"
"github.com/yetone/magpie/internal/provider"
"github.com/yetone/magpie/internal/testenv"
)
// besideFake installs the fake plugin as the plugin of the built-in id,
@@ -23,10 +23,7 @@ import (
// key, its requests going to up; it gives the provider's id.
func besideFake(t *testing.T, id string, up http.Handler) string {
t.Helper()
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
fresh(t)
t.Setenv("MAGPIE_BUN", bun)
t.Setenv("FAKE_ID", id)
@@ -5,7 +5,6 @@ import (
"fmt"
"net/http"
"net/http/httptest"
"os/exec"
"path/filepath"
"slices"
"testing"
@@ -13,16 +12,14 @@ import (
"github.com/yetone/magpie/internal/plugin"
"github.com/yetone/magpie/internal/provider"
"github.com/yetone/magpie/internal/testenv"
)
// A plugin account's model list is asked through the account's own proxy,
// as a built-in account's is fetched through its: each of two accounts
// behind its own proxy is told the list its proxy carried.
func TestPluginModelListThroughAccountProxy(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
fresh(t)
for _, k := range []string{"HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy", "ALL_PROXY", "all_proxy", "NO_PROXY", "no_proxy"} {
t.Setenv(k, "")
+2 -5
View File
@@ -6,7 +6,6 @@ import (
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
@@ -15,16 +14,14 @@ import (
"github.com/yetone/magpie/internal/plugin"
"github.com/yetone/magpie/internal/provider"
"github.com/yetone/magpie/internal/testenv"
)
// movedFake is the fake plugin signed in as the built-in id it was moved
// onto, its requests going to up.
func movedFake(t *testing.T, id string, up http.Handler) {
t.Helper()
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
fresh(t)
t.Setenv("MAGPIE_BUN", bun)
t.Setenv("FAKE_ID", id)
+2 -5
View File
@@ -5,7 +5,6 @@ import (
"io"
"net/http"
"net/http/httptest"
"os/exec"
"path/filepath"
"strings"
"sync"
@@ -14,16 +13,14 @@ import (
"github.com/yetone/magpie/internal/plugin"
"github.com/yetone/magpie/internal/provider"
"github.com/yetone/magpie/internal/testenv"
)
// A plugin's account whose plan lacks the model is tried after the ones
// that list it, as a Codex account's is (a Free one behind a Plus): the
// plugin's first account, told only fake-1, isn't sent fake-resp.
func TestPluginAccountLackingTheModelGoesLast(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
fresh(t)
t.Setenv("MAGPIE_BUN", bun)
t.Setenv("FAKE_RESPONSES", "1")
+2 -5
View File
@@ -7,7 +7,6 @@ import (
"net"
"net/http"
"net/http/httptest"
"os/exec"
"path/filepath"
"strings"
"testing"
@@ -15,16 +14,14 @@ import (
"github.com/yetone/magpie/internal/plugin"
"github.com/yetone/magpie/internal/provider"
"github.com/yetone/magpie/internal/testenv"
)
// A plugin account whose proxy is down, HTTP or SOCKS5, never reached its
// vendor: as for a built-in's (TestProxyDownDoesNotRest), the next account
// answers and the first isn't set aside.
func TestPluginProxyDownDoesNotRest(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
for _, scheme := range []string{"http", "socks5"} {
t.Run(scheme, func(t *testing.T) {
fresh(t)
+2 -5
View File
@@ -6,7 +6,6 @@ import (
"io"
"net/http"
"net/http/httptest"
"os/exec"
"path/filepath"
"slices"
"strings"
@@ -16,6 +15,7 @@ import (
"github.com/yetone/magpie/internal/plugin"
"github.com/yetone/magpie/internal/provider"
"github.com/yetone/magpie/internal/testenv"
)
// A plugin's provider signed in to is a provider like the others: each of
@@ -23,10 +23,7 @@ import (
// base URL the plugin's loader gives, and the plugin's fetch carries the
// request with the sign-in it keeps.
func TestPluginProvider(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
fresh(t)
t.Setenv("MAGPIE_BUN", bun)
t.Cleanup(plugin.Settle)
+1 -5
View File
@@ -6,7 +6,6 @@ import (
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"testing"
@@ -19,10 +18,7 @@ import (
// /api/plugins carries the clash, naming the plugin that serves it and
// the provider as it is listed, and prefer hands it to the other one.
func TestPluginsClashRows(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
dir := t.TempDir()
testenv.SetHome(t, dir)
t.Setenv("XDG_CONFIG_HOME", filepath.Join(dir, "config"))
+2 -9
View File
@@ -11,7 +11,6 @@ import (
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
@@ -211,10 +210,7 @@ func TestCheckBunSetsAsideOneThatFailsItsTry(t *testing.T) {
// The real try: the Bun on PATH passes it, as its own version.
func TestTryBunOnRealBun(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
v := bunReported(bun)
if err := tryBun(context.Background(), bun, v); err != nil {
t.Fatalf("bun %s: %v", v, err)
@@ -230,10 +226,7 @@ func TestHostFallsBackWhenTheNewBunDies(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("the dying bun is a shell script")
}
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
bunHome(t, "9.9.9", time.Now().Add(-72*time.Hour))
t.Cleanup(Settle)
if err := os.MkdirAll(bunDirOf(BunVersion), 0o755); err != nil {
+2 -5
View File
@@ -9,7 +9,6 @@ import (
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"reflect"
"runtime"
@@ -20,16 +19,14 @@ import (
"time"
"github.com/yetone/magpie/internal/netproxy"
"github.com/yetone/magpie/internal/testenv"
)
// sandbox gives the test its own magpie folders and the Bun on PATH; a
// machine without Bun skips it.
func sandbox(t *testing.T) {
t.Helper()
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
dir := t.TempDir()
t.Setenv("HOME", dir)
t.Setenv("USERPROFILE", dir)
@@ -2,23 +2,20 @@ package provider
import (
"context"
"os/exec"
"path/filepath"
"reflect"
"testing"
"time"
"github.com/yetone/magpie/internal/plugin"
"github.com/yetone/magpie/internal/testenv"
)
// A plugin provider's accounts are arranged like a built-in's: the page
// names them by the provider's id, magpie keeps them as plugin:<id>, and
// the gateway tries them in the order dragged.
func TestAccountOrderPluginAccounts(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
claudeHome(t)
t.Setenv("MAGPIE_BUN", bun)
t.Cleanup(plugin.Settle)
+1 -4
View File
@@ -208,10 +208,7 @@ func TestCommandCodeSignInPastedKey(t *testing.T) {
// bash" in Docker, where there's no shell to run it: the CLI is installed
// first, as it is for a moved one.
func TestGrokPluginSignInInstallsCLI(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
home := claudeHome(t)
t.Setenv("MAGPIE_BUN", bun)
t.Cleanup(plugin.Settle)
+2 -5
View File
@@ -2,13 +2,13 @@ package provider
import (
"context"
"os/exec"
"path/filepath"
"slices"
"testing"
"time"
"github.com/yetone/magpie/internal/plugin"
"github.com/yetone/magpie/internal/testenv"
)
// #694: a plugin's subscription removed from magpie was listed among the
@@ -17,10 +17,7 @@ import (
// ForgetAccount signs its accounts out of plugin-auth.json and logins.json:
// it leaves the removed list, and a sign-in later starts afresh.
func TestForgetRemovedPluginAccount(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
claudeHome(t)
t.Setenv("MAGPIE_BUN", bun)
t.Cleanup(plugin.Settle)
+4 -12
View File
@@ -11,6 +11,7 @@ import (
"time"
"github.com/yetone/magpie/internal/plugin"
"github.com/yetone/magpie/internal/testenv"
)
// An account the vendor refused while it ran on the plugin goes back to
@@ -18,10 +19,7 @@ import (
// (every real mover clears the mark there): the built-in had marked it
// the same way, and it would show signed in until its next refusal.
func TestMoveBackKeepsLapse(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
claudeHome(t)
t.Setenv("MAGPIE_BUN", bun)
t.Cleanup(plugin.Settle)
@@ -76,10 +74,7 @@ func TestMoveBackKeepsLapse(t *testing.T) {
// on a refused model token) moves along marked, as the built-in would
// have marked it on its own model read, and doesn't stop the move.
func TestMoveRefusedWhileListing(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
claudeHome(t)
t.Setenv("MAGPIE_BUN", bun)
t.Cleanup(plugin.Settle)
@@ -123,10 +118,7 @@ func TestMoveRefusedWhileListing(t *testing.T) {
// the whole move. A plugin failing an account for any other reason still
// does (TestMove's r-dead).
func TestMoveExpiredInWords(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
claudeHome(t)
t.Setenv("MAGPIE_BUN", bun)
t.Cleanup(plugin.Settle)
+7 -18
View File
@@ -4,7 +4,6 @@ import (
"context"
"encoding/json"
"os"
"os/exec"
"path/filepath"
"slices"
"strings"
@@ -12,6 +11,7 @@ import (
"time"
"github.com/yetone/magpie/internal/plugin"
"github.com/yetone/magpie/internal/testenv"
"github.com/yetone/magpie/internal/zed"
)
@@ -77,10 +77,7 @@ func fakeSaved(t *testing.T) map[string]savedLogin {
// Moved, the plugin has the accounts in the user's order; moved back, the
// built-in has them again, fresh tokens and order included.
func TestMoveToPlugin(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
claudeHome(t)
t.Setenv("MAGPIE_BUN", bun)
t.Cleanup(plugin.Settle)
@@ -128,7 +125,7 @@ func TestMoveToPlugin(t *testing.T) {
// an account the vendor refuses: nothing moves, and the other's token,
// spent by the plugin, comes back renewed
reset(fakeLogin("a@fake", "rot-a", false, true), fakeLogin("c@fake", "r-dead", false, true))
err = Move(ctx, "fakeco")
err := Move(ctx, "fakeco")
if err == nil || !strings.Contains(err.Error(), "c@fake") {
t.Fatalf("Move with a refused account = %v", err)
}
@@ -282,10 +279,7 @@ func TestMoveToPlugin(t *testing.T) {
// A move cut short (magpie quit mid-way) is put back before it is tried
// again, and a failed one waits before magpie tries it by itself again.
func TestMoveCutShort(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
claudeHome(t)
t.Setenv("MAGPIE_BUN", bun)
t.Cleanup(plugin.Settle)
@@ -361,10 +355,7 @@ func mustPlugin(t *testing.T) plugin.Provider {
// when it renewed one, the built-in's when that renewed it meanwhile (a
// request went to it), none when neither did.
func TestPutBackKeepsNewer(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
claudeHome(t)
t.Setenv("MAGPIE_BUN", bun)
t.Cleanup(plugin.Settle)
@@ -470,10 +461,7 @@ func TestKeepMovedCurrent(t *testing.T) {
// the built-in back first: its accounts go back to it rather than out of
// sight with the plugin.
func TestReleasePlugin(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
for _, op := range []string{"off", "remove"} {
t.Run(op, func(t *testing.T) {
claudeHome(t)
@@ -502,6 +490,7 @@ func TestReleasePlugin(t *testing.T) {
if got := MovedOnto(abs); len(got) != 1 || got[0] != "fakeco" {
t.Fatalf("moved onto the plugin: %v", got)
}
var err error
if op == "off" {
err = SetPluginOff(ctx, abs, true)
} else {
+2 -5
View File
@@ -4,13 +4,13 @@ import (
"context"
"encoding/json"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
"github.com/yetone/magpie/internal/plugin"
"github.com/yetone/magpie/internal/testenv"
"github.com/yetone/magpie/internal/zed"
)
@@ -18,10 +18,7 @@ import (
// the plugin not yet installed: the move's install adds it.
func fakePlugin(t *testing.T, inUse *[]string) (context.Context, string, *int) {
t.Helper()
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
claudeHome(t)
t.Setenv("MAGPIE_BUN", bun)
t.Cleanup(plugin.Settle)
+3 -9
View File
@@ -3,7 +3,6 @@ package provider
import (
"context"
"os"
"os/exec"
"path/filepath"
"slices"
"strings"
@@ -12,16 +11,14 @@ import (
"github.com/yetone/magpie/internal/plugin"
"github.com/yetone/magpie/internal/settings"
"github.com/yetone/magpie/internal/testenv"
)
// pluginCheckinSandbox is the fake plugin with its own check-in, signed in
// with each key; it says the file each press is written to.
func pluginCheckinSandbox(t *testing.T, keys ...string) string {
t.Helper()
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
home := claudeHome(t)
t.Setenv("MAGPIE_BUN", bun)
presses := filepath.Join(home, "presses")
@@ -175,10 +172,7 @@ func TestPluginCheckinRunsAsTheBuiltinsDo(t *testing.T) {
// through its fetch, so an account isn't checked in twice; and its switch
// follows the vendor's, which it took over, until set on its own.
func TestPluginCheckinTakesOverTheVendors(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
claudeHome(t)
t.Setenv("MAGPIE_BUN", bun)
t.Setenv("FAKE_ID", "qoder")
+2 -5
View File
@@ -3,23 +3,20 @@ package provider
import (
"context"
"net/http"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
"github.com/yetone/magpie/internal/plugin"
"github.com/yetone/magpie/internal/testenv"
)
// A plugin's fetch hook that throws on its sign-in (the vendor turned the
// refresh away) marks the account lapsed, as a built-in's refused refresh
// marked it; the other account is left alone.
func TestPluginFetchSignInExpiredLapses(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
claudeHome(t)
t.Setenv("MAGPIE_BUN", bun)
t.Setenv("FAKE_BASE", "http://127.0.0.1:9/v1")
+3 -9
View File
@@ -6,13 +6,13 @@ import (
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"sync/atomic"
"testing"
"time"
"github.com/yetone/magpie/internal/plugin"
"github.com/yetone/magpie/internal/testenv"
)
// A plugin's account listed with the plugin's providers is not asked for
@@ -20,10 +20,7 @@ import (
// the Providers page built its state through FetchNew, and every ten
// minutes asked every plugin's vendors for their lists again.
func TestFetchNewLeavesListedPlugins(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
claudeHome(t)
t.Setenv("MAGPIE_BUN", bun)
t.Cleanup(plugin.Settle)
@@ -64,10 +61,7 @@ func TestFetchNewLeavesListedPlugins(t *testing.T) {
// plugin:<id> (routing) is read from the vendor once, as a built-in's,
// asked for by one name, is.
func TestPluginUsageReadOnceWhicheverName(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
claudeHome(t)
t.Setenv("MAGPIE_BUN", bun)
t.Cleanup(plugin.Settle)
+2 -5
View File
@@ -2,12 +2,12 @@ package provider
import (
"context"
"os/exec"
"path/filepath"
"testing"
"time"
"github.com/yetone/magpie/internal/plugin"
"github.com/yetone/magpie/internal/testenv"
)
// #867: a provider of the user's own saved before a plugin with its id was
@@ -15,10 +15,7 @@ import (
// subscription once signed in — "signed in, but magpie can't list it". The
// plugin's is id-plugin then, and the user's own keeps its id.
func TestPluginIDBesideCustomProvider(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
claudeHome(t)
t.Setenv("MAGPIE_BUN", bun)
t.Cleanup(plugin.Settle)
+2 -5
View File
@@ -2,22 +2,19 @@ package provider
import (
"context"
"os/exec"
"path/filepath"
"testing"
"time"
"github.com/yetone/magpie/internal/plugin"
"github.com/yetone/magpie/internal/testenv"
)
// An installed plugin's provider id is kept for it signed in or not, as a
// built-in subscription's is: a provider of the user's own saved on it, or
// renamed onto it, would hide the plugin's subscription once signed in.
func TestPluginIDReservedSignedOut(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
claudeHome(t)
t.Setenv("MAGPIE_BUN", bun)
t.Cleanup(plugin.Settle)
+1 -5
View File
@@ -4,7 +4,6 @@ import (
"context"
"encoding/json"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
@@ -170,10 +169,7 @@ func TestAllLoginsMoved(t *testing.T) {
// installed first when it isn't there, the code the page asks about shown
// to copy, and the account it signs in to named as the one in use.
func TestMovedSignInAsBuiltIn(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
home := claudeHome(t)
t.Setenv("MAGPIE_BUN", bun)
t.Cleanup(plugin.Settle)
+6 -21
View File
@@ -5,23 +5,20 @@ import (
"fmt"
"net/http"
"net/http/httptest"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
"github.com/yetone/magpie/internal/plugin"
"github.com/yetone/magpie/internal/testenv"
)
// A plugin's accounts show their allowance as the built-ins' do: beside
// each account, on the usage page, and to the gateway, a window counting
// only some models holding back those alone.
func TestPluginUsage(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
claudeHome(t)
t.Setenv("MAGPIE_BUN", bun)
t.Cleanup(plugin.Settle)
@@ -165,10 +162,7 @@ func TestPluginUsage(t *testing.T) {
// built-ins' did, and a clean one takes the mark off; one the network
// failed shows the last reading, as a built-in's did.
func TestPluginUsageLapse(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
claudeHome(t)
t.Setenv("MAGPIE_BUN", bun)
t.Cleanup(plugin.Settle)
@@ -229,10 +223,7 @@ func TestPluginUsageLapse(t *testing.T) {
// marks or clears as before. TestPluginFailover's refused account lost
// its mark to a reading of its allowance begun just before the 401.
func TestPluginUsageReadOlderThanAnswer(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
claudeHome(t)
t.Setenv("MAGPIE_BUN", bun)
t.Cleanup(plugin.Settle)
@@ -320,10 +311,7 @@ func TestPluginUsageReadOlderThanAnswer(t *testing.T) {
// serves, as each of a built-in's accounts had: a key that has fewer
// doesn't take on the first account's.
func TestPluginAccountModels(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
claudeHome(t)
t.Setenv("MAGPIE_BUN", bun)
t.Cleanup(plugin.Settle)
@@ -379,10 +367,7 @@ func TestMovedProviderName(t *testing.T) {
// A models hook that finds the sign-in refused and says so marks the
// account, as a built-in whose model list the vendor refused marked it.
func TestPluginModelsSayExpired(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
claudeHome(t)
t.Setenv("MAGPIE_BUN", bun)
t.Cleanup(plugin.Settle)
+2 -5
View File
@@ -3,13 +3,13 @@ package provider
import (
"context"
"net/url"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
"github.com/yetone/magpie/internal/plugin"
"github.com/yetone/magpie/internal/testenv"
)
// A plugin's browser sign-in that comes back to a port of the plugin's on
@@ -18,10 +18,7 @@ import (
// only this sign-in's port is taken, a page that sends the browser on is
// the next page to open, a wrong code fails, the right one signs in.
func TestPluginSignInTakesAPastedCallback(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
claudeHome(t)
t.Setenv("MAGPIE_BUN", bun)
t.Setenv("FAKE_LOOPBACK", "1")
+3 -9
View File
@@ -2,7 +2,6 @@ package provider
import (
"context"
"os/exec"
"path/filepath"
"slices"
"strings"
@@ -10,6 +9,7 @@ import (
"time"
"github.com/yetone/magpie/internal/plugin"
"github.com/yetone/magpie/internal/testenv"
)
// A plugin's sign-in is followed like a built-in one: the page, then the
@@ -17,10 +17,7 @@ import (
// done, its provider is there, and removing it signs out. A key signs in
// at once.
func TestPluginSignIn(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
claudeHome(t)
t.Setenv("MAGPIE_BUN", bun)
t.Cleanup(plugin.Settle)
@@ -102,10 +99,7 @@ func TestPluginSignIn(t *testing.T) {
// subscription does: each signed in beside the others, one first and the
// rest on behind it or off, any put first, and removing one signs it out.
func TestPluginAccounts(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
claudeHome(t)
t.Setenv("MAGPIE_BUN", bun)
t.Cleanup(plugin.Settle)
+2 -5
View File
@@ -6,7 +6,6 @@ import (
"net"
"net/http"
"net/http/httptest"
"os/exec"
"path/filepath"
"sync"
"testing"
@@ -14,6 +13,7 @@ import (
"github.com/yetone/magpie/internal/catalog"
"github.com/yetone/magpie/internal/plugin"
"github.com/yetone/magpie/internal/testenv"
)
// Signing in to an account removed from magpie shows it again before the
@@ -62,10 +62,7 @@ func TestSignInDoneOnceTheAccountIsBack(t *testing.T) {
}, {
agent: "fakeco",
setUp: func(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
claudeHome(t)
t.Setenv("MAGPIE_BUN", bun)
t.Cleanup(plugin.Settle)
+27
View File
@@ -0,0 +1,27 @@
package testenv
import (
"os/exec"
"sync"
"testing"
)
// bunsRun holds a *sync.Once for each bun Bun has run.
var bunsRun sync.Map
// Bun is the bun on PATH for a test that runs plugins with it (as
// MAGPIE_BUN), run once before the test opens its deadline; a machine
// without one skips the test. A bun just installed or upgraded hasn't run
// on this machine yet, and macOS checks a program before its first exec,
// which can take tens of seconds on a busy machine: that check must not eat
// the minute a test gives its plugin calls (ggbdpq, #1306).
func Bun(t testing.TB) string {
t.Helper()
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
once, _ := bunsRun.LoadOrStore(bun, new(sync.Once))
once.(*sync.Once).Do(func() { exec.Command(bun, "--version").Run() })
return bun
}
+2 -5
View File
@@ -3,22 +3,19 @@ package main
import (
"context"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"github.com/yetone/magpie/internal/plugin"
"github.com/yetone/magpie/internal/testenv"
)
// magpie plugin lists both plugins that sign in to one provider (neiko on
// Discord: a third-party plugin and their own), says which one serves it
// and how to have the other one serve it; `magpie plugin use` does that.
func TestPluginListSaysClash(t *testing.T) {
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
bun := testenv.Bun(t)
groupsHome(t)
t.Setenv("MAGPIE_BUN", bun)
t.Setenv("MAGPIE_PLUGIN_MARKET", "off")
+2 -17
View File
@@ -5,7 +5,6 @@ import (
"fmt"
"net/http"
"net/http/httptest"
"os/exec"
"path/filepath"
"sync/atomic"
"testing"
@@ -13,6 +12,7 @@ import (
"github.com/yetone/magpie/internal/plugin"
"github.com/yetone/magpie/internal/provider"
"github.com/yetone/magpie/internal/testenv"
)
// magpie accounts add, signing in to a plugin's provider removed from
@@ -101,7 +101,7 @@ func TestPluginLoginReadsUsageAfresh(t *testing.T) {
// plugin (FakeCo) added, as TestAccountsOfAPlugin sets one up.
func fakeCoHome(t *testing.T) context.Context {
t.Helper()
bun := pathBun(t)
bun := testenv.Bun(t)
home := t.TempDir()
t.Setenv("HOME", home)
t.Setenv("USERPROFILE", home)
@@ -122,21 +122,6 @@ func fakeCoHome(t *testing.T) context.Context {
return ctx
}
// pathBun is the bun on PATH, run once before the caller opens its
// one-minute context. A bun just installed or upgraded hasn't run on this
// machine yet, and macOS checks a program before its first exec, which can
// take tens of seconds on a busy machine: that check must not eat the
// minute the plugin calls have (ggbdpq, #1306; 7ddea2c2 for the copied bun).
func pathBun(t *testing.T) string {
t.Helper()
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
exec.Command(bun, "--version").Run()
return bun
}
// noBrowser has the sign-in pages magpie opens go nowhere. It swaps the
// opener rather than putting a do-nothing open first on PATH: a script
// written now is a program macOS checks on its first run, which took 25 to