File view with blame information shown in the left gutter beside each line.

barerepo / server / e2e/portability_test.go
363 lines · 14kb · 133728eaa05486504991b230f9d4c7e986b7defc
log files threads runs releases config jump to file t
133728e barerepo 1mo
1
// Package e2e runs chapter 45.1's test, because an unexercised portability claim goes false.
133728e barerepo 1mo
2
package e2e
133728e barerepo 1mo
3
133728e barerepo 1mo
4
import (
133728e barerepo 1mo
5
"context"
133728e barerepo 1mo
6
"fmt"
133728e barerepo 1mo
7
"net/http/httptest"
133728e barerepo 1mo
8
"os"
133728e barerepo 1mo
9
"os/exec"
133728e barerepo 1mo
10
"path/filepath"
133728e barerepo 1mo
11
"strings"
133728e barerepo 1mo
12
"testing"
133728e barerepo 1mo
13
133728e barerepo 1mo
14
"github.com/barerepo/server/internal/cache"
133728e barerepo 1mo
15
"github.com/barerepo/server/internal/config"
133728e barerepo 1mo
16
"github.com/barerepo/server/internal/gitread"
133728e barerepo 1mo
17
"github.com/barerepo/server/internal/httpd"
133728e barerepo 1mo
18
"github.com/barerepo/server/internal/repocfg"
133728e barerepo 1mo
19
"github.com/barerepo/server/internal/store"
133728e barerepo 1mo
20
"github.com/barerepo/server/internal/thread"
133728e barerepo 1mo
21
"github.com/barerepo/server/internal/transport"
133728e barerepo 1mo
22
)
133728e barerepo 1mo
23
133728e barerepo 1mo
24
var binary string
133728e barerepo 1mo
25
133728e barerepo 1mo
26
func TestMain(m *testing.M) {
133728e barerepo 1mo
27
dir, err := os.MkdirTemp("", "barerepo-e2e-")
133728e barerepo 1mo
28
if err != nil {
133728e barerepo 1mo
29
panic(err)
133728e barerepo 1mo
30
}
133728e barerepo 1mo
31
binary = filepath.Join(dir, "barerepo")
133728e barerepo 1mo
32
build := exec.Command("go", "build", "-o", binary, "../cmd/barerepo")
133728e barerepo 1mo
33
if out, err := build.CombinedOutput(); err != nil {
133728e barerepo 1mo
34
fmt.Fprintf(os.Stderr, "building barerepo: %v\n%s", err, out)
133728e barerepo 1mo
35
os.Exit(1)
133728e barerepo 1mo
36
}
133728e barerepo 1mo
37
code := m.Run()
133728e barerepo 1mo
38
os.RemoveAll(dir)
133728e barerepo 1mo
39
os.Exit(code)
133728e barerepo 1mo
40
}
133728e barerepo 1mo
41
133728e barerepo 1mo
42
// instance is one barerepo, so the test can push a mirror to a second one.
133728e barerepo 1mo
43
type instance struct {
133728e barerepo 1mo
44
t *testing.T
133728e barerepo 1mo
45
cfg config.Config
133728e barerepo 1mo
46
db *store.DB
133728e barerepo 1mo
47
srv *httpd.Server
133728e barerepo 1mo
48
http *httptest.Server
133728e barerepo 1mo
49
// keys remembers each account's key, so a test can sign as that account.
133728e barerepo 1mo
50
keys map[string]string
133728e barerepo 1mo
51
}
133728e barerepo 1mo
52
133728e barerepo 1mo
53
// keyOf is the private half of the key the account was made with.
133728e barerepo 1mo
54
func (in *instance) keyOf(name string) string {
133728e barerepo 1mo
55
in.t.Helper()
133728e barerepo 1mo
56
key, ok := in.keys[name]
133728e barerepo 1mo
57
if !ok {
133728e barerepo 1mo
58
in.t.Fatalf("no key was recorded for %s", name)
133728e barerepo 1mo
59
}
133728e barerepo 1mo
60
return key
133728e barerepo 1mo
61
}
133728e barerepo 1mo
62
133728e barerepo 1mo
63
func newInstance(t *testing.T) *instance {
133728e barerepo 1mo
64
t.Helper()
133728e barerepo 1mo
65
root := t.TempDir()
133728e barerepo 1mo
66
cfg := config.Default()
133728e barerepo 1mo
67
cfg.Paths.Repos = filepath.Join(root, "repos")
133728e barerepo 1mo
68
cfg.Paths.Cache = filepath.Join(root, "cache")
133728e barerepo 1mo
69
cfg.Paths.Artifacts = filepath.Join(root, "artifacts")
133728e barerepo 1mo
70
cfg.Database.URL = "sqlite://" + filepath.Join(root, "barerepo.db")
133728e barerepo 1mo
71
cfg.Path = filepath.Join(root, "barerepo.toml")
133728e barerepo 1mo
72
133728e barerepo 1mo
73
body := fmt.Sprintf("[paths]\nrepos = %q\ncache = %q\nartifacts = %q\n\n[database]\nurl = %q\n",
133728e barerepo 1mo
74
cfg.Paths.Repos, cfg.Paths.Cache, cfg.Paths.Artifacts, cfg.Database.URL)
133728e barerepo 1mo
75
if err := os.WriteFile(cfg.Path, []byte(body), 0o600); err != nil {
133728e barerepo 1mo
76
t.Fatal(err)
133728e barerepo 1mo
77
}
133728e barerepo 1mo
78
for _, d := range []string{cfg.Paths.Repos, cfg.Paths.Cache, cfg.Paths.Artifacts} {
133728e barerepo 1mo
79
if err := os.MkdirAll(d, 0o750); err != nil {
133728e barerepo 1mo
80
t.Fatal(err)
133728e barerepo 1mo
81
}
133728e barerepo 1mo
82
}
133728e barerepo 1mo
83
// The server writes this on every key change, and the hook checks signatures against it.
133728e barerepo 1mo
84
store.SignersPath = filepath.Join(cfg.Paths.Cache, "allowed_signers")
133728e barerepo 1mo
85
db, err := store.Open(context.Background(), cfg.Database.URL)
133728e barerepo 1mo
86
if err != nil {
133728e barerepo 1mo
87
t.Fatal(err)
133728e barerepo 1mo
88
}
133728e barerepo 1mo
89
t.Cleanup(func() { db.Close() })
133728e barerepo 1mo
90
133728e barerepo 1mo
91
// Every test runs warm, because a cache that returns the wrong answer only does so on a hit.
133728e barerepo 1mo
92
shared := cache.New(cfg.Paths.Cache)
133728e barerepo 1mo
93
gitread.Cache = shared
133728e barerepo 1mo
94
repocfg.Cache = shared
133728e barerepo 1mo
95
thread.Cache = shared
133728e barerepo 1mo
96
httpd.Cache = shared
133728e barerepo 1mo
97
133728e barerepo 1mo
98
srv := &httpd.Server{Cfg: cfg, DB: db,
133728e barerepo 1mo
99
Transport: &transport.Server{Cfg: cfg, DB: db, Bin: binary}}
133728e barerepo 1mo
100
ts := httptest.NewServer(srv.Handler())
133728e barerepo 1mo
101
t.Cleanup(ts.Close)
133728e barerepo 1mo
102
133728e barerepo 1mo
103
// The external url must be the one the server actually got, so clone urls point at it.
133728e barerepo 1mo
104
cfg.Server.ExternalURL = ts.URL
133728e barerepo 1mo
105
srv.Cfg = cfg
133728e barerepo 1mo
106
srv.Transport.Cfg = cfg
133728e barerepo 1mo
107
body += fmt.Sprintf("\n[server]\nexternal_url = %q\n", ts.URL)
133728e barerepo 1mo
108
if err := os.WriteFile(cfg.Path, []byte(body), 0o600); err != nil {
133728e barerepo 1mo
109
t.Fatal(err)
133728e barerepo 1mo
110
}
133728e barerepo 1mo
111
return &instance{t: t, cfg: cfg, db: db, srv: srv, http: ts}
133728e barerepo 1mo
112
}
133728e barerepo 1mo
113
133728e barerepo 1mo
114
// account makes an account with a fresh key and returns a push token.
133728e barerepo 1mo
115
func (in *instance) account(name string) string {
133728e barerepo 1mo
116
in.t.Helper()
133728e barerepo 1mo
117
dir := in.t.TempDir()
133728e barerepo 1mo
118
key := filepath.Join(dir, "id")
133728e barerepo 1mo
119
run(in.t, "", "ssh-keygen", "-t", "ed25519", "-N", "", "-C", name, "-f", key, "-q")
133728e barerepo 1mo
120
pub, err := os.ReadFile(key + ".pub")
133728e barerepo 1mo
121
if err != nil {
133728e barerepo 1mo
122
in.t.Fatal(err)
133728e barerepo 1mo
123
}
133728e barerepo 1mo
124
if _, err := in.db.CreateAccount(context.Background(), name, string(pub), false); err != nil {
133728e barerepo 1mo
125
in.t.Fatal(err)
133728e barerepo 1mo
126
}
133728e barerepo 1mo
127
if in.keys == nil {
133728e barerepo 1mo
128
in.keys = map[string]string{}
133728e barerepo 1mo
129
}
133728e barerepo 1mo
130
in.keys[name] = key
133728e barerepo 1mo
131
tok, _, err := in.db.CreateToken(context.Background(), "gt", name, "", "test")
133728e barerepo 1mo
132
if err != nil {
133728e barerepo 1mo
133
in.t.Fatal(err)
133728e barerepo 1mo
134
}
133728e barerepo 1mo
135
return tok
133728e barerepo 1mo
136
}
133728e barerepo 1mo
137
133728e barerepo 1mo
138
func (in *instance) url(token, path string) string {
133728e barerepo 1mo
139
return strings.Replace(in.http.URL, "://", "://x:"+token+"@", 1) + path
133728e barerepo 1mo
140
}
133728e barerepo 1mo
141
133728e barerepo 1mo
142
func run(t *testing.T, dir string, name string, args ...string) string {
133728e barerepo 1mo
143
t.Helper()
133728e barerepo 1mo
144
cmd := exec.Command(name, args...)
133728e barerepo 1mo
145
cmd.Dir = dir
133728e barerepo 1mo
146
cmd.Env = append(os.Environ(),
133728e barerepo 1mo
147
"GIT_AUTHOR_NAME=tester", "GIT_AUTHOR_EMAIL=t@x",
133728e barerepo 1mo
148
"GIT_COMMITTER_NAME=tester", "GIT_COMMITTER_EMAIL=t@x",
133728e barerepo 1mo
149
"GIT_TERMINAL_PROMPT=0", "GIT_CONFIG_COUNT=1",
133728e barerepo 1mo
150
"GIT_CONFIG_KEY_0=credential.helper", "GIT_CONFIG_VALUE_0=")
133728e barerepo 1mo
151
out, err := cmd.CombinedOutput()
133728e barerepo 1mo
152
if err != nil {
133728e barerepo 1mo
153
t.Fatalf("%s %s: %v\n%s", name, strings.Join(args, " "), err, out)
133728e barerepo 1mo
154
}
133728e barerepo 1mo
155
return strings.TrimSpace(string(out))
133728e barerepo 1mo
156
}
133728e barerepo 1mo
157
133728e barerepo 1mo
158
func try(t *testing.T, dir string, name string, args ...string) (string, error) {
133728e barerepo 1mo
159
t.Helper()
133728e barerepo 1mo
160
cmd := exec.Command(name, args...)
133728e barerepo 1mo
161
cmd.Dir = dir
133728e barerepo 1mo
162
cmd.Env = append(os.Environ(),
133728e barerepo 1mo
163
"GIT_AUTHOR_NAME=tester", "GIT_AUTHOR_EMAIL=t@x",
133728e barerepo 1mo
164
"GIT_COMMITTER_NAME=tester", "GIT_COMMITTER_EMAIL=t@x",
133728e barerepo 1mo
165
"GIT_TERMINAL_PROMPT=0", "GIT_CONFIG_COUNT=1",
133728e barerepo 1mo
166
"GIT_CONFIG_KEY_0=credential.helper", "GIT_CONFIG_VALUE_0=")
133728e barerepo 1mo
167
out, err := cmd.CombinedOutput()
133728e barerepo 1mo
168
return string(out), err
133728e barerepo 1mo
169
}
133728e barerepo 1mo
170
133728e barerepo 1mo
171
// TestPortability is chapter 45.1, step for step.
133728e barerepo 1mo
172
func TestPortability(t *testing.T) {
133728e barerepo 1mo
173
if _, err := exec.LookPath("git"); err != nil {
133728e barerepo 1mo
174
t.Skip("git is not installed")
133728e barerepo 1mo
175
}
133728e barerepo 1mo
176
origin := newInstance(t)
133728e barerepo 1mo
177
john := origin.account("john")
133728e barerepo 1mo
178
lisa := origin.account("lisa")
133728e barerepo 1mo
179
133728e barerepo 1mo
180
// 1. Create a repository. Push code.
133728e barerepo 1mo
181
work := t.TempDir()
133728e barerepo 1mo
182
run(t, work, "git", "init", "-q", "-b", "master")
133728e barerepo 1mo
183
write(t, work, "config.go", "package main\n\nfunc Load() error {\n\treturn nil\n}\n")
133728e barerepo 1mo
184
write(t, work, ".barerepo/config", "[repo]\nvisibility = \"public\"\n\n[build]\ncommand = \"true\"\n")
133728e barerepo 1mo
185
run(t, work, "git", "add", "-A")
133728e barerepo 1mo
186
run(t, work, "git", "commit", "-qm", "first")
133728e barerepo 1mo
187
run(t, work, "git", "push", "-q", origin.url(john, "/john/johnbot"), "master")
133728e barerepo 1mo
188
133728e barerepo 1mo
189
// 2. Open a thread. Reply to it.
133728e barerepo 1mo
190
dir := repoDir(t, origin, "john", "johnbot")
133728e barerepo 1mo
191
head := run(t, dir, "git", "rev-parse", "HEAD")
133728e barerepo 1mo
192
note(t, dir, 1, "title: does this work behind a socks proxy?\nstate: open\nauthor: lisa\n",
133728e barerepo 1mo
193
head, "author: lisa\ntime: 1\n\nasking about proxies.\n")
133728e barerepo 1mo
194
appendNote(t, dir, 1, head, "author: john\ntime: 2\n\nit should.\n")
133728e barerepo 1mo
195
133728e barerepo 1mo
196
// 3. Push a proposal from a second account. Comment on a line.
133728e barerepo 1mo
197
other := t.TempDir()
133728e barerepo 1mo
198
run(t, other, "git", "clone", "-q", origin.url(lisa, "/john/johnbot"), other+"/c")
133728e barerepo 1mo
199
clone := other + "/c"
133728e barerepo 1mo
200
write(t, clone, "config.go", "package main\n\nfunc Load() error {\n\treturn fmt.Errorf(\"no\")\n}\n")
133728e barerepo 1mo
201
run(t, clone, "git", "commit", "-qam", "return a real error")
133728e barerepo 1mo
202
// Thread 1 was pushed straight in, as chapter 35.5 allows, so allocation steps over it.
133728e barerepo 1mo
203
out := run(t, clone, "git", "push", origin.url(lisa, "/john/johnbot"), "HEAD:refs/proposals/new")
133728e barerepo 1mo
204
if !strings.Contains(out, "refs/proposals/2") {
133728e barerepo 1mo
205
t.Fatalf("the proposal did not step over the thread that already had 1:\n%s", out)
133728e barerepo 1mo
206
}
133728e barerepo 1mo
207
blob := run(t, dir, "git", "rev-parse", "refs/proposals/2:config.go")
133728e barerepo 1mo
208
tip := run(t, dir, "git", "rev-parse", "refs/proposals/2")
133728e barerepo 1mo
209
appendNote(t, dir, 1, tip,
133728e barerepo 1mo
210
"author: john\ntime: 3\nanchor: config.go:4\nblob: "+blob+"\nside: new\n\nis fmt imported?\n")
133728e barerepo 1mo
211
133728e barerepo 1mo
212
// 4. Merge the proposal.
212 of 363 lines · the whole file
history · rawbarerepo 0.1.0