package pythonlang_test import ( "os" "path/filepath" "runtime" "slices" "testing" "rickub.com/turbo-editors/turbo-core/ui" "rickub.com/turbo-editors/turbo-python/internal/pythonlang" ) // The slug is load-bearing: four different names are derived from it, and // changing it renames a directory in everybody's projects. func TestTheSlugNamesTheProjectDirectoryAndEveryEnvironmentVariable(t *testing.T) { p := pythonlang.Profile() for _, c := range []struct { what string got string want string }{ {"ProjectDir", p.ProjectDir(), ".turbo-python"}, {"DirEnvVar", p.DirEnvVar(), "TURBO_PYTHON_DIR"}, {"ThemeDirEnvVar", p.ThemeDirEnvVar(), "TURBO_PYTHON_THEME_DIR"}, {"SnippetDirEnvVar", p.SnippetDirEnvVar(), "TURBO_PYTHON_SNIPPET_DIR"}, } { if c.got != c.want { t.Errorf("%s() = %q, want %q", c.what, c.got, c.want) } } } func TestTheUserDirectoryFollowsItsEnvironmentVariable(t *testing.T) { p := pythonlang.Profile() dir := t.TempDir() t.Setenv(p.DirEnvVar(), dir) if got := p.UserDir(); got != dir { t.Errorf("UserDir() = %q, want %q", got, dir) } } // The theme directory's variable names the directory itself, not the one above // it — the one asymmetry in the derived paths, and the one worth a test. func TestTheThemeDirectoryFollowsItsOwnVariable(t *testing.T) { p := pythonlang.Profile() dir := t.TempDir() t.Setenv(p.ThemeDirEnvVar(), dir) if got := p.ThemeDir(); got != dir { t.Errorf("ThemeDir() = %q, want %q", got, dir) } } // P is free because the fixed menus take F, E, S, R, C, O, W, N and H. That the // bar agrees is TestTheToolchainMenuIsCalledPython's job, against the real menu // bar; this one holds the label itself to what was decided. // // The key comes back in lower case: a hot key is matched against what the // terminal delivers, and Alt-P and Alt-Shift-P are the same menu. func TestTheToolchainMenuIsPythonOnP(t *testing.T) { label, hot, _ := ui.SplitHotKey(pythonlang.Profile().ToolsMenu) if label != "Python" { t.Errorf("the toolchain menu is labelled %q, want %q", label, "Python") } if hot != 'p' { t.Errorf("its hot key is %q, want %q", hot, 'p') } } func TestTheProjectRootIsLookedForInThreeMarkersInOrder(t *testing.T) { want := []string{"pyproject.toml", "setup.py", "setup.cfg"} if got := pythonlang.Profile().RootMarkers; !slices.Equal(got, want) { t.Errorf("RootMarkers = %q, want %q", got, want) } } func TestTheServerIsPylspWithNoArguments(t *testing.T) { server := pythonlang.Profile().Server if server.Command != "pylsp" { t.Errorf("Server.Command = %q, want %q", server.Command, "pylsp") } if len(server.Args) != 0 { t.Errorf("Server.Args = %q, want none", server.Args) } if server.InstallHint == "" { t.Error("Server.InstallHint is empty; a missing server would be reported with no way out") } } // An active environment is the most specific answer there is, so it goes first: // a server installed into this project's environment must win over one the user // installed years ago. func TestAnActiveVirtualEnvironmentIsTheFirstPlaceLookedAfterPath(t *testing.T) { env := t.TempDir() t.Setenv("VIRTUAL_ENV", env) dirs := pythonlang.ServerDirs() if len(dirs) == 0 || dirs[0] != filepath.Join(env, "bin") { t.Errorf("ServerDirs() = %q, want %q first", dirs, filepath.Join(env, "bin")) } } func TestNoActiveEnvironmentContributesNothing(t *testing.T) { t.Setenv("VIRTUAL_ENV", "") if got := pythonlang.VirtualEnvBinDir(); got != "" { t.Errorf("VirtualEnvBinDir() = %q with VIRTUAL_ENV unset, want empty", got) } } func TestPyenvIsLookedForUnderItsOwnRootWhenOneIsSet(t *testing.T) { root := t.TempDir() t.Setenv("PYENV_ROOT", root) if got, want := pythonlang.PyenvShimDir(), filepath.Join(root, "shims"); got != want { t.Errorf("PyenvShimDir() = %q, want %q", got, want) } } func TestPyenvFallsBackToTheHomeDirectory(t *testing.T) { home := useTemporaryHome(t) t.Setenv("PYENV_ROOT", "") if got, want := pythonlang.PyenvShimDir(), filepath.Join(home, ".pyenv", "shims"); got != want { t.Errorf("PyenvShimDir() = %q, want %q", got, want) } } // The macOS user-scripts directory carries a version number nobody can predict, // so it is read off the disk. A machine with no such directory — every Linux // one — must contribute nothing rather than a path that cannot exist. func TestTheMacOSUserScriptDirectoriesAreReadRatherThanGuessed(t *testing.T) { home := useTemporaryHome(t) if got := pythonlang.FrameworkScriptDirs(); len(got) != 0 { t.Fatalf("FrameworkScriptDirs() = %q with no Library/Python, want none", got) } makeDir(t, filepath.Join(home, "Library", "Python", "3.9", "bin")) makeDir(t, filepath.Join(home, "Library", "Python", "3.13", "bin")) want := []string{ filepath.Join(home, "Library", "Python", "3.13", "bin"), filepath.Join(home, "Library", "Python", "3.9", "bin"), } if got := pythonlang.FrameworkScriptDirs(); !slices.Equal(got, want) { t.Errorf("FrameworkScriptDirs() = %q, want %q", got, want) } } // useTemporaryHome points the home directory at an empty one, so that the tests // never read the directories of whoever is running them. func useTemporaryHome(t *testing.T) string { t.Helper() if runtime.GOOS == "windows" { t.Skip("the home directory is not read from HOME on Windows") } home := t.TempDir() t.Setenv("HOME", home) return home } // makeDir creates a directory and every parent it needs. func makeDir(t *testing.T, path string) { t.Helper() if err := os.MkdirAll(path, 0o755); err != nil { t.Fatalf("creating %s: %v", path, err) } }