Skip to content

Commit 9a95191

Browse files
committed
integration: add 'gok update' test
1 parent 5675dbb commit 9a95191

File tree

7 files changed

+215
-11
lines changed

7 files changed

+215
-11
lines changed
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
package gokupdate_test
2+
3+
import (
4+
"context"
5+
"crypto/sha256"
6+
"encoding/json"
7+
"fmt"
8+
"hash"
9+
"hash/crc32"
10+
"io"
11+
"log"
12+
"net/http"
13+
"net/http/httptest"
14+
"net/url"
15+
"os"
16+
"path/filepath"
17+
"runtime"
18+
"strings"
19+
"testing"
20+
"time"
21+
22+
"github.com/gokrazy/internal/config"
23+
"github.com/gokrazy/tools/gok"
24+
"github.com/gokrazy/tools/internal/packer"
25+
)
26+
27+
type gokrazyTestInstance struct {
28+
configDir string
29+
}
30+
31+
func (inst *gokrazyTestInstance) writeConfig(t *testing.T, basename, content string) {
32+
t.Helper()
33+
fn := filepath.Join(inst.configDir, basename)
34+
if err := os.WriteFile(fn, []byte(content), 0600); err != nil {
35+
t.Fatal(err)
36+
}
37+
}
38+
39+
func writeGokrazyInstance(t *testing.T) *gokrazyTestInstance {
40+
t.Helper()
41+
42+
// Redirect os.UserConfigDir() to a temporary directory under our
43+
// control. gokrazy always uses a path under os.UserConfigDir().
44+
var configDir string
45+
switch runtime.GOOS {
46+
case "linux":
47+
configHomeDir := t.TempDir()
48+
os.Setenv("XDG_CONFIG_HOME", configHomeDir)
49+
// where linux looks:
50+
configDir = filepath.Join(configHomeDir, "gokrazy")
51+
52+
case "darwin":
53+
homeDir := t.TempDir()
54+
os.Setenv("HOME", homeDir)
55+
// where darwin looks:
56+
configDir = filepath.Join(homeDir, "Library", "Application Support", "gokrazy")
57+
58+
default:
59+
t.Fatalf("GOOS=%s unsupported", runtime.GOOS)
60+
}
61+
62+
if err := os.MkdirAll(configDir, 0755); err != nil {
63+
t.Fatal(err)
64+
}
65+
66+
return &gokrazyTestInstance{
67+
configDir: configDir,
68+
}
69+
}
70+
71+
func TestGokUpdate(t *testing.T) {
72+
// Run this whole test in a throw-away temporary directory to not litter the
73+
// gokrazy/tools repository working copy.
74+
t.Chdir(t.TempDir())
75+
76+
_ = writeGokrazyInstance(t)
77+
78+
// TODO: run the gokrazy instance in a VM instead of providing a fake
79+
// implementation of the update protocol.
80+
mux := http.NewServeMux()
81+
mux.HandleFunc("/update/features", func(w http.ResponseWriter, r *http.Request) {
82+
http.Error(w, "not found", http.StatusNotFound)
83+
})
84+
mux.HandleFunc("/update/", func(w http.ResponseWriter, r *http.Request) {
85+
// accept whatever for now.
86+
var hash hash.Hash
87+
switch r.Header.Get("X-Gokrazy-Update-Hash") {
88+
case "crc32":
89+
hash = crc32.NewIEEE()
90+
default:
91+
hash = sha256.New()
92+
}
93+
if _, err := io.Copy(hash, r.Body); err != nil {
94+
http.Error(w, err.Error(), http.StatusInternalServerError)
95+
return
96+
}
97+
fmt.Fprintf(w, "%x", hash.Sum(nil))
98+
})
99+
mux.HandleFunc("/reboot", func(w http.ResponseWriter, r *http.Request) {
100+
// you got it, boss!
101+
})
102+
mux.HandleFunc("/uploadtemp/", func(w http.ResponseWriter, r *http.Request) {
103+
log.Printf("[HTTP] uploadtemp: %s", r.URL.Path)
104+
})
105+
mux.HandleFunc("/divert", func(w http.ResponseWriter, r *http.Request) {
106+
log.Printf("[HTTP] divert: %s to %s",
107+
r.FormValue("path"),
108+
r.FormValue("diversion"))
109+
})
110+
mux.HandleFunc("/log", func(w http.ResponseWriter, r *http.Request) {
111+
log.Printf("[HTTP] log: %s", r.FormValue("path"))
112+
w.Header().Set("Content-type", "text/event-stream")
113+
if r.FormValue("stream") == "stdout" {
114+
const text = "Hello Sun"
115+
line := fmt.Sprintf("data: %s\n", text)
116+
if _, err := fmt.Fprintln(w, line); err != nil {
117+
return
118+
}
119+
}
120+
if f, ok := w.(http.Flusher); ok {
121+
f.Flush()
122+
}
123+
select {}
124+
})
125+
fakeBuildTimestamp := "fake-" + time.Now().Format(time.RFC3339)
126+
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
127+
if strings.Contains(strings.ToLower(r.Header.Get("Accept")), "application/json") {
128+
status := struct {
129+
BuildTimestamp string `json:"BuildTimestamp"`
130+
}{
131+
BuildTimestamp: fakeBuildTimestamp,
132+
}
133+
b, err := json.Marshal(&status)
134+
if err != nil {
135+
http.Error(w, err.Error(), http.StatusInternalServerError)
136+
return
137+
}
138+
w.Header().Set("Content-Type", "application/json")
139+
w.Write(b)
140+
return
141+
}
142+
http.Error(w, "handler not implemented", http.StatusNotImplemented)
143+
})
144+
srv := httptest.NewServer(mux)
145+
u, err := url.Parse(srv.URL)
146+
if err != nil {
147+
t.Fatal(err)
148+
}
149+
150+
// create a new instance
151+
c := gok.Context{
152+
Args: []string{
153+
"--parent_dir", "gokrazy",
154+
"-i", "hello",
155+
"new",
156+
},
157+
}
158+
t.Logf("running %q", append([]string{"<gok>"}, c.Args...))
159+
if err := c.Execute(context.Background()); err != nil {
160+
t.Fatalf("%v: %v", c.Args, err)
161+
}
162+
163+
// update the instance config to speak to the test server
164+
const configPath = "gokrazy/hello/config.json"
165+
b, err := os.ReadFile(configPath)
166+
if err != nil {
167+
t.Fatal(err)
168+
}
169+
var cfg config.Struct
170+
if err := json.Unmarshal(b, &cfg); err != nil {
171+
t.Fatal(err)
172+
}
173+
cfg.Update.Hostname = "localhost"
174+
cfg.Update.HTTPPort = u.Port()
175+
t.Logf("Updated cfg.Update = %+v", cfg.Update)
176+
b, err = cfg.FormatForFile()
177+
if err != nil {
178+
t.Fatal(err)
179+
}
180+
if err := os.WriteFile(configPath, b, 0644); err != nil {
181+
t.Fatal(err)
182+
}
183+
184+
// verify overwrite works (i.e. locates extrafiles)
185+
ctx := context.WithValue(context.Background(), packer.BuildTimestampOverride, fakeBuildTimestamp)
186+
c = gok.Context{
187+
Args: []string{
188+
"--parent_dir", "gokrazy",
189+
"-i", "hello",
190+
"update",
191+
},
192+
}
193+
t.Logf("running %q", append([]string{"<gok>"}, c.Args...))
194+
if err := c.Execute(ctx); err != nil {
195+
t.Fatalf("%v: %v", c.Args, err)
196+
}
197+
}

