blob: f9155c4b55b7ad3cd88ef9684a5d464ad799f382 [file] [log] [blame]
package dockerimg
import (
"context"
"encoding/json"
"flag"
"io/fs"
"net/http"
"os"
"os/exec"
"strings"
"testing"
"testing/fstest"
"github.com/google/go-cmp/cmp"
"sketch.dev/httprr"
)
var flagRewriteWant = flag.Bool("rewritewant", false, "rewrite the dockerfiles we want from the model")
func TestCreateDockerfile(t *testing.T) {
ctx := context.Background()
tests := []struct {
name string
fsys fs.FS
}{
{
name: "Basic repo with README",
fsys: fstest.MapFS{
"README.md": &fstest.MapFile{Data: []byte("# Test Project\nA Go project for testing.")},
},
},
{
// TODO: this looks bogus.
name: "Repo with README and workflow",
fsys: fstest.MapFS{
"README.md": &fstest.MapFile{Data: []byte("# Test Project\nA Go project for testing.")},
".github/workflows/test.yml": &fstest.MapFile{Data: []byte(`name: Test
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install and activate corepack
run: |
npm install -g corepack
corepack enable
- run: go test ./...`)},
},
},
{
name: "mention a devtool in the readme",
fsys: fstest.MapFS{
"readme.md": &fstest.MapFile{Data: []byte("# Test Project\nYou must install `dot` to run the tests.")},
},
},
{
name: "empty repo",
fsys: fstest.MapFS{
"main.go": &fstest.MapFile{Data: []byte("package main\n\nfunc main() {}")},
},
},
{
name: "python misery",
fsys: fstest.MapFS{
"README.md": &fstest.MapFile{Data: []byte("# Our amazing repo\n\nTo use this project you need python 3.11 and the dvc tool")},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
basePath := "testdata/" + strings.ToLower(strings.Replace(t.Name(), "/", "_", -1))
rrPath := basePath + ".httprr"
rr, err := httprr.Open(rrPath, http.DefaultTransport)
if err != nil && !os.IsNotExist(err) {
t.Fatal(err)
}
rr.ScrubReq(func(req *http.Request) error {
req.Header.Del("x-api-key")
return nil
})
initFiles, err := readInitFiles(tt.fsys)
if err != nil {
t.Fatal(err)
}
result, err := createDockerfile(ctx, rr.Client(), "", os.Getenv("ANTHROPIC_API_KEY"), initFiles, "")
if err != nil {
t.Fatal(err)
}
wantPath := basePath + ".dockerfile"
if *flagRewriteWant {
if err := os.WriteFile(wantPath, []byte(result), 0o666); err != nil {
t.Fatal(err)
}
return
}
wantBytes, err := os.ReadFile(wantPath)
if err != nil {
t.Fatal(err)
}
want := string(wantBytes)
if diff := cmp.Diff(want, result); diff != "" {
t.Errorf("dockerfile does not match. got:\n----\n%s\n----\n\ndiff: %s", result, diff)
}
})
}
}
func TestReadInitFiles(t *testing.T) {
testFS := fstest.MapFS{
"README.md": &fstest.MapFile{Data: []byte("# Test Repo")},
".github/workflows/test.yml": &fstest.MapFile{Data: []byte("name: Test Workflow")},
"main.go": &fstest.MapFile{Data: []byte("package main")},
".git/HEAD": &fstest.MapFile{Data: []byte("ref: refs/heads/main")},
"random/README.md": &fstest.MapFile{Data: []byte("ignore me")},
}
files, err := readInitFiles(testFS)
if err != nil {
t.Fatalf("readInitFiles failed: %v", err)
}
// Should have 2 files: README.md and .github/workflows/test.yml
if len(files) != 2 {
t.Errorf("Expected 2 files, got %d", len(files))
}
if content, ok := files["README.md"]; !ok {
t.Error("README.md not found")
} else if content != "# Test Repo" {
t.Errorf("README.md has incorrect content: %q", content)
}
if content, ok := files[".github/workflows/test.yml"]; !ok {
t.Error(".github/workflows/test.yml not found")
} else if content != "name: Test Workflow" {
t.Errorf("Workflow file has incorrect content: %q", content)
}
if _, ok := files["main.go"]; ok {
t.Error("main.go should not be included")
}
if _, ok := files[".git/HEAD"]; ok {
t.Error(".git/HEAD should not be included")
}
}
func TestReadInitFilesWithSubdir(t *testing.T) {
// Create a file system with files in a subdirectory
testFS := fstest.MapFS{
"subdir/README.md": &fstest.MapFile{Data: []byte("# Test Repo")},
"subdir/.github/workflows/test.yml": &fstest.MapFile{Data: []byte("name: Test Workflow")},
"subdir/main.go": &fstest.MapFile{Data: []byte("package main")},
}
// Use fs.Sub to get a sub-filesystem
subFS, err := fs.Sub(testFS, "subdir")
if err != nil {
t.Fatalf("fs.Sub failed: %v", err)
}
files, err := readInitFiles(subFS)
if err != nil {
t.Fatalf("readInitFiles failed: %v", err)
}
// Should have 2 files: README.md and .github/workflows/test.yml
if len(files) != 2 {
t.Errorf("Expected 2 files, got %d", len(files))
}
// Verify README.md was found
if content, ok := files["README.md"]; !ok {
t.Error("README.md not found")
} else if content != "# Test Repo" {
t.Errorf("README.md has incorrect content: %q", content)
}
// Verify workflow file was found
if content, ok := files[".github/workflows/test.yml"]; !ok {
t.Error(".github/workflows/test.yml not found")
} else if content != "name: Test Workflow" {
t.Errorf("Workflow file has incorrect content: %q", content)
}
}
// TestDockerHashIsPushed ensures that any changes made to the
// dockerfile template have been pushed to the default image.
func TestDockerHashIsPushed(t *testing.T) {
name, _, hash := DefaultImage()
cmd := exec.Command("docker", "buildx", "imagetools", "inspect", name, "--raw")
out, err := cmd.CombinedOutput()
if err != nil {
t.Logf("command %v", cmd.Args)
t.Fatalf("failed to inspect docker image %s: %v: %s", name, err, out)
}
var config struct {
Annotations map[string]string `json:"annotations"`
}
if err := json.Unmarshal(out, &config); err != nil {
t.Fatal(err)
}
rev := config.Annotations["org.opencontainers.image.revision"]
if rev != hash {
t.Fatalf(`Currently released docker image %s does not match dockerfileCustomTmpl.
Inspecting the docker image shows revision hash %s,
but the current hash of dockerfileCustomTmpl is %s.
This means the template constants in createdockerfile.go have been
edited (e.g. defaultBaseImg or dockerfileBaseTmpl), but a new version
of the public default docker image has not been built and pushed.
To do so:
go run ./dockerimg/pushdockerimg.go
`, name, rev, hash)
}
}