package sshinstall import ( "strings" "testing" ) func TestGoArch(t *testing.T) { cases := map[string]string{ "x86_64": "amd64", "X86_64": "amd64", "amd64": "amd64", "aarch64": "arm64", "arm64": "arm64", "armv7l": "armv6l", "armv6l": "armv6l", "i686": "386", "i386": "386", "loongarch64": "loong64", "mips": "mips", "mipsel": "mipsle", "mips64": "mips64", "mips64el": "mips64le", "ppc64": "ppc64", "ppc64le": "ppc64le", "riscv64": "riscv64", "s390x": "s390x", } for in, want := range cases { got, err := GoArch(in) if err != nil || got != want { t.Fatalf("GoArch(%q) = %q, %v; want %q", in, got, err, want) } } for _, bad := range []string{"", "sparc", "x86", "mips64el-le"} { if _, err := GoArch(bad); err == nil { t.Fatalf("GoArch(%q) succeeded", bad) } } } // TestToolchainTableConsistentWithGoArch keeps GoArch and toolchainSHA in // sync: every `uname -m` mapping must resolve to a pinned checksum, and // every pinned checksum must be reachable through some `uname -m` value. func TestToolchainTableConsistentWithGoArch(t *testing.T) { // unameExamples maps a Go archive suffix to a real `uname -m` value // that GoArch accepts for it. unameExamples := map[string]string{ "amd64": "x86_64", "arm64": "aarch64", "armv6l": "armv7l", "386": "i686", "loong64": "loongarch64", "mips": "mips", "mipsle": "mipsel", "mips64": "mips64", "mips64le": "mips64el", "ppc64": "ppc64", "ppc64le": "ppc64le", "riscv64": "riscv64", "s390x": "s390x", } for archSuffix, unameValue := range unameExamples { tc, err := ToolchainFor(archSuffix, DefaultGoVersion) if err != nil { t.Fatalf("GoArch(%q) = %q has no pinned checksum: %v", unameValue, archSuffix, err) } if tc.SHA256 == "" || tc.Arch != "linux-"+archSuffix { t.Fatalf("ToolchainFor(%q) = %+v", archSuffix, tc) } } for suffix := range toolchainSHA { arch := strings.TrimPrefix(suffix, "linux-") if _, ok := unameExamples[arch]; !ok { t.Fatalf("checksum %q is not reachable through any GoArch `uname -m` mapping", suffix) } } } func TestToolchainFor(t *testing.T) { tc, err := ToolchainFor("amd64", "") if err != nil { t.Fatal(err) } if tc.Version != DefaultGoVersion { t.Fatalf("version = %q, want %q", tc.Version, DefaultGoVersion) } if tc.Arch != "linux-amd64" { t.Fatalf("arch = %q, want linux-amd64", tc.Arch) } if tc.URL != "https://go.dev/dl/go1.26.0.linux-amd64.tar.gz" { t.Fatalf("url = %q", tc.URL) } if len(tc.SHA256) != 64 { t.Fatalf("sha = %q, want 64 hex chars", tc.SHA256) } for _, c := range tc.SHA256 { hexDigit := c >= '0' && c <= '9' || c >= 'a' && c <= 'f' if !hexDigit { t.Fatalf("sha %q contains non-lowercase-hex char", tc.SHA256) } } } func TestToolchainForArm64Pinned(t *testing.T) { tc, err := ToolchainFor("arm64", DefaultGoVersion) if err != nil { t.Fatal(err) } if tc.SHA256 == "" || tc.Arch != "linux-arm64" { t.Fatalf("arm64 toolchain not pinned: %+v", tc) } } func TestToolchainForRejectsUnpinnedVersion(t *testing.T) { if _, err := ToolchainFor("amd64", "1.27.0"); err == nil { t.Fatal("unpinned Go version accepted") } if _, err := ToolchainFor("sparc", ""); err == nil { t.Fatal("unknown architecture accepted") } } func TestPlanSourceAlpine(t *testing.T) { d := Detect("ID=alpine\n", prober("/sbin/openrc")) p, err := PlanSource(d, SourceOptions{UnameM: "x86_64"}) if err != nil { t.Fatal(err) } if p.Repo != DefaultRepo || p.Branch != "" { t.Fatalf("plan defaults wrong: %+v", p) } if p.Toolchain.Arch != "linux-amd64" { t.Fatalf("toolchain = %+v", p.Toolchain) } if len(p.Packages) != 5 || p.Packages[0] != "git" { t.Fatalf("alpine packages = %v", p.Packages) } if p.InitSystem != InitOpenRC { t.Fatalf("init = %q, want openrc", p.InitSystem) } steps := p.Steps() if len(steps) != 6 { t.Fatalf("steps = %d, want 6", len(steps)) } if steps[0].Kind != StepInstallPackages || steps[1].Kind != StepInstallToolchain { t.Fatalf("step order wrong: %+v", steps) } if !strings.Contains(steps[1].Detail, "Go 1.26.0") || !strings.Contains(steps[1].Detail, "linux-amd64") { t.Fatalf("toolchain step detail = %q", steps[1].Detail) } if !strings.Contains(steps[5].Detail, "openrc") { t.Fatalf("install step detail = %q", steps[5].Detail) } } func TestPlanSourceUbuntuArch(t *testing.T) { ubuntu := Detect("ID=ubuntu\n", prober("/usr/lib/systemd/system")) p, err := PlanSource(ubuntu, SourceOptions{UnameM: "aarch64"}) if err != nil { t.Fatal(err) } if p.Toolchain.Arch != "linux-arm64" || p.InitSystem != InitSystemd { t.Fatalf("ubuntu plan = %+v", p) } arch := Detect("ID=arch\n", prober("/usr/lib/systemd/system")) p, err = PlanSource(arch, SourceOptions{UnameM: "x86_64"}) if err != nil { t.Fatal(err) } if p.Packages[0] != "git" || p.InitSystem != InitSystemd { t.Fatalf("arch plan = %+v", p) } } func TestPlanSourceExplicitOverrides(t *testing.T) { d := Detect("ID=ubuntu\n", nil) p, err := PlanSource(d, SourceOptions{ Repo: "https://example.test/worker.git", Branch: "release-1.0", GoArch: "arm64", BuildDir: "/srv/rsmon", GoModuleProxy: "https://proxy.golang.org,direct", }) if err != nil { t.Fatal(err) } if p.Repo != "https://example.test/worker.git" || p.Branch != "release-1.0" || p.BuildDir != "/srv/rsmon" || p.GoModuleProxy != "https://proxy.golang.org,direct" { t.Fatalf("overrides not applied: %+v", p) } if p.Toolchain.Arch != "linux-arm64" { t.Fatalf("toolchain = %+v", p.Toolchain) } } func TestPlanSourceErrors(t *testing.T) { unknown := Detect("ID=weirdos\n", nil) if _, err := PlanSource(unknown, SourceOptions{UnameM: "x86_64"}); err == nil { t.Fatal("unknown distro planned") } ubuntu := Detect("ID=ubuntu\n", nil) if _, err := PlanSource(ubuntu, SourceOptions{}); err == nil { t.Fatal("missing architecture planned") } for _, repo := range []string{"file:///tmp/worker.git", "not-a-url", "https://ex ample/x"} { if _, err := PlanSource(ubuntu, SourceOptions{UnameM: "x86_64", Repo: repo}); err == nil { t.Fatalf("invalid repo %q planned", repo) } } if _, err := PlanSource(ubuntu, SourceOptions{UnameM: "x86_64", BuildDir: "relative"}); err == nil { t.Fatal("relative build dir planned") } } func TestPackagePrereqsNeverIncludeCompiler(t *testing.T) { for _, pkg := range []PackageManager{PkgApk, PkgApt, PkgPacman, PkgDnf} { for _, name := range packagePrereqs(pkg) { switch name { case "build-essential", "gcc", "g++", "base-devel", "gcc-c++", "make": t.Fatalf("plan includes compiler package %q", name) } } } if got := packagePrereqs(PkgUnknown); len(got) != 0 { t.Fatalf("unknown package manager planned packages %v", got) } } func TestValidateRepoURLSchemes(t *testing.T) { for _, ok := range []string{"https://rocketgit.ru/rsmon/worker.git", "https://example.test/r"} { if err := validateRepoURL(ok); err != nil { t.Fatalf("validateRepoURL(%q): %v", ok, err) } } for _, bad := range []string{ "ssh://h@x/r", "s3://bucket/key", "x y", "", "http://x/y", "git://example.test/r", "https://user:pass@example.test/r", "https://token@example.test/r", "file:///tmp/r", } { if err := validateRepoURL(bad); err == nil { t.Fatalf("validateRepoURL(%q) succeeded", bad) } } } func TestPlanSourceRejectsUnsafeCharset(t *testing.T) { ubuntu := Detect("ID=ubuntu\n", nil) for _, goarch := range []string{"amd64;rm", "x;rm -rf", "$(id)", "..", "a b"} { if _, err := PlanSource(ubuntu, SourceOptions{UnameM: "x86_64", GoArch: goarch}); err == nil { t.Fatalf("unsafe GoArch %q planned", goarch) } } for _, version := range []string{"1.26;rm", "$(id)", "1.26.0 x", "a/b"} { if _, err := PlanSource(ubuntu, SourceOptions{UnameM: "x86_64", GoVersion: version}); err == nil { t.Fatalf("unsafe GoVersion %q planned", version) } } if !ValidGoVersion("1.26.0") || !ValidGoArch("amd64") { t.Fatal("valid version/arch rejected") } } func TestPlanSourceEmptyBranchSteps(t *testing.T) { ubuntu := Detect("ID=ubuntu\n", nil) p, err := PlanSource(ubuntu, SourceOptions{UnameM: "x86_64"}) if err != nil { t.Fatal(err) } if !strings.Contains(p.Steps()[3].Detail, "remote default branch") { t.Fatalf("checkout step detail for empty branch = %q", p.Steps()[3].Detail) } }