blob: 9e39e9c776447033d6da00e063b6186e5ff15f75 [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"
10 "strings"
11 "testing"
12 "testing/fstest"
13
Josh Bleecher Snyder4d5e9972025-05-01 15:56:37 -070014 gcmp "github.com/google/go-cmp/cmp"
Earl Lee2e463fb2025-04-17 11:22:22 -070015 "sketch.dev/httprr"
16)
17
18var flagRewriteWant = flag.Bool("rewritewant", false, "rewrite the dockerfiles we want from the model")
19
20func TestCreateDockerfile(t *testing.T) {
21 ctx := context.Background()
22
23 tests := []struct {
24 name string
25 fsys fs.FS
26 }{
27 {
28 name: "Basic repo with README",
29 fsys: fstest.MapFS{
30 "README.md": &fstest.MapFile{Data: []byte("# Test Project\nA Go project for testing.")},
31 },
32 },
33 {
34 // TODO: this looks bogus.
35 name: "Repo with README and workflow",
36 fsys: fstest.MapFS{
37 "README.md": &fstest.MapFile{Data: []byte("# Test Project\nA Go project for testing.")},
38 ".github/workflows/test.yml": &fstest.MapFile{Data: []byte(`name: Test
39on: [push]
40jobs:
41 test:
42 runs-on: ubuntu-latest
43 steps:
44 - uses: actions/checkout@v2
45 - uses: actions/setup-node@v3
46 with:
47 node-version: '18'
48 - name: Install and activate corepack
49 run: |
50 npm install -g corepack
51 corepack enable
52 - run: go test ./...`)},
53 },
54 },
55 {
56 name: "mention a devtool in the readme",
57 fsys: fstest.MapFS{
58 "readme.md": &fstest.MapFile{Data: []byte("# Test Project\nYou must install `dot` to run the tests.")},
59 },
60 },
61 {
62 name: "empty repo",
63 fsys: fstest.MapFS{
64 "main.go": &fstest.MapFile{Data: []byte("package main\n\nfunc main() {}")},
65 },
66 },
67 {
68 name: "python misery",
69 fsys: fstest.MapFS{
70 "README.md": &fstest.MapFile{Data: []byte("# Our amazing repo\n\nTo use this project you need python 3.11 and the dvc tool")},
71 },
72 },
73 }
74
75 for _, tt := range tests {
76 t.Run(tt.name, func(t *testing.T) {
77 basePath := "testdata/" + strings.ToLower(strings.Replace(t.Name(), "/", "_", -1))
78 rrPath := basePath + ".httprr"
79 rr, err := httprr.Open(rrPath, http.DefaultTransport)
80 if err != nil && !os.IsNotExist(err) {
81 t.Fatal(err)
82 }
83 rr.ScrubReq(func(req *http.Request) error {
84 req.Header.Del("x-api-key")
85 return nil
86 })
87 initFiles, err := readInitFiles(tt.fsys)
88 if err != nil {
89 t.Fatal(err)
90 }
Josh Bleecher Snyder4d5e9972025-05-01 15:56:37 -070091 apiKey := cmp.Or(os.Getenv("OUTER_SKETCH_ANTHROPIC_API_KEY"), os.Getenv("ANTHROPIC_API_KEY"))
92 result, err := createDockerfile(ctx, rr.Client(), "", apiKey, initFiles, "")
Earl Lee2e463fb2025-04-17 11:22:22 -070093 if err != nil {
94 t.Fatal(err)
95 }
96
97 wantPath := basePath + ".dockerfile"
98
99 if *flagRewriteWant {
100 if err := os.WriteFile(wantPath, []byte(result), 0o666); err != nil {
101 t.Fatal(err)
102 }
103 return
104 }
105
106 wantBytes, err := os.ReadFile(wantPath)
107 if err != nil {
108 t.Fatal(err)
109 }
110 want := string(wantBytes)
Josh Bleecher Snyder4d5e9972025-05-01 15:56:37 -0700111 if diff := gcmp.Diff(want, result); diff != "" {
Earl Lee2e463fb2025-04-17 11:22:22 -0700112 t.Errorf("dockerfile does not match. got:\n----\n%s\n----\n\ndiff: %s", result, diff)
113 }
114 })
115 }
116}
117
118func TestReadInitFiles(t *testing.T) {
119 testFS := fstest.MapFS{
120 "README.md": &fstest.MapFile{Data: []byte("# Test Repo")},
121 ".github/workflows/test.yml": &fstest.MapFile{Data: []byte("name: Test Workflow")},
122 "main.go": &fstest.MapFile{Data: []byte("package main")},
123 ".git/HEAD": &fstest.MapFile{Data: []byte("ref: refs/heads/main")},
124 "random/README.md": &fstest.MapFile{Data: []byte("ignore me")},
125 }
126
127 files, err := readInitFiles(testFS)
128 if err != nil {
129 t.Fatalf("readInitFiles failed: %v", err)
130 }
131
132 // Should have 2 files: README.md and .github/workflows/test.yml
133 if len(files) != 2 {
134 t.Errorf("Expected 2 files, got %d", len(files))
135 }
136
137 if content, ok := files["README.md"]; !ok {
138 t.Error("README.md not found")
139 } else if content != "# Test Repo" {
140 t.Errorf("README.md has incorrect content: %q", content)
141 }
142
143 if content, ok := files[".github/workflows/test.yml"]; !ok {
144 t.Error(".github/workflows/test.yml not found")
145 } else if content != "name: Test Workflow" {
146 t.Errorf("Workflow file has incorrect content: %q", content)
147 }
148
149 if _, ok := files["main.go"]; ok {
150 t.Error("main.go should not be included")
151 }
152
153 if _, ok := files[".git/HEAD"]; ok {
154 t.Error(".git/HEAD should not be included")
155 }
156}
157
158func TestReadInitFilesWithSubdir(t *testing.T) {
159 // Create a file system with files in a subdirectory
160 testFS := fstest.MapFS{
161 "subdir/README.md": &fstest.MapFile{Data: []byte("# Test Repo")},
162 "subdir/.github/workflows/test.yml": &fstest.MapFile{Data: []byte("name: Test Workflow")},
163 "subdir/main.go": &fstest.MapFile{Data: []byte("package main")},
164 }
165
166 // Use fs.Sub to get a sub-filesystem
167 subFS, err := fs.Sub(testFS, "subdir")
168 if err != nil {
169 t.Fatalf("fs.Sub failed: %v", err)
170 }
171
172 files, err := readInitFiles(subFS)
173 if err != nil {
174 t.Fatalf("readInitFiles failed: %v", err)
175 }
176
177 // Should have 2 files: README.md and .github/workflows/test.yml
178 if len(files) != 2 {
179 t.Errorf("Expected 2 files, got %d", len(files))
180 }
181
182 // Verify README.md was found
183 if content, ok := files["README.md"]; !ok {
184 t.Error("README.md not found")
185 } else if content != "# Test Repo" {
186 t.Errorf("README.md has incorrect content: %q", content)
187 }
188
189 // Verify workflow file was found
190 if content, ok := files[".github/workflows/test.yml"]; !ok {
191 t.Error(".github/workflows/test.yml not found")
192 } else if content != "name: Test Workflow" {
193 t.Errorf("Workflow file has incorrect content: %q", content)
194 }
195}
David Crawshaw11129492025-04-25 20:41:53 -0700196
197// TestDockerHashIsPushed ensures that any changes made to the
198// dockerfile template have been pushed to the default image.
199func TestDockerHashIsPushed(t *testing.T) {
David Crawshaw2a5bd6d2025-04-30 14:29:46 -0700200 name, _, tag := DefaultImage()
David Crawshaw11129492025-04-25 20:41:53 -0700201
David Crawshaw2a5bd6d2025-04-30 14:29:46 -0700202 if err := checkTagExists(tag); err != nil {
203 if strings.Contains(err.Error(), "not found") {
204 t.Fatalf(`Currently released docker image %s does not match dockerfileCustomTmpl.
David Crawshaw11129492025-04-25 20:41:53 -0700205
David Crawshaw2a5bd6d2025-04-30 14:29:46 -0700206Inspecting the docker image shows the current hash of dockerfileBase is %s,
207but it is not published in the GitHub container registry.
David Crawshaw11129492025-04-25 20:41:53 -0700208
209This means the template constants in createdockerfile.go have been
David Crawshaw2a5bd6d2025-04-30 14:29:46 -0700210edited (e.g. dockerfileBase changed), but a new version
David Crawshaw11129492025-04-25 20:41:53 -0700211of the public default docker image has not been built and pushed.
212
213To do so:
214
215 go run ./dockerimg/pushdockerimg.go
216
David Crawshaw2a5bd6d2025-04-30 14:29:46 -0700217`, name, tag)
218 } else {
219 t.Fatalf("checkTagExists: %v", err)
220 }
David Crawshaw11129492025-04-25 20:41:53 -0700221 }
222}