cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

cat/module-path
012 / scripts / install / install_test.go
install_test.go 6.6K
1// Package install holds the install scripts served at the docs site's2// root (install.sh, install.ps1) and the tests that run install.sh3// against a local server standing in for the site's /releases/.4package install56import (7 "archive/tar"8 "bytes"9 "compress/gzip"10 "crypto/sha256"11 "encoding/hex"12 "fmt"13 "net/http"14 "net/http/httptest"15 "os"16 "os/exec"17 "path/filepath"18 "runtime"19 "strings"20 "testing"21)2223// release is a fake release: a SHA256SUMS and an archive per Unix24// platform whose 012 is a shell script printing its version.25type release struct {26 files map[string][]byte27}2829func newRelease(t *testing.T, version string) release {30 t.Helper()31 r := release{files: map[string][]byte{}}32 var sums strings.Builder33 for _, goos := range []string{"darwin", "linux"} {34 for _, arch := range []string{"amd64", "arm64"} {35 name := fmt.Sprintf("012_%s_%s_%s", strings.TrimPrefix(version, "v"), goos, arch)36 bin := fmt.Sprintf("#!/bin/sh\necho 012 %s %s/%s\n", version, goos, arch)37 data := tarball(t, name, bin)38 r.files[name+".tar.gz"] = data39 sum := sha256.Sum256(data)40 fmt.Fprintf(&sums, "%s %s.tar.gz\n", hex.EncodeToString(sum[:]), name)41 }42 }43 r.files["SHA256SUMS"] = []byte(sums.String())44 return r45}4647func tarball(t *testing.T, dir, bin string) []byte {48 t.Helper()49 var buf bytes.Buffer50 gz := gzip.NewWriter(&buf)51 tw := tar.NewWriter(gz)52 for _, f := range []struct {53 name string54 body string55 mode int6456 }{{dir + "/012", bin, 0o755}, {dir + "/LICENSE", "license\n", 0o644}} {57 if err := tw.WriteHeader(&tar.Header{Name: f.name, Mode: f.mode, Size: int64(len(f.body)), Typeflag: tar.TypeReg}); err != nil {58 t.Fatal(err)59 }60 if _, err := tw.Write([]byte(f.body)); err != nil {61 t.Fatal(err)62 }63 }64 if err := tw.Close(); err != nil {65 t.Fatal(err)66 }67 if err := gz.Close(); err != nil {68 t.Fatal(err)69 }70 return buf.Bytes()71}7273// serve stands in for the site: /releases/<version>/<file>, with latest74// as one of the versions.75func serve(t *testing.T, releases map[string]release) *httptest.Server {76 t.Helper()77 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {78 version, file, ok := strings.Cut(strings.TrimPrefix(r.URL.Path, "/releases/"), "/")79 data, found := releases[version].files[file]80 if !ok || !found {81 http.NotFound(w, r)82 return83 }84 _, _ = w.Write(data)85 }))86 t.Cleanup(srv.Close)87 return srv88}8990// shells are the POSIX shells install.sh runs under: sh, and dash when91// it's installed, as Debian's /bin/sh is.92func shells(t *testing.T) []string {93 t.Helper()94 if runtime.GOOS == "windows" {95 t.Skip("install.sh is for macOS and Linux")96 }97 out := []string{"sh"}98 if _, err := exec.LookPath("dash"); err == nil {99 out = append(out, "dash")100 }101 return out102}103104type run struct {105 shell string106 env []string107 args []string108 stdin bool // the script piped to the shell, as curl | sh does109}110111func (r run) do(t *testing.T, home, base string) (string, error) {112 t.Helper()113 script, err := filepath.Abs("install.sh")114 if err != nil {115 t.Fatal(err)116 }117 var cmd *exec.Cmd118 if r.stdin {119 cmd = exec.Command(r.shell, append([]string{"-s", "--"}, r.args...)...)120 f, err := os.Open(script)121 if err != nil {122 t.Fatal(err)123 }124 defer f.Close()125 cmd.Stdin = f126 } else {127 cmd = exec.Command(r.shell, append([]string{script}, r.args...)...)128 }129 cmd.Dir = home130 cmd.Env = append([]string{"HOME=" + home, "PATH=" + os.Getenv("PATH"), "O12_BASE_URL=" + base}, r.env...)131 out, err := cmd.CombinedOutput()132 return string(out), err133}134135func TestInstallsTheLatestIntoLocalBin(t *testing.T) {136 srv := serve(t, map[string]release{"latest": newRelease(t, "v9.9.9")})137 for _, sh := range shells(t) {138 for _, stdin := range []bool{false, true} {139 t.Run(fmt.Sprintf("%s/stdin=%v", sh, stdin), func(t *testing.T) {140 home := t.TempDir()141 out, err := run{shell: sh, stdin: stdin}.do(t, home, srv.URL)142 if err != nil {143 t.Fatalf("install.sh: %v\n%s", err, out)144 }145 bin := filepath.Join(home, ".local", "bin", "012")146 got, err := exec.Command(bin).Output()147 if err != nil {148 t.Fatalf("running the installed 012: %v\n%s", err, out)149 }150 if !strings.HasPrefix(string(got), "012 v9.9.9 "+runtime.GOOS+"/") {151 t.Errorf("installed 012 prints %q", got)152 }153 for _, want := range []string{"SHA256 checked", "installed 012 v9.9.9", bin, "is not on your PATH"} {154 if !strings.Contains(out, want) {155 t.Errorf("output lacks %q:\n%s", want, out)156 }157 }158 })159 }160 }161}162163func TestInstallsAVersionUnderPrefix(t *testing.T) {164 srv := serve(t, map[string]release{165 "latest": newRelease(t, "v9.9.9"),166 "v1.2.3": newRelease(t, "v1.2.3"),167 })168 for _, sh := range shells(t) {169 t.Run(sh, func(t *testing.T) {170 home := t.TempDir()171 prefix := filepath.Join(home, "opt")172 out, err := run{shell: sh, env: []string{"PREFIX=" + prefix}, args: []string{"--version", "1.2.3"}}.do(t, home, srv.URL)173 if err != nil {174 t.Fatalf("install.sh: %v\n%s", err, out)175 }176 got, err := exec.Command(filepath.Join(prefix, "bin", "012")).Output()177 if err != nil || !strings.HasPrefix(string(got), "012 v1.2.3 ") {178 t.Fatalf("installed 012 prints %q (%v)\n%s", got, err, out)179 }180 if _, err := os.Stat(filepath.Join(home, ".local")); !os.IsNotExist(err) {181 t.Errorf("PREFIX set, but ~/.local was touched: %v", err)182 }183 })184 }185}186187func TestRefusesAnArchiveThatFailsItsChecksum(t *testing.T) {188 rel := newRelease(t, "v9.9.9")189 for name, data := range rel.files {190 if strings.HasSuffix(name, ".tar.gz") {191 rel.files[name] = append(append([]byte{}, data...), 0)192 }193 }194 srv := serve(t, map[string]release{"latest": rel})195 for _, sh := range shells(t) {196 t.Run(sh, func(t *testing.T) {197 home := t.TempDir()198 out, err := run{shell: sh}.do(t, home, srv.URL)199 if err == nil {200 t.Fatalf("install.sh succeeded with a bad archive:\n%s", out)201 }202 if !strings.Contains(out, "nothing installed") {203 t.Errorf("output doesn't say why:\n%s", out)204 }205 if _, err := os.Stat(filepath.Join(home, ".local", "bin", "012")); !os.IsNotExist(err) {206 t.Errorf("012 was installed anyway: %v", err)207 }208 })209 }210}211212func TestFailsOnAMissingReleaseOrOption(t *testing.T) {213 srv := serve(t, map[string]release{"latest": newRelease(t, "v9.9.9")})214 for _, tc := range []struct {215 args []string216 want string217 }{218 {[]string{"--version", "v0.0.1"}, "couldn't download"},219 {[]string{"--version", "next"}, "must be a release"},220 {[]string{"--frobnicate"}, "unknown option"},221 } {222 home := t.TempDir()223 out, err := run{shell: "sh", args: tc.args}.do(t, home, srv.URL)224 if err == nil || !strings.Contains(out, tc.want) {225 t.Errorf("install.sh %v: err %v, output lacks %q:\n%s", tc.args, err, tc.want, out)226 }227 }228}