| Earl Lee | 2e463fb | 2025-04-17 11:22:22 -0700 | [diff] [blame] | 1 | package dockerimg |
| 2 | |
| 3 | import ( |
| Josh Bleecher Snyder | 1c18ec9 | 2025-07-08 10:55:54 -0700 | [diff] [blame] | 4 | "bytes" |
| Earl Lee | 2e463fb | 2025-04-17 11:22:22 -0700 | [diff] [blame] | 5 | "context" |
| Josh Bleecher Snyder | 1c18ec9 | 2025-07-08 10:55:54 -0700 | [diff] [blame] | 6 | "crypto/sha256" |
| 7 | "encoding/hex" |
| Earl Lee | 2e463fb | 2025-04-17 11:22:22 -0700 | [diff] [blame] | 8 | "os" |
| Josh Bleecher Snyder | fa424f5 | 2025-07-11 18:43:55 +0000 | [diff] [blame] | 9 | "os/exec" |
| Josh Bleecher Snyder | 1c18ec9 | 2025-07-08 10:55:54 -0700 | [diff] [blame] | 10 | "path/filepath" |
| Earl Lee | 2e463fb | 2025-04-17 11:22:22 -0700 | [diff] [blame] | 11 | "testing" |
| Earl Lee | 2e463fb | 2025-04-17 11:22:22 -0700 | [diff] [blame] | 12 | ) |
| 13 | |
| Philip Zeyliger | 983b58a | 2025-07-02 19:42:08 -0700 | [diff] [blame] | 14 | // TestDockerHashIsPushed tests that the published image hash is available |
| David Crawshaw | 1112949 | 2025-04-25 20:41:53 -0700 | [diff] [blame] | 15 | func TestDockerHashIsPushed(t *testing.T) { |
| Philip Zeyliger | 983b58a | 2025-07-02 19:42:08 -0700 | [diff] [blame] | 16 | // Skip this test if we can't reach the internet |
| 17 | if os.Getenv("CI") == "" { |
| 18 | t.Skip("Skipping test that requires internet access") |
| 19 | } |
| David Crawshaw | 1112949 | 2025-04-25 20:41:53 -0700 | [diff] [blame] | 20 | |
| Philip Zeyliger | 983b58a | 2025-07-02 19:42:08 -0700 | [diff] [blame] | 21 | if err := checkTagExists(dockerfileBaseHash()); err != nil { |
| 22 | t.Errorf("Docker image tag %s not found: %v", dockerfileBaseHash(), err) |
| 23 | } |
| David Crawshaw | 1112949 | 2025-04-25 20:41:53 -0700 | [diff] [blame] | 24 | |
| Philip Zeyliger | 983b58a | 2025-07-02 19:42:08 -0700 | [diff] [blame] | 25 | // Test that the default image components are reasonable |
| 26 | name, dockerfile, tag := DefaultImage() |
| 27 | if name == "" { |
| 28 | t.Error("DefaultImage name is empty") |
| 29 | } |
| 30 | if dockerfile == "" { |
| 31 | t.Error("DefaultImage dockerfile is empty") |
| 32 | } |
| 33 | if tag == "" { |
| 34 | t.Error("DefaultImage tag is empty") |
| 35 | } |
| 36 | if len(tag) < 10 { |
| 37 | t.Errorf("DefaultImage tag suspiciously short: %s", tag) |
| David Crawshaw | 1112949 | 2025-04-25 20:41:53 -0700 | [diff] [blame] | 38 | } |
| 39 | } |
| Josh Bleecher Snyder | 3e6a4c4 | 2025-05-23 17:29:57 +0000 | [diff] [blame] | 40 | |
| Philip Zeyliger | 983b58a | 2025-07-02 19:42:08 -0700 | [diff] [blame] | 41 | // TestCreateCacheKey tests the cache key generation |
| 42 | func TestCreateCacheKey(t *testing.T) { |
| 43 | key1 := createCacheKey("image1", "/path1") |
| 44 | key2 := createCacheKey("image2", "/path1") |
| 45 | key3 := createCacheKey("image1", "/path2") |
| 46 | key4 := createCacheKey("image1", "/path1") |
| 47 | |
| 48 | // Different inputs should produce different keys |
| 49 | if key1 == key2 { |
| 50 | t.Error("Different base images should produce different cache keys") |
| Josh Bleecher Snyder | 3e6a4c4 | 2025-05-23 17:29:57 +0000 | [diff] [blame] | 51 | } |
| Philip Zeyliger | 983b58a | 2025-07-02 19:42:08 -0700 | [diff] [blame] | 52 | if key1 == key3 { |
| 53 | t.Error("Different paths should produce different cache keys") |
| 54 | } |
| 55 | |
| 56 | // Same inputs should produce same key |
| 57 | if key1 != key4 { |
| 58 | t.Error("Same inputs should produce same cache key") |
| 59 | } |
| 60 | |
| 61 | // Keys should be reasonably short |
| 62 | if len(key1) != 12 { |
| 63 | t.Errorf("Cache key length should be 12, got %d", len(key1)) |
| Josh Bleecher Snyder | 3e6a4c4 | 2025-05-23 17:29:57 +0000 | [diff] [blame] | 64 | } |
| 65 | } |
| Jon Friesen | d27921f | 2025-06-05 13:15:56 +0000 | [diff] [blame] | 66 | |
| Philip Zeyliger | 983b58a | 2025-07-02 19:42:08 -0700 | [diff] [blame] | 67 | // TestEnsureBaseImageExists tests the base image existence check and pull logic |
| 68 | func TestEnsureBaseImageExists(t *testing.T) { |
| 69 | // This test would require Docker to be running and would make network calls |
| 70 | // So we'll skip it unless we're in an integration test environment |
| 71 | if testing.Short() { |
| 72 | t.Skip("Skipping integration test that requires Docker") |
| Jon Friesen | d27921f | 2025-06-05 13:15:56 +0000 | [diff] [blame] | 73 | } |
| 74 | |
| Philip Zeyliger | 983b58a | 2025-07-02 19:42:08 -0700 | [diff] [blame] | 75 | ctx := context.Background() |
| Jon Friesen | d27921f | 2025-06-05 13:15:56 +0000 | [diff] [blame] | 76 | |
| Philip Zeyliger | 983b58a | 2025-07-02 19:42:08 -0700 | [diff] [blame] | 77 | // Test with a non-existent image (should fail gracefully) |
| 78 | err := ensureBaseImageExists(ctx, "nonexistent/image:tag") |
| 79 | if err == nil { |
| 80 | t.Error("Expected error for nonexistent image, got nil") |
| Philip Zeyliger | 2343f8a | 2025-06-17 06:16:19 -0700 | [diff] [blame] | 81 | } |
| 82 | } |
| Josh Bleecher Snyder | 1c18ec9 | 2025-07-08 10:55:54 -0700 | [diff] [blame] | 83 | |
| 84 | // TestBinaryCaching tests the content-addressable binary caching functionality |
| 85 | func TestBinaryCaching(t *testing.T) { |
| 86 | // Mock the embedded binary |
| 87 | testBinary := []byte("fake binary content for testing") |
| 88 | |
| 89 | // Calculate expected hash |
| 90 | hash := sha256.Sum256(testBinary) |
| 91 | hashHex := hex.EncodeToString(hash[:]) |
| 92 | |
| 93 | // Create a temporary directory for this test |
| 94 | tempDir := t.TempDir() |
| 95 | cacheDir := filepath.Join(tempDir, "sketch-binary-cache") |
| 96 | binaryPath := filepath.Join(cacheDir, hashHex) |
| 97 | |
| 98 | // First, create the cache directory |
| 99 | err := os.MkdirAll(cacheDir, 0o755) |
| 100 | if err != nil { |
| 101 | t.Fatalf("Failed to create cache directory: %v", err) |
| 102 | } |
| 103 | |
| 104 | // Verify the binary doesn't exist initially |
| 105 | if _, err := os.Stat(binaryPath); !os.IsNotExist(err) { |
| 106 | t.Fatalf("Binary should not exist initially, but stat returned: %v", err) |
| 107 | } |
| 108 | |
| 109 | // Write the binary (simulating first time) |
| 110 | err = os.WriteFile(binaryPath, testBinary, 0o700) |
| 111 | if err != nil { |
| 112 | t.Fatalf("Failed to write binary: %v", err) |
| 113 | } |
| 114 | |
| 115 | // Verify the binary now exists and has correct permissions |
| 116 | info, err := os.Stat(binaryPath) |
| 117 | if err != nil { |
| 118 | t.Fatalf("Failed to stat cached binary: %v", err) |
| 119 | } |
| 120 | |
| 121 | if info.Mode().Perm() != 0o700 { |
| 122 | t.Errorf("Expected permissions 0700, got %o", info.Mode().Perm()) |
| 123 | } |
| 124 | |
| 125 | // Verify content matches |
| 126 | cachedContent, err := os.ReadFile(binaryPath) |
| 127 | if err != nil { |
| 128 | t.Fatalf("Failed to read cached binary: %v", err) |
| 129 | } |
| 130 | |
| 131 | if !bytes.Equal(testBinary, cachedContent) { |
| 132 | t.Error("Cached binary content doesn't match original") |
| 133 | } |
| 134 | |
| 135 | // Test that the same hash produces the same path |
| 136 | hash2 := sha256.Sum256(testBinary) |
| 137 | hashHex2 := hex.EncodeToString(hash2[:]) |
| 138 | |
| 139 | if hashHex != hashHex2 { |
| 140 | t.Error("Same content should produce same hash") |
| 141 | } |
| 142 | |
| 143 | // Test that different content produces different hash |
| 144 | differentBinary := []byte("different fake binary content") |
| 145 | differentHash := sha256.Sum256(differentBinary) |
| 146 | differentHashHex := hex.EncodeToString(differentHash[:]) |
| 147 | |
| 148 | if hashHex == differentHashHex { |
| 149 | t.Error("Different content should produce different hash") |
| 150 | } |
| 151 | } |
| Josh Bleecher Snyder | fa424f5 | 2025-07-11 18:43:55 +0000 | [diff] [blame] | 152 | |
| 153 | func TestCollectGoModules(t *testing.T) { |
| 154 | // Create a temporary directory with test files |
| 155 | tempDir := t.TempDir() |
| 156 | |
| 157 | // Initialize a git repository |
| 158 | cmd := exec.Command("git", "init", ".") |
| 159 | cmd.Dir = tempDir |
| 160 | if err := cmd.Run(); err != nil { |
| 161 | t.Fatalf("Failed to init git repo: %v", err) |
| 162 | } |
| 163 | |
| 164 | // Create test go.mod files |
| 165 | modContent := "module test\n\ngo 1.19\n" |
| 166 | sumContent := "example.com/test v1.0.0 h1:abc\n" |
| 167 | |
| 168 | // Root go.mod |
| 169 | if err := os.WriteFile(filepath.Join(tempDir, "go.mod"), []byte(modContent), 0o644); err != nil { |
| 170 | t.Fatalf("Failed to create go.mod: %v", err) |
| 171 | } |
| 172 | if err := os.WriteFile(filepath.Join(tempDir, "go.sum"), []byte(sumContent), 0o644); err != nil { |
| 173 | t.Fatalf("Failed to create go.sum: %v", err) |
| 174 | } |
| 175 | |
| 176 | // Subdirectory go.mod |
| 177 | subDir := filepath.Join(tempDir, "subdir") |
| 178 | if err := os.MkdirAll(subDir, 0o755); err != nil { |
| 179 | t.Fatalf("Failed to create subdir: %v", err) |
| 180 | } |
| 181 | if err := os.WriteFile(filepath.Join(subDir, "go.mod"), []byte(modContent), 0o644); err != nil { |
| 182 | t.Fatalf("Failed to create subdir/go.mod: %v", err) |
| 183 | } |
| 184 | // No go.sum for subdir to test the case where go.sum is missing |
| 185 | |
| 186 | // Add files to git |
| 187 | cmd = exec.Command("git", "add", ".") |
| 188 | cmd.Dir = tempDir |
| 189 | if err := cmd.Run(); err != nil { |
| 190 | t.Fatalf("Failed to add files to git: %v", err) |
| 191 | } |
| 192 | |
| 193 | // Configure git user for the test repo |
| 194 | cmd = exec.Command("git", "config", "user.email", "test@example.com") |
| 195 | cmd.Dir = tempDir |
| 196 | if err := cmd.Run(); err != nil { |
| 197 | t.Fatalf("Failed to set git user email: %v", err) |
| 198 | } |
| 199 | cmd = exec.Command("git", "config", "user.name", "Test User") |
| 200 | cmd.Dir = tempDir |
| 201 | if err := cmd.Run(); err != nil { |
| 202 | t.Fatalf("Failed to set git user name: %v", err) |
| 203 | } |
| 204 | |
| 205 | // Commit the files |
| 206 | cmd = exec.Command("git", "commit", "-m", "test commit") |
| 207 | cmd.Dir = tempDir |
| 208 | if err := cmd.Run(); err != nil { |
| 209 | t.Fatalf("Failed to commit files: %v", err) |
| 210 | } |
| 211 | |
| 212 | // Collect go modules |
| 213 | ctx := context.Background() |
| 214 | modules, err := collectGoModules(ctx, tempDir) |
| 215 | if err != nil { |
| 216 | t.Fatalf("collectGoModules failed: %v", err) |
| 217 | } |
| 218 | |
| 219 | // Verify results |
| 220 | if len(modules) != 2 { |
| 221 | t.Fatalf("Expected 2 modules, got %d", len(modules)) |
| 222 | } |
| 223 | |
| 224 | // Check root module |
| 225 | root := modules[0] |
| 226 | if root.modPath != "go.mod" { |
| 227 | t.Errorf("Expected root modPath to be 'go.mod', got %s", root.modPath) |
| 228 | } |
| 229 | if root.modSHA == "" { |
| 230 | t.Errorf("Expected root modSHA to be non-empty") |
| 231 | } |
| 232 | if root.sumSHA == "" { |
| 233 | t.Errorf("Expected root sumSHA to be non-empty") |
| 234 | } |
| 235 | |
| 236 | // Check subdir module |
| 237 | sub := modules[1] |
| 238 | if sub.modPath != "subdir/go.mod" { |
| 239 | t.Errorf("Expected subdir modPath to be 'subdir/go.mod', got %s", sub.modPath) |
| 240 | } |
| 241 | if sub.modSHA == "" { |
| 242 | t.Errorf("Expected subdir modSHA to be non-empty") |
| 243 | } |
| 244 | if sub.sumSHA != "" { |
| 245 | t.Errorf("Expected subdir sumSHA to be empty, got %s", sub.sumSHA) |
| 246 | } |
| 247 | } |
| 248 | |
| 249 | func TestCollectGoModulesNoModFiles(t *testing.T) { |
| 250 | // Create a temporary directory with no go.mod files |
| 251 | tempDir := t.TempDir() |
| 252 | |
| 253 | // Initialize a git repository |
| 254 | cmd := exec.Command("git", "init", ".") |
| 255 | cmd.Dir = tempDir |
| 256 | if err := cmd.Run(); err != nil { |
| 257 | t.Fatalf("Failed to init git repo: %v", err) |
| 258 | } |
| 259 | |
| 260 | // Create a non-go.mod file |
| 261 | if err := os.WriteFile(filepath.Join(tempDir, "README.md"), []byte("# Test"), 0o644); err != nil { |
| 262 | t.Fatalf("Failed to create README.md: %v", err) |
| 263 | } |
| 264 | |
| 265 | // Add files to git |
| 266 | cmd = exec.Command("git", "add", ".") |
| 267 | cmd.Dir = tempDir |
| 268 | if err := cmd.Run(); err != nil { |
| 269 | t.Fatalf("Failed to add files to git: %v", err) |
| 270 | } |
| 271 | |
| 272 | // Configure git user for the test repo |
| 273 | cmd = exec.Command("git", "config", "user.email", "test@example.com") |
| 274 | cmd.Dir = tempDir |
| 275 | if err := cmd.Run(); err != nil { |
| 276 | t.Fatalf("Failed to set git user email: %v", err) |
| 277 | } |
| 278 | cmd = exec.Command("git", "config", "user.name", "Test User") |
| 279 | cmd.Dir = tempDir |
| 280 | if err := cmd.Run(); err != nil { |
| 281 | t.Fatalf("Failed to set git user name: %v", err) |
| 282 | } |
| 283 | |
| 284 | // Commit the files |
| 285 | cmd = exec.Command("git", "commit", "-m", "test commit") |
| 286 | cmd.Dir = tempDir |
| 287 | if err := cmd.Run(); err != nil { |
| 288 | t.Fatalf("Failed to commit files: %v", err) |
| 289 | } |
| 290 | |
| 291 | // Collect go modules |
| 292 | ctx := context.Background() |
| 293 | modules, err := collectGoModules(ctx, tempDir) |
| 294 | if err != nil { |
| 295 | t.Fatalf("collectGoModules failed: %v", err) |
| 296 | } |
| 297 | |
| 298 | // Verify no modules found |
| 299 | if len(modules) != 0 { |
| 300 | t.Fatalf("Expected 0 modules, got %d", len(modules)) |
| 301 | } |
| 302 | } |