blob: b19b6d5fa351090c4d0abd0702e2941ba68fd245 [file] [log] [blame]
Earl Lee2e463fb2025-04-17 11:22:22 -07001package dockerimg
2
3import (
Josh Bleecher Snyder4d5e9972025-05-01 15:56:37 -07004 "cmp"
Earl Lee2e463fb2025-04-17 11:22:22 -07005 "context"
6 "flag"
7 "io/fs"
8 "net/http"
9 "os"
Josh Bleecher Snyder3e6a4c42025-05-23 17:29:57 +000010 "path/filepath"
Earl Lee2e463fb2025-04-17 11:22:22 -070011 "strings"
12 "testing"
13 "testing/fstest"
14
Josh Bleecher Snyder4d5e9972025-05-01 15:56:37 -070015 gcmp "github.com/google/go-cmp/cmp"
Earl Lee2e463fb2025-04-17 11:22:22 -070016 "sketch.dev/httprr"
Josh Bleecher Snyder4f84ab72025-04-22 16:40:54 -070017 "sketch.dev/llm/ant"
Earl Lee2e463fb2025-04-17 11:22:22 -070018)
19
20var flagRewriteWant = flag.Bool("rewritewant", false, "rewrite the dockerfiles we want from the model")
21
22func TestCreateDockerfile(t *testing.T) {
23 ctx := context.Background()
24
25 tests := []struct {
26 name string
27 fsys fs.FS
28 }{
29 {
30 name: "Basic repo with README",
31 fsys: fstest.MapFS{
32 "README.md": &fstest.MapFile{Data: []byte("# Test Project\nA Go project for testing.")},
33 },
34 },
35 {
36 // TODO: this looks bogus.
37 name: "Repo with README and workflow",
38 fsys: fstest.MapFS{
39 "README.md": &fstest.MapFile{Data: []byte("# Test Project\nA Go project for testing.")},
40 ".github/workflows/test.yml": &fstest.MapFile{Data: []byte(`name: Test
41on: [push]
42jobs:
43 test:
44 runs-on: ubuntu-latest
45 steps:
46 - uses: actions/checkout@v2
47 - uses: actions/setup-node@v3
48 with:
49 node-version: '18'
50 - name: Install and activate corepack
51 run: |
52 npm install -g corepack
53 corepack enable
54 - run: go test ./...`)},
55 },
56 },
57 {
58 name: "mention a devtool in the readme",
59 fsys: fstest.MapFS{
60 "readme.md": &fstest.MapFile{Data: []byte("# Test Project\nYou must install `dot` to run the tests.")},
61 },
62 },
63 {
64 name: "empty repo",
65 fsys: fstest.MapFS{
66 "main.go": &fstest.MapFile{Data: []byte("package main\n\nfunc main() {}")},
67 },
68 },
69 {
70 name: "python misery",
71 fsys: fstest.MapFS{
72 "README.md": &fstest.MapFile{Data: []byte("# Our amazing repo\n\nTo use this project you need python 3.11 and the dvc tool")},
73 },
74 },
75 }
76
77 for _, tt := range tests {
78 t.Run(tt.name, func(t *testing.T) {
79 basePath := "testdata/" + strings.ToLower(strings.Replace(t.Name(), "/", "_", -1))
80 rrPath := basePath + ".httprr"
81 rr, err := httprr.Open(rrPath, http.DefaultTransport)
82 if err != nil && !os.IsNotExist(err) {
83 t.Fatal(err)
84 }
85 rr.ScrubReq(func(req *http.Request) error {
86 req.Header.Del("x-api-key")
87 return nil
88 })
89 initFiles, err := readInitFiles(tt.fsys)
90 if err != nil {
91 t.Fatal(err)
92 }
David Crawshaw3659d872025-05-05 17:52:23 -070093 apiKey := cmp.Or(os.Getenv("OUTER_SKETCH_MODEL_API_KEY"), os.Getenv("ANTHROPIC_API_KEY"))
Josh Bleecher Snyder4f84ab72025-04-22 16:40:54 -070094 srv := &ant.Service{
95 APIKey: apiKey,
96 HTTPC: rr.Client(),
97 }
Pokey Rulec31e2962025-05-13 10:53:33 +000098 result, err := createDockerfile(ctx, srv, initFiles, "", false)
Earl Lee2e463fb2025-04-17 11:22:22 -070099 if err != nil {
100 t.Fatal(err)
101 }
102
103 wantPath := basePath + ".dockerfile"
104
105 if *flagRewriteWant {
106 if err := os.WriteFile(wantPath, []byte(result), 0o666); err != nil {
107 t.Fatal(err)
108 }
109 return
110 }
111
112 wantBytes, err := os.ReadFile(wantPath)
113 if err != nil {
114 t.Fatal(err)
115 }
116 want := string(wantBytes)
Josh Bleecher Snyder4d5e9972025-05-01 15:56:37 -0700117 if diff := gcmp.Diff(want, result); diff != "" {
Earl Lee2e463fb2025-04-17 11:22:22 -0700118 t.Errorf("dockerfile does not match. got:\n----\n%s\n----\n\ndiff: %s", result, diff)
119 }
120 })
121 }
122}
123
124func TestReadInitFiles(t *testing.T) {
125 testFS := fstest.MapFS{
126 "README.md": &fstest.MapFile{Data: []byte("# Test Repo")},
127 ".github/workflows/test.yml": &fstest.MapFile{Data: []byte("name: Test Workflow")},
128 "main.go": &fstest.MapFile{Data: []byte("package main")},
129 ".git/HEAD": &fstest.MapFile{Data: []byte("ref: refs/heads/main")},
130 "random/README.md": &fstest.MapFile{Data: []byte("ignore me")},
131 }
132
133 files, err := readInitFiles(testFS)
134 if err != nil {
135 t.Fatalf("readInitFiles failed: %v", err)
136 }
137
138 // Should have 2 files: README.md and .github/workflows/test.yml
139 if len(files) != 2 {
140 t.Errorf("Expected 2 files, got %d", len(files))
141 }
142
143 if content, ok := files["README.md"]; !ok {
144 t.Error("README.md not found")
145 } else if content != "# Test Repo" {
146 t.Errorf("README.md has incorrect content: %q", content)
147 }
148
149 if content, ok := files[".github/workflows/test.yml"]; !ok {
150 t.Error(".github/workflows/test.yml not found")
151 } else if content != "name: Test Workflow" {
152 t.Errorf("Workflow file has incorrect content: %q", content)
153 }
154
155 if _, ok := files["main.go"]; ok {
156 t.Error("main.go should not be included")
157 }
158
159 if _, ok := files[".git/HEAD"]; ok {
160 t.Error(".git/HEAD should not be included")
161 }
162}
163
164func TestReadInitFilesWithSubdir(t *testing.T) {
165 // Create a file system with files in a subdirectory
166 testFS := fstest.MapFS{
167 "subdir/README.md": &fstest.MapFile{Data: []byte("# Test Repo")},
168 "subdir/.github/workflows/test.yml": &fstest.MapFile{Data: []byte("name: Test Workflow")},
169 "subdir/main.go": &fstest.MapFile{Data: []byte("package main")},
170 }
171
172 // Use fs.Sub to get a sub-filesystem
173 subFS, err := fs.Sub(testFS, "subdir")
174 if err != nil {
175 t.Fatalf("fs.Sub failed: %v", err)
176 }
177
178 files, err := readInitFiles(subFS)
179 if err != nil {
180 t.Fatalf("readInitFiles failed: %v", err)
181 }
182
183 // Should have 2 files: README.md and .github/workflows/test.yml
184 if len(files) != 2 {
185 t.Errorf("Expected 2 files, got %d", len(files))
186 }
187
188 // Verify README.md was found
189 if content, ok := files["README.md"]; !ok {
190 t.Error("README.md not found")
191 } else if content != "# Test Repo" {
192 t.Errorf("README.md has incorrect content: %q", content)
193 }
194
195 // Verify workflow file was found
196 if content, ok := files[".github/workflows/test.yml"]; !ok {
197 t.Error(".github/workflows/test.yml not found")
198 } else if content != "name: Test Workflow" {
199 t.Errorf("Workflow file has incorrect content: %q", content)
200 }
201}
David Crawshaw11129492025-04-25 20:41:53 -0700202
203// TestDockerHashIsPushed ensures that any changes made to the
204// dockerfile template have been pushed to the default image.
205func TestDockerHashIsPushed(t *testing.T) {
David Crawshaw2a5bd6d2025-04-30 14:29:46 -0700206 name, _, tag := DefaultImage()
David Crawshaw11129492025-04-25 20:41:53 -0700207
David Crawshaw2a5bd6d2025-04-30 14:29:46 -0700208 if err := checkTagExists(tag); err != nil {
209 if strings.Contains(err.Error(), "not found") {
210 t.Fatalf(`Currently released docker image %s does not match dockerfileCustomTmpl.
David Crawshaw11129492025-04-25 20:41:53 -0700211
David Crawshaw2a5bd6d2025-04-30 14:29:46 -0700212Inspecting the docker image shows the current hash of dockerfileBase is %s,
213but it is not published in the GitHub container registry.
David Crawshaw11129492025-04-25 20:41:53 -0700214
215This means the template constants in createdockerfile.go have been
David Crawshaw2a5bd6d2025-04-30 14:29:46 -0700216edited (e.g. dockerfileBase changed), but a new version
David Crawshaw11129492025-04-25 20:41:53 -0700217of the public default docker image has not been built and pushed.
218
219To do so:
220
221 go run ./dockerimg/pushdockerimg.go
222
David Crawshaw2a5bd6d2025-04-30 14:29:46 -0700223`, name, tag)
224 } else {
225 t.Fatalf("checkTagExists: %v", err)
226 }
David Crawshaw11129492025-04-25 20:41:53 -0700227 }
228}
Josh Bleecher Snyder3e6a4c42025-05-23 17:29:57 +0000229
230func TestGetHostGoCacheDirs(t *testing.T) {
231 ctx := context.Background()
232
233 // Test getHostGoCacheDir
234 goCacheDir, err := getHostGoCacheDir(ctx)
235 if err != nil {
236 t.Fatalf("getHostGoCacheDir failed: %v", err)
237 }
238 if goCacheDir == "" {
239 t.Fatal("getHostGoCacheDir returned empty string")
240 }
241 t.Logf("GOCACHE: %s", goCacheDir)
242
243 // Test getHostGoModCacheDir
244 goModCacheDir, err := getHostGoModCacheDir(ctx)
245 if err != nil {
246 t.Fatalf("getHostGoModCacheDir failed: %v", err)
247 }
248 if goModCacheDir == "" {
249 t.Fatal("getHostGoModCacheDir returned empty string")
250 }
251 t.Logf("GOMODCACHE: %s", goModCacheDir)
252
253 // Both should be absolute paths
254 if !filepath.IsAbs(goCacheDir) {
255 t.Errorf("GOCACHE is not an absolute path: %s", goCacheDir)
256 }
257 if !filepath.IsAbs(goModCacheDir) {
258 t.Errorf("GOMODCACHE is not an absolute path: %s", goModCacheDir)
259 }
260}