Files
Behavision/agent/cmd/behavision-setup/python_test.go
Suriyakumarvijayanayagam 248025cdf9 The Python ceiling was defeated by the venv the failure left behind
makeVenv reused any environment already on disk, whatever Python built it.
The machine that found the version bug already had a runtime built by 3.14,
left there by the run that failed - so with the ceiling in place setup would
choose a good interpreter, reach makeVenv, find the 3.14 environment, keep it,
and die in the same clang error as before.

A fix a user cannot reach because the bug's own debris is in the way is not a
fix, and it would have read as the release not working.

It now asks the interpreter inside an existing environment what it is and
rebuilds when the answer is unsupported, saying so. Rebuilding costs a
re-download of the libraries and nothing else - the models live in the state
root. An environment that cannot be asked counts as unusable too: a
half-created one answers nothing, and reusing it fails later in pip with an
error about a package rather than about the environment.

Tested against real environments rather than a fake, because what is under
test is what an interpreter on disk reports about itself.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj
2026-09-30 17:11:47 +05:30

113 lines
4.0 KiB
Go

package main
import (
"os"
"path/filepath"
"testing"
)
// The choice this program makes silently, and got wrong.
//
// findPython took the newest interpreter on the machine, with a floor and no
// ceiling - backwards, because the newest Python is the one least likely to
// have binary wheels. On a Mac holding Python 3.14 it chose 3.14, pip found
// no numpy wheel for cp314, fell back to a source build and produced two
// screens of clang errors ending in "<arm_neon.h> is intended only for ARM
// and AArch64 targets". The operator's machine was fine; the version was not.
func TestTooNewIsRefusedRatherThanCompiled(t *testing.T) {
if got := pythonVerdict(3, maxMinor+1, true); got != verdictTooNew {
t.Errorf("3.%d = %q, want %q - picking it means a source build",
maxMinor+1, got, verdictTooNew)
}
if got := pythonVerdict(3, maxMinor, true); got != verdictOK {
t.Errorf("3.%d = %q, want %q - the ceiling is inclusive", maxMinor, got, verdictOK)
}
}
// Too old and too new must stay different answers. Telling somebody holding
// Python 3.14 that no Python was found, or that theirs is too old, sends them
// to install a newer one - which is the direction that already failed.
func TestOldAndNewAreDifferentAnswers(t *testing.T) {
old := pythonVerdict(3, minMinor-1, true)
fresh := pythonVerdict(3, maxMinor+1, true)
if old == fresh {
t.Fatalf("3.%d and 3.%d both reported %q", minMinor-1, maxMinor+1, old)
}
if old != verdictTooOld {
t.Errorf("3.%d = %q, want %q", minMinor-1, old, verdictTooOld)
}
}
// Every version in the range is accepted, so the window this program claims
// to support is the one it actually uses.
func TestTheWholeSupportedRangeIsAccepted(t *testing.T) {
for m := minMinor; m <= maxMinor; m++ {
if got := pythonVerdict(3, m, true); got != verdictOK {
t.Errorf("3.%d = %q, want %q", m, got, verdictOK)
}
}
if minMinor > maxMinor {
t.Fatal("the supported range is empty; nothing would ever be chosen")
}
}
// A major version nobody has tested against is not something to guess at, and
// an unreadable version string is not a working interpreter.
func TestUnknownVersionsAreNotAccepted(t *testing.T) {
for _, c := range []struct {
name string
major, minor int
parsed bool
}{
{"python 4", 4, 0, true},
{"python 2", 2, 7, true},
{"unparseable", 0, 0, false},
} {
if got := pythonVerdict(c.major, c.minor, c.parsed); got == verdictOK {
t.Errorf("%s was accepted", c.name)
}
}
}
// An environment already on disk is reused, and that is right until the Python
// inside it is one this build cannot use.
//
// It was reused unconditionally, which would have defeated the ceiling above
// on the exact machine that found the bug: that Mac already had a runtime
// built by Python 3.14, left behind by the run that failed. Setup would pick a
// good interpreter, find the 3.14 environment, keep it, and die in the same
// clang error as before - a fix defeated by the wreckage of the bug it fixes.
//
// Real environments, not a fake: the thing under test is what an interpreter
// on disk reports about itself.
func TestAnUnsupportedEnvironmentIsNotReused(t *testing.T) {
py, _, err := findPython()
if err != nil {
t.Skipf("no supported Python on this machine: %v", err)
}
venv := filepath.Join(t.TempDir(), "runtime")
if err := makeVenv(py, venv); err != nil {
t.Fatalf("makeVenv: %v", err)
}
if ok, ver := venvUsable(venv); !ok {
t.Fatalf("an environment built from the interpreter setup just chose "+
"reported itself unusable (%s)", ver)
}
// The two states that must not be confused with a working one.
empty := filepath.Join(t.TempDir(), "gone")
if ok, _ := venvUsable(empty); ok {
t.Error("a missing environment was reported usable")
}
broken := filepath.Join(t.TempDir(), "broken")
if err := os.MkdirAll(filepath.Dir(venvPython(broken)), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(venvPython(broken), []byte("not an interpreter"), 0o755); err != nil {
t.Fatal(err)
}
if ok, ver := venvUsable(broken); ok {
t.Errorf("a half-created environment was reported usable (%s)", ver)
}
}