Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ dockerfilegraph
# dockerfilegraph
Dockerfile.*
!/Dockerfile.alpine
!Dockerfile.golden.dot
!examples/dockerfiles/Dockerfile.large
106 changes: 106 additions & 0 deletions internal/cmd/integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package cmd_test

import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"

"github.com/google/go-cmp/cmp"
)

func TestIntegrationCLIGeneratesOutputFile(t *testing.T) {
// Find the project root directory
_, thisFile, _, _ := runtime.Caller(0)
projectRoot := filepath.Clean(filepath.Join(filepath.Dir(thisFile), "../.."))

// Build the Linux binary before building the Docker image
makeCmd := exec.Command("make", "build-linux")
makeCmd.Dir = projectRoot
makeOut, err := makeCmd.CombinedOutput()
if err != nil {
t.Fatalf("make build-linux failed: %v\nOutput:\n%s", err, string(makeOut))
}
binPath := filepath.Join(projectRoot, "dockerfilegraph")
defer func() {
if err := os.Remove(binPath); err != nil && !os.IsNotExist(err) {
t.Errorf("failed to remove built binary %s: %v", binPath, err)
}
}()

// Build the Docker image from the project root
buildCmd := exec.Command("docker", "build", "-t", "dockerfilegraph-test", "-f", "Dockerfile", ".")
buildCmd.Dir = projectRoot
buildOut, err := buildCmd.CombinedOutput()
if err != nil {
t.Fatalf("docker build failed: %v\nOutput:\n%s", err, string(buildOut))
}

// Prepare temp dir for output
tempDir := t.TempDir()
// Copy example Dockerfile to temp dir
dockerfileSrc := filepath.Join(projectRoot, "examples", "dockerfiles", "Dockerfile")
dockerfileDst := filepath.Join(tempDir, "Dockerfile")
content, err := os.ReadFile(dockerfileSrc)
if err != nil {
t.Fatalf("failed to read example Dockerfile from %s: %v", dockerfileSrc, err)
}
if err := os.WriteFile(dockerfileDst, content, 0644); err != nil {
t.Fatalf("failed to write Dockerfile to temp dir %s: %v", dockerfileDst, err)
}

// Run the CLI in Docker to generate Dockerfile.dot
dockerCmd := exec.Command(
"docker", "run", "--rm",
"-u", fmt.Sprintf("%d:%d", os.Getuid(), os.Getgid()),
"-v", tempDir+":/data",
"-w", "/data",
"dockerfilegraph-test",
"--filename", "Dockerfile", "--output", "dot",
)
dockerOut, err := dockerCmd.CombinedOutput()
if err != nil {
t.Fatalf("docker run CLI failed: %v\nOutput:\n%s", err, string(dockerOut))
}

// Read the DOT file generated by the CLI
dotFile := filepath.Join(tempDir, "Dockerfile.dot")
outputBytes, err := os.ReadFile(dotFile)
if err != nil {
t.Fatalf("failed to read generated dot file %s: %v", dotFile, err)
}

checkGoldenFile(t, outputBytes)
}

func checkGoldenFile(t *testing.T, dotBytes []byte) {
_, thisFile, _, _ := runtime.Caller(0)
goldenDir := filepath.Join(filepath.Dir(thisFile), "testdata")
goldenFile := filepath.Join(goldenDir, "Dockerfile.golden.dot")

if _, err := os.Stat(goldenFile); os.IsNotExist(err) {
if err := os.MkdirAll(goldenDir, 0755); err != nil {
t.Fatalf("failed to create testdata dir: %v", err)
}
if err := os.WriteFile(goldenFile, dotBytes, 0644); err != nil {
t.Fatalf("failed to write golden file: %v", err)
}
t.Logf("golden file did not exist, created: %s", goldenFile)
} else {
goldenBytes, err := os.ReadFile(goldenFile)
if err != nil {
t.Fatalf("failed to read golden file: %v", err)
}
diff := cmp.Diff(string(goldenBytes), string(dotBytes))
if diff != "" {
t.Errorf(
"output DOT does not match golden file.\n"+
"To update, delete %s and re-run the test.\n"+
"Diff (-want +got):\n%s",
goldenFile, diff,
)
}
}
}
72 changes: 72 additions & 0 deletions internal/cmd/testdata/Dockerfile.golden.dot
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
digraph G {
graph [bb="0,0,543,252",
compound=true,
nodesep=1.00,
rankdir=LR,
ranksep=0.50
];
node [label="\N"];
external_image_0 [color=grey20,
fontcolor=grey20,
height=0.5,
label="ubuntu:l...887c2c7ac",
pos="86,234",
shape=box,
style="dashed,rounded",
width=2.3056];
stage_0 [height=0.5,
label=ubuntu,
pos="285.5,234",
shape=box,
style=rounded,
width=2];
external_image_0 -> stage_0 [pos="e,213.41,234 169.04,234 180.32,234 191.9,234 203.17,234"];
stage_2 [fillcolor=grey90,
height=0.5,
label=release,
pos="471,126",
shape=box,
style="filled,rounded",
width=2];
stage_0 -> stage_2 [arrowhead=empty,
pos="e,439.19,144.14 317.21,215.92 348.31,197.62 396.52,169.25 430.45,149.28",
style=dashed];
external_image_1 [color=grey20,
fontcolor=grey20,
height=0.5,
label="golang:1...b738433da",
pos="86,126",
shape=box,
style="dashed,rounded",
width=2.3889];
stage_1 [height=0.5,
label="build-tool-depend...",
pos="285.5,126",
shape=box,
style=rounded,
width=2.1528];
external_image_1 -> stage_1 [pos="e,207.99,126 172.19,126 180.65,126 189.25,126 197.73,126"];
stage_1 -> stage_2 [arrowhead=empty,
pos="e,398.82,126 363.26,126 371.64,126 380.19,126 388.62,126",
style=dashed];
external_image_2 [color=grey20,
fontcolor=grey20,
height=0.5,
label=buildcache,
pos="86,18",
shape=box,
style="dashed,rounded",
width=2];
external_image_2 -> stage_1 [arrowhead=ediamond,
pos="e,251.34,107.86 120.06,36.077 153.1,54.144 204.09,82.03 240.53,101.96",
style=dotted];
external_image_3 [color=grey20,
fontcolor=grey20,
height=0.5,
label=scratch,
pos="285.5,18",
shape=box,
style="dashed,rounded",
width=2];
external_image_3 -> stage_2 [pos="e,439.19,107.86 317.21,36.077 348.31,54.378 396.52,82.753 430.45,102.72"];
}