Skip to content

Commit f650be8

Browse files
Add archive build for DBR testing (#3452)
## Changes This PR adds an internal program that can build a CLI test archive that contains the entire repo and binaries like uv, go, and jq to run acceptance tests. This PR builds on top of #3444 ## Why A single deployable binary makes testing on DBR very straightforward. All you then need to do is decompile the binary and run tests using a test runner of your choice. For now, the tests will be run using the go binary, but this could be extended to run them using gotestsum. ## Tests A new unit test. This has a build tag so that we can limit its execution to only a DBR CI environment.
1 parent 0a1a309 commit f650be8

File tree

5 files changed

+242
-0
lines changed

5 files changed

+242
-0
lines changed

internal/testarchive/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
_build/
2+
_bin/

internal/testarchive/archive.go

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
package main
2+
3+
import (
4+
"archive/tar"
5+
"bufio"
6+
"compress/gzip"
7+
"fmt"
8+
"io"
9+
"os"
10+
"os/exec"
11+
"path/filepath"
12+
"strings"
13+
)
14+
15+
// gitFiles returns a list of all git-tracked files in the repository.
16+
func gitFiles(repoRoot string) ([]string, error) {
17+
cmd := exec.Command("git", "ls-files")
18+
cmd.Dir = repoRoot
19+
output, err := cmd.Output()
20+
if err != nil {
21+
return nil, fmt.Errorf("failed to get git tracked files: %w", err)
22+
}
23+
24+
scanner := bufio.NewScanner(strings.NewReader(string(output)))
25+
var gitFiles []string
26+
for scanner.Scan() {
27+
file := strings.TrimSpace(scanner.Text())
28+
if file != "" {
29+
gitFiles = append(gitFiles, file)
30+
}
31+
}
32+
33+
if err := scanner.Err(); err != nil {
34+
return nil, fmt.Errorf("error reading git ls-files output: %w", err)
35+
}
36+
37+
return gitFiles, nil
38+
}
39+
40+
func binFiles(binDir string) ([]string, error) {
41+
var binFiles []string
42+
43+
err := filepath.WalkDir(binDir, func(path string, d os.DirEntry, err error) error {
44+
if err != nil {
45+
return err
46+
}
47+
48+
if d.IsDir() {
49+
return nil
50+
}
51+
52+
relPath, err := filepath.Rel(binDir, path)
53+
if err != nil {
54+
return fmt.Errorf("failed to get relative path: %w", err)
55+
}
56+
57+
binFiles = append(binFiles, relPath)
58+
return nil
59+
})
60+
61+
return binFiles, err
62+
}
63+
64+
// addFileToArchive adds a single file to the tar archive
65+
func addFileToArchive(tarWriter *tar.Writer, src, dst string) error {
66+
info, err := os.Stat(src)
67+
if err != nil {
68+
return fmt.Errorf("failed to stat: %w", err)
69+
}
70+
71+
// Skip directories and non-regular files
72+
if !info.Mode().IsRegular() {
73+
return nil
74+
}
75+
76+
header := &tar.Header{
77+
Name: dst,
78+
Size: info.Size(),
79+
Mode: int64(info.Mode()),
80+
ModTime: info.ModTime(),
81+
}
82+
if err := tarWriter.WriteHeader(header); err != nil {
83+
return fmt.Errorf("failed to write header: %w", err)
84+
}
85+
86+
fileReader, err := os.Open(src)
87+
if err != nil {
88+
return fmt.Errorf("failed to open file: %w", err)
89+
}
90+
defer fileReader.Close()
91+
92+
_, err = io.Copy(tarWriter, fileReader)
93+
if err != nil {
94+
return fmt.Errorf("failed to copy file content: %w", err)
95+
}
96+
97+
return nil
98+
}
99+
100+
// createArchive creates a tar.gz archive of all git-tracked files plus downloaded tools
101+
func createArchive(archiveDir, binDir, repoRoot string) error {
102+
archivePath := filepath.Join(archiveDir, "archive.tar.gz")
103+
104+
// Download tools for both arm and amd64 architectures.
105+
// The right architecture to use is decided at runtime on the serverless driver.
106+
// The Databricks platform explicitly does not provide any guarantees around
107+
// the CPU architecture to keep the door open for future optimizations.
108+
downloaders := []downloader{
109+
goDownloader{arch: "amd64", binDir: binDir},
110+
goDownloader{arch: "arm64", binDir: binDir},
111+
uvDownloader{arch: "amd64", binDir: binDir},
112+
uvDownloader{arch: "arm64", binDir: binDir},
113+
jqDownloader{arch: "amd64", binDir: binDir},
114+
jqDownloader{arch: "arm64", binDir: binDir},
115+
}
116+
117+
for _, downloader := range downloaders {
118+
err := downloader.Download()
119+
if err != nil {
120+
return fmt.Errorf("failed to download %s: %w", downloader, err)
121+
}
122+
}
123+
124+
gitFiles, err := gitFiles(repoRoot)
125+
if err != nil {
126+
return fmt.Errorf("failed to get git tracked files: %w", err)
127+
}
128+
129+
binFiles, err := binFiles(binDir)
130+
if err != nil {
131+
return fmt.Errorf("failed to get bin files: %w", err)
132+
}
133+
134+
totalFiles := len(gitFiles) + len(binFiles)
135+
fmt.Printf("Found %d git-tracked files and %d downloaded files (%d total)\n",
136+
len(gitFiles), len(binFiles), totalFiles)
137+
138+
// Create archive directory if it doesn't exist
139+
if err := os.MkdirAll(archiveDir, 0o755); err != nil {
140+
return fmt.Errorf("failed to create output directory: %w", err)
141+
}
142+
143+
// Create the tar.gz file
144+
outFile, err := os.Create(archivePath)
145+
if err != nil {
146+
return fmt.Errorf("failed to create output file: %w", err)
147+
}
148+
defer outFile.Close()
149+
150+
// Create gzip writer
151+
gzWriter := gzip.NewWriter(outFile)
152+
defer gzWriter.Close()
153+
154+
// Create tar.gz writer
155+
tarWriter := tar.NewWriter(gzWriter)
156+
defer tarWriter.Close()
157+
158+
fmt.Printf("Creating archive %s...\n", archivePath)
159+
160+
// Add git-tracked files to the archive
161+
for _, file := range gitFiles {
162+
err := addFileToArchive(tarWriter, filepath.Join(repoRoot, file), filepath.Join("cli", file))
163+
if err != nil {
164+
fmt.Printf("Warning: failed to add git file %s: %v\n", file, err)
165+
}
166+
}
167+
168+
// Add downloaded files / binaries to the archive
169+
for _, file := range binFiles {
170+
err := addFileToArchive(tarWriter, filepath.Join(binDir, file), filepath.Join("bin", file))
171+
if err != nil {
172+
fmt.Printf("Warning: failed to add downloaded file %s: %v\n", file, err)
173+
}
174+
}
175+
176+
stat, err := outFile.Stat()
177+
if err != nil {
178+
return fmt.Errorf("failed to stat archive: %w", err)
179+
}
180+
181+
fmt.Printf("✅ Successfully created comprehensive archive. Archive size: %.1f MB\n", float64(stat.Size())/(1024*1024))
182+
return nil
183+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//go:build dbr_only
2+
3+
package main
4+
5+
import (
6+
"path/filepath"
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func TestArchive(t *testing.T) {
14+
archiveDir := t.TempDir()
15+
binDir := t.TempDir()
16+
repoRoot := "../.."
17+
18+
err := createArchive(archiveDir, binDir, repoRoot)
19+
require.NoError(t, err)
20+
21+
assertDir := t.TempDir()
22+
err = extractTarGz(filepath.Join(archiveDir, "archive.tar.gz"), assertDir)
23+
require.NoError(t, err)
24+
25+
// Go installation is a directory because it includes the
26+
// standard library source code along with the Go binary.
27+
assert.FileExists(t, filepath.Join(assertDir, "bin", "arm64", "go", "bin", "go"))
28+
assert.FileExists(t, filepath.Join(assertDir, "bin", "amd64", "go", "bin", "go"))
29+
assert.FileExists(t, filepath.Join(assertDir, "bin", "arm64", "uv"))
30+
assert.FileExists(t, filepath.Join(assertDir, "bin", "amd64", "uv"))
31+
assert.FileExists(t, filepath.Join(assertDir, "bin", "arm64", "jq"))
32+
assert.FileExists(t, filepath.Join(assertDir, "bin", "amd64", "jq"))
33+
34+
assert.FileExists(t, filepath.Join(assertDir, "cli", "go.mod"))
35+
assert.FileExists(t, filepath.Join(assertDir, "cli", "go.sum"))
36+
}

internal/testarchive/go.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ var (
1818
_ = jqDownloader{}
1919
)
2020

21+
type downloader interface {
22+
Download() error
23+
}
24+
2125
type goDownloader struct {
2226
binDir string
2327
arch string

internal/testarchive/main.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
)
7+
8+
func main() {
9+
// Directories with the _ prefix are ignored by Go. That is important
10+
// since the go installation in _bin would include stdlib go modules which would
11+
// otherwise cause an error during a build of the CLI.
12+
err := createArchive("_build", "_bin", "../..")
13+
if err != nil {
14+
fmt.Fprintf(os.Stderr, "Error creating archive: %v\n", err)
15+
os.Exit(1)
16+
}
17+
}

0 commit comments

Comments
 (0)