blob: f9155c4b55b7ad3cd88ef9684a5d464ad799f382 [file] [log] [blame]
Earl Lee2e463fb2025-04-17 11:22:22 -07001package dockerimg
2
3import (
4 "context"
David Crawshaw11129492025-04-25 20:41:53 -07005 "encoding/json"
Earl Lee2e463fb2025-04-17 11:22:22 -07006 "flag"
7 "io/fs"
8 "net/http"
9 "os"
David Crawshaw11129492025-04-25 20:41:53 -070010 "os/exec"
Earl Lee2e463fb2025-04-17 11:22:22 -070011 "strings"
12 "testing"
13 "testing/fstest"
14
15 "github.com/google/go-cmp/cmp"
16 "sketch.dev/httprr"
17)
18
19var flagRewriteWant = flag.Bool("rewritewant", false, "rewrite the dockerfiles we want from the model")
20
21func TestCreateDockerfile(t *testing.T) {
22 ctx := context.Background()
23
24 tests := []struct {
25 name string
26 fsys fs.FS
27 }{
28 {
29 name: "Basic repo with README",
30 fsys: fstest.MapFS{
31 "README.md": &fstest.MapFile{Data: []byte("# Test Project\nA Go project for testing.")},
32 },
33 },
34 {
35 // TODO: this looks bogus.
36 name: "Repo with README and workflow",
37 fsys: fstest.MapFS{
38 "README.md": &fstest.MapFile{Data: []byte("# Test Project\nA Go project for testing.")},
39 ".github/workflows/test.yml": &fstest.MapFile{Data: []byte(`name: Test
40on: [push]
41jobs:
42 test:
43 runs-on: ubuntu-latest
44 steps:
45 - uses: actions/checkout@v2
46 - uses: actions/setup-node@v3
47 with:
48 node-version: '18'
49 - name: Install and activate corepack
50 run: |
51 npm install -g corepack
52 corepack enable
53 - run: go test ./...`)},
54 },
55 },
56 {
57 name: "mention a devtool in the readme",
58 fsys: fstest.MapFS{
59 "readme.md": &fstest.MapFile{Data: []byte("# Test Project\nYou must install `dot` to run the tests.")},
60 },
61 },
62 {
63 name: "empty repo",
64 fsys: fstest.MapFS{
65 "main.go": &fstest.MapFile{Data: []byte("package main\n\nfunc main() {}")},
66 },
67 },
68 {
69 name: "python misery",
70 fsys: fstest.MapFS{
71 "README.md": &fstest.MapFile{Data: []byte("# Our amazing repo\n\nTo use this project you need python 3.11 and the dvc tool")},
72 },
73 },
74 }
75
76 for _, tt := range tests {
77 t.Run(tt.name, func(t *testing.T) {
78 basePath := "testdata/" + strings.ToLower(strings.Replace(t.Name(), "/", "_", -1))
79 rrPath := basePath + ".httprr"
80 rr, err := httprr.Open(rrPath, http.DefaultTransport)
81 if err != nil && !os.IsNotExist(err) {
82 t.Fatal(err)
83 }
84 rr.ScrubReq(func(req *http.Request) error {
85 req.Header.Del("x-api-key")
86 return nil
87 })
88 initFiles, err := readInitFiles(tt.fsys)
89 if err != nil {
90 t.Fatal(err)
91 }
92 result, err := createDockerfile(ctx, rr.Client(), "", os.Getenv("ANTHROPIC_API_KEY"), initFiles, "")
93 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)
111 if diff := cmp.Diff(want, result); diff != "" {
112 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) {
200 name, _, hash := DefaultImage()
201
202 cmd := exec.Command("docker", "buildx", "imagetools", "inspect", name, "--raw")
203 out, err := cmd.CombinedOutput()
204 if err != nil {
205 t.Logf("command %v", cmd.Args)
206 t.Fatalf("failed to inspect docker image %s: %v: %s", name, err, out)
207 }
208
209 var config struct {
210 Annotations map[string]string `json:"annotations"`
211 }
212 if err := json.Unmarshal(out, &config); err != nil {
213 t.Fatal(err)
214 }
215 rev := config.Annotations["org.opencontainers.image.revision"]
216 if rev != hash {
217 t.Fatalf(`Currently released docker image %s does not match dockerfileCustomTmpl.
218
219Inspecting the docker image shows revision hash %s,
220but the current hash of dockerfileCustomTmpl is %s.
221
222This means the template constants in createdockerfile.go have been
223edited (e.g. defaultBaseImg or dockerfileBaseTmpl), but a new version
224of the public default docker image has not been built and pushed.
225
226To do so:
227
228 go run ./dockerimg/pushdockerimg.go
229
230`, name, rev, hash)
231 }
232}