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

barerepo / server / testserver/testserver.go
186 lines · 5.5kb · master
log files threads runs releases config jump to file t
133728e barerepo 1mo
1
// Package testserver runs a real barerepo in a test, so the other two repos can drive this one.
133728e barerepo 1mo
2
package testserver
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
"sync"
133728e barerepo 1mo
13
"testing"
133728e barerepo 1mo
14
133728e barerepo 1mo
15
"github.com/barerepo/server/internal/cache"
133728e barerepo 1mo
16
"github.com/barerepo/server/internal/config"
133728e barerepo 1mo
17
"github.com/barerepo/server/internal/gitread"
133728e barerepo 1mo
18
"github.com/barerepo/server/internal/httpd"
133728e barerepo 1mo
19
"github.com/barerepo/server/internal/repocfg"
133728e barerepo 1mo
20
"github.com/barerepo/server/internal/store"
133728e barerepo 1mo
21
"github.com/barerepo/server/internal/thread"
133728e barerepo 1mo
22
"github.com/barerepo/server/internal/token"
133728e barerepo 1mo
23
"github.com/barerepo/server/internal/transport"
133728e barerepo 1mo
24
)
133728e barerepo 1mo
25
133728e barerepo 1mo
26
// Instance is one running barerepo, torn down when the test ends.
133728e barerepo 1mo
27
type Instance struct {
133728e barerepo 1mo
28
URL string
133728e barerepo 1mo
29
Cfg config.Config
133728e barerepo 1mo
30
DB *store.DB
133728e barerepo 1mo
31
}
133728e barerepo 1mo
32
133728e barerepo 1mo
33
var (
133728e barerepo 1mo
34
buildOnce sync.Once
133728e barerepo 1mo
35
buildPath string
133728e barerepo 1mo
36
buildErr error
133728e barerepo 1mo
37
)
133728e barerepo 1mo
38
133728e barerepo 1mo
39
// Binary builds the server once per test binary, because the hooks are this program calling itself.
133728e barerepo 1mo
40
func Binary(tb testing.TB) string {
133728e barerepo 1mo
41
tb.Helper()
133728e barerepo 1mo
42
buildOnce.Do(func() {
133728e barerepo 1mo
43
dir, err := os.MkdirTemp("", "barerepo-testserver-")
133728e barerepo 1mo
44
if err != nil {
133728e barerepo 1mo
45
buildErr = err
133728e barerepo 1mo
46
return
133728e barerepo 1mo
47
}
133728e barerepo 1mo
48
buildPath = filepath.Join(dir, "barerepo")
133728e barerepo 1mo
49
out, err := exec.Command("go", "build", "-o", buildPath,
133728e barerepo 1mo
50
"github.com/barerepo/server/cmd/barerepo").CombinedOutput()
133728e barerepo 1mo
51
if err != nil {
133728e barerepo 1mo
52
buildErr = fmt.Errorf("building the server: %v\n%s", err, out)
133728e barerepo 1mo
53
}
133728e barerepo 1mo
54
})
133728e barerepo 1mo
55
if buildErr != nil {
133728e barerepo 1mo
56
tb.Fatal(buildErr)
133728e barerepo 1mo
57
}
133728e barerepo 1mo
58
return buildPath
133728e barerepo 1mo
59
}
133728e barerepo 1mo
60
133728e barerepo 1mo
61
// New starts a barerepo on a loopback port with its own disk and database.
133728e barerepo 1mo
62
func New(tb testing.TB) *Instance {
133728e barerepo 1mo
63
tb.Helper()
133728e barerepo 1mo
64
bin := Binary(tb)
133728e barerepo 1mo
65
root := tb.TempDir()
133728e barerepo 1mo
66
133728e barerepo 1mo
67
cfg := config.Default()
133728e barerepo 1mo
68
cfg.Paths.Repos = filepath.Join(root, "repos")
133728e barerepo 1mo
69
cfg.Paths.Cache = filepath.Join(root, "cache")
133728e barerepo 1mo
70
cfg.Paths.Artifacts = filepath.Join(root, "artifacts")
133728e barerepo 1mo
71
cfg.Database.URL = "sqlite://" + filepath.Join(root, "barerepo.db")
133728e barerepo 1mo
72
cfg.Path = filepath.Join(root, "barerepo.toml")
133728e barerepo 1mo
73
133728e barerepo 1mo
74
body := fmt.Sprintf("[paths]\nrepos = %q\ncache = %q\nartifacts = %q\n\n[database]\nurl = %q\n",
133728e barerepo 1mo
75
cfg.Paths.Repos, cfg.Paths.Cache, cfg.Paths.Artifacts, cfg.Database.URL)
133728e barerepo 1mo
76
if err := os.WriteFile(cfg.Path, []byte(body), 0o600); err != nil {
133728e barerepo 1mo
77
tb.Fatal(err)
133728e barerepo 1mo
78
}
133728e barerepo 1mo
79
for _, d := range []string{cfg.Paths.Repos, cfg.Paths.Cache, cfg.Paths.Artifacts} {
133728e barerepo 1mo
80
if err := os.MkdirAll(d, 0o750); err != nil {
133728e barerepo 1mo
81
tb.Fatal(err)
133728e barerepo 1mo
82
}
133728e barerepo 1mo
83
}
133728e barerepo 1mo
84
133728e barerepo 1mo
85
// The server writes this on every key change, and the hook checks signatures against it.
133728e barerepo 1mo
86
store.SignersPath = filepath.Join(cfg.Paths.Cache, "allowed_signers")
133728e barerepo 1mo
87
db, err := store.Open(context.Background(), cfg.Database.URL)
133728e barerepo 1mo
88
if err != nil {
133728e barerepo 1mo
89
tb.Fatal(err)
133728e barerepo 1mo
90
}
133728e barerepo 1mo
91
tb.Cleanup(func() { db.Close() })
133728e barerepo 1mo
92
133728e barerepo 1mo
93
shared := cache.New(cfg.Paths.Cache)
133728e barerepo 1mo
94
gitread.Cache = shared
133728e barerepo 1mo
95
repocfg.Cache = shared
133728e barerepo 1mo
96
thread.Cache = shared
133728e barerepo 1mo
97
httpd.Cache = shared
133728e barerepo 1mo
98
133728e barerepo 1mo
99
srv := &httpd.Server{Cfg: cfg, DB: db,
133728e barerepo 1mo
100
Transport: &transport.Server{Cfg: cfg, DB: db, Bin: bin}}
133728e barerepo 1mo
101
ts := httptest.NewServer(srv.Handler())
133728e barerepo 1mo
102
tb.Cleanup(ts.Close)
133728e barerepo 1mo
103
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
tb.Fatal(err)
133728e barerepo 1mo
110
}
133728e barerepo 1mo
111
return &Instance{URL: ts.URL, Cfg: cfg, DB: db}
133728e barerepo 1mo
112
}
133728e barerepo 1mo
113
133728e barerepo 1mo
114
// Account makes an account with a fresh key and hands back a token for git over https.
133728e barerepo 1mo
115
func (in *Instance) Account(tb testing.TB, name string) string {
133728e barerepo 1mo
116
tb.Helper()
133728e barerepo 1mo
117
dir := tb.TempDir()
133728e barerepo 1mo
118
key := filepath.Join(dir, "id")
133728e barerepo 1mo
119
if out, err := exec.Command("ssh-keygen", "-t", "ed25519", "-N", "", "-C", name,
133728e barerepo 1mo
120
"-f", key, "-q").CombinedOutput(); err != nil {
133728e barerepo 1mo
121
tb.Fatalf("ssh-keygen: %v\n%s", err, out)
133728e barerepo 1mo
122
}
133728e barerepo 1mo
123
pub, err := os.ReadFile(key + ".pub")
133728e barerepo 1mo
124
if err != nil {
133728e barerepo 1mo
125
tb.Fatal(err)
133728e barerepo 1mo
126
}
133728e barerepo 1mo
127
if _, err := in.DB.CreateAccount(context.Background(), name, string(pub), false); err != nil {
133728e barerepo 1mo
128
tb.Fatal(err)
133728e barerepo 1mo
129
}
133728e barerepo 1mo
130
tok, _, err := in.DB.CreateToken(context.Background(), token.Git, name, "", "test")
133728e barerepo 1mo
131
if err != nil {
133728e barerepo 1mo
132
tb.Fatal(err)
133728e barerepo 1mo
133
}
133728e barerepo 1mo
134
return tok
133728e barerepo 1mo
135
}
133728e barerepo 1mo
136
133728e barerepo 1mo
137
// RunnerToken issues a token scoped to one repository, which is what chapter 15 requires.
133728e barerepo 1mo
138
func (in *Instance) RunnerToken(tb testing.TB, account, owner, name string) string {
133728e barerepo 1mo
139
tb.Helper()
133728e barerepo 1mo
140
tok, _, err := in.DB.CreateToken(context.Background(), token.Runner, account,
133728e barerepo 1mo
141
owner+"/"+name, "test runner")
133728e barerepo 1mo
142
if err != nil {
133728e barerepo 1mo
143
tb.Fatal(err)
133728e barerepo 1mo
144
}
133728e barerepo 1mo
145
return tok
133728e barerepo 1mo
146
}
133728e barerepo 1mo
147
133728e barerepo 1mo
148
// Repo pushes a first commit, which is how a repository comes into being here.
133728e barerepo 1mo
149
func (in *Instance) Repo(tb testing.TB, tok, owner, name string) string {
133728e barerepo 1mo
150
tb.Helper()
133728e barerepo 1mo
151
work := tb.TempDir()
133728e barerepo 1mo
152
write := func(path, body string) {
133728e barerepo 1mo
153
full := filepath.Join(work, path)
133728e barerepo 1mo
154
if err := os.MkdirAll(filepath.Dir(full), 0o750); err != nil {
133728e barerepo 1mo
155
tb.Fatal(err)
133728e barerepo 1mo
156
}
133728e barerepo 1mo
157
if err := os.WriteFile(full, []byte(body), 0o600); err != nil {
133728e barerepo 1mo
158
tb.Fatal(err)
133728e barerepo 1mo
159
}
133728e barerepo 1mo
160
}
133728e barerepo 1mo
161
write("config.go", "package main\n")
133728e barerepo 1mo
162
write(".barerepo/config", "[repo]\nvisibility = \"public\"\n")
133728e barerepo 1mo
163
for _, args := range [][]string{
133728e barerepo 1mo
164
{"init", "-q", "-b", "master"},
133728e barerepo 1mo
165
{"add", "-A"},
133728e barerepo 1mo
166
{"commit", "-qm", "first"},
133728e barerepo 1mo
167
{"push", "-q", in.URLFor(tok, "/"+owner+"/"+name), "master"},
133728e barerepo 1mo
168
} {
133728e barerepo 1mo
169
cmd := exec.Command("git", args...)
133728e barerepo 1mo
170
cmd.Dir = work
133728e barerepo 1mo
171
cmd.Env = append(os.Environ(),
133728e barerepo 1mo
172
"GIT_AUTHOR_NAME=tester", "GIT_AUTHOR_EMAIL=t@x",
133728e barerepo 1mo
173
"GIT_COMMITTER_NAME=tester", "GIT_COMMITTER_EMAIL=t@x",
133728e barerepo 1mo
174
"GIT_TERMINAL_PROMPT=0", "GIT_CONFIG_COUNT=1",
133728e barerepo 1mo
175
"GIT_CONFIG_KEY_0=credential.helper", "GIT_CONFIG_VALUE_0=")
133728e barerepo 1mo
176
if out, err := cmd.CombinedOutput(); err != nil {
133728e barerepo 1mo
177
tb.Fatalf("git %s: %v\n%s", strings.Join(args, " "), err, out)
133728e barerepo 1mo
178
}
133728e barerepo 1mo
179
}
133728e barerepo 1mo
180
return work
133728e barerepo 1mo
181
}
133728e barerepo 1mo
182
133728e barerepo 1mo
183
// URLFor is the clone url with a token in it, which is how a test pushes without an agent.
133728e barerepo 1mo
184
func (in *Instance) URLFor(tok, path string) string {
133728e barerepo 1mo
185
return strings.Replace(in.URL, "://", "://x:"+tok+"@", 1) + path
133728e barerepo 1mo
186
}
history · rawbarerepo 0.1.0