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 " 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) } }