internal/gok/overwrite.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ func (r *overwriteImplConfig) run(ctx context.Context, args []string, stdout, st
151151
Output: &output,
152152
}
153153

154-
pack.Main("gokrazy gok")
154+
pack.Main(ctx, "gokrazy gok")
155155

156156
return nil
157157
}

internal/gok/sbom.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ func (r *sbomConfig) run(ctx context.Context, args []string, stdout, stderr io.W
8080
Cfg: cfg,
8181
}
8282

83-
sbomMarshaled, sbomWithHash, err := pack.GenerateSBOM()
83+
sbomMarshaled, sbomWithHash, err := pack.GenerateSBOM(ctx)
8484
if os.IsNotExist(err) {
8585
// Common case, handle with a good error message
8686
os.Stderr.WriteString("\n")

internal/gok/update.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ func (r *updateImplConfig) run(ctx context.Context, args []string, stdout, stder
9090
Cfg: cfg,
9191
}
9292

93-
pack.Main("gokrazy gok")
93+
pack.Main(ctx, "gokrazy gok")
9494

9595
return nil
9696
}

internal/gok/vmrun.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ func (r *vmRunConfig) buildFullDiskImage(ctx context.Context, dest string, fileC
133133
Output: &output,
134134
}
135135

136-
pack.Main("gokrazy gok")
136+
pack.Main(ctx, "gokrazy gok")
137137

138138
return nil
139139
}

internal/packer/packer.go

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ import (
3737
"github.com/gokrazy/updater"
3838
)
3939

40+
type contextKey int
41+
42+
var BuildTimestampOverride contextKey
43+
4044
const MB = 1024 * 1024
4145

4246
type filePathAndModTime struct {
@@ -1099,7 +1103,7 @@ func filterGoEnv(env []string) []string {
10991103
return relevant
11001104
}
11011105

1102-
func (pack *Pack) logicPrepare(programName string, sbomHook func(marshaled []byte, withHash SBOMWithHash)) error {
1106+
func (pack *Pack) logicPrepare(ctx context.Context, programName string, sbomHook func(marshaled []byte, withHash SBOMWithHash)) error {
11031107
log := pack.Env.Logger()
11041108
cfg := pack.Cfg
11051109
updateflag.SetUpdate(cfg.InternalCompatibilityFlags.Update)
@@ -1178,6 +1182,9 @@ func (pack *Pack) logicPrepare(programName string, sbomHook func(marshaled []byt
11781182
log.Printf("Build target: %s", strings.Join(filterGoEnv(packer.Env()), " "))
11791183

11801184
pack.buildTimestamp = time.Now().Format(time.RFC3339)
1185+
if ts, ok := ctx.Value(BuildTimestampOverride).(string); ok {
1186+
pack.buildTimestamp = ts
1187+
}
11811188
log.Printf("Build timestamp: %s", pack.buildTimestamp)
11821189

11831190
systemCertsPEM, err := pack.findSystemCertsPEM()
@@ -1630,7 +1637,7 @@ func (pack *Pack) logicBuild(programName string, sbomHook func(marshaled []byte,
16301637
return nil
16311638
}
16321639

1633-
func (pack *Pack) logic(programName string, sbomHook func(marshaled []byte, withHash SBOMWithHash)) error {
1640+
func (pack *Pack) logic(ctx context.Context, programName string, sbomHook func(marshaled []byte, withHash SBOMWithHash)) error {
16341641
dnsCheck := make(chan error)
16351642
go func() {
16361643
defer close(dnsCheck)
@@ -1646,7 +1653,7 @@ func (pack *Pack) logic(programName string, sbomHook func(marshaled []byte, with
16461653
dnsCheck <- nil
16471654
}()
16481655

1649-
if err := pack.logicPrepare(programName, sbomHook); err != nil {
1656+
if err := pack.logicPrepare(ctx, programName, sbomHook); err != nil {
16501657
return err
16511658
}
16521659

@@ -2186,17 +2193,17 @@ func (pack *Pack) updateWithProgress(prog *progress.Reporter, reader io.Reader,
21862193
return nil
21872194
}
21882195

2189-
func (pack *Pack) Main(programName string) {
2190-
if err := pack.logic(programName, nil); err != nil {
2196+
func (pack *Pack) Main(ctx context.Context, programName string) {
2197+
if err := pack.logic(ctx, programName, nil); err != nil {
21912198
fmt.Fprintf(os.Stderr, "ERROR:\n %s\n", err)
21922199
os.Exit(1)
21932200
}
21942201
}
21952202

2196-
func (pack *Pack) GenerateSBOM() ([]byte, SBOMWithHash, error) {
2203+
func (pack *Pack) GenerateSBOM(ctx context.Context) ([]byte, SBOMWithHash, error) {
21972204
var sbom []byte
21982205
var sbomWithHash SBOMWithHash
2199-
if err := pack.logic("gokrazy gok", func(b []byte, wh SBOMWithHash) {
2206+
if err := pack.logic(ctx, "gokrazy gok", func(b []byte, wh SBOMWithHash) {
22002207
sbom = b
22012208
sbomWithHash = wh
22022209
}); err != nil {

0 commit comments

Comments
 (0)