Add multi-module release launcher example
This commit is contained in:
247
cmd/releasekit/multi_module_example_test.go
Normal file
247
cmd/releasekit/multi_module_example_test.go
Normal file
@@ -0,0 +1,247 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMultiModuleSelectorMapsSelectorsAndTags(t *testing.T) {
|
||||
example := filepath.Join(cliRepositoryRoot(t), "examples", "multi-module")
|
||||
helper := filepath.Join(example, "select-module.sh")
|
||||
for _, test := range []struct {
|
||||
mode string
|
||||
value string
|
||||
want string
|
||||
}{
|
||||
{mode: "selector", value: "root", want: "root\tv[01].*\t.\treleasekit-root.json"},
|
||||
{mode: "selector", value: "ckconfig", want: "ckconfig\tintegrations/ckconfig/v*\tintegrations/ckconfig\treleasekit-ckconfig.json"},
|
||||
{mode: "tag", value: "integrations/ckconfig/v1.2.3", want: "ckconfig\tintegrations/ckconfig/v*\tintegrations/ckconfig\treleasekit-ckconfig.json"},
|
||||
{mode: "tag", value: "integrations/core/v2.3.4", want: "core-v2\tintegrations/core/v2.*\tintegrations/core/v2\treleasekit-core-v2.json"},
|
||||
{mode: "tag", value: "v1.2.3", want: "root\tv[01].*\t.\treleasekit-root.json"},
|
||||
} {
|
||||
t.Run(test.mode+" "+test.value, func(t *testing.T) {
|
||||
got := runModuleSelector(t, helper, test.mode, test.value, "")
|
||||
if strings.TrimSpace(got) != test.want {
|
||||
t.Fatalf("selector output = %q, want %q", got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
mapPath := filepath.Join(t.TempDir(), "release modules.tsv")
|
||||
if err := os.WriteFile(mapPath, []byte("one\tfeature/*\tmodules/one\tprofiles/one.json\ntwo\tfeature/v1.*\tmodules/two\tprofiles/two.json\n"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if output := runModuleSelectorError(t, helper, "selector", "absent", mapPath); !strings.Contains(output, "no module map row") {
|
||||
t.Fatalf("unknown selector error = %q", output)
|
||||
}
|
||||
if output := runModuleSelectorError(t, helper, "tag", "feature/v1.2.3", mapPath); !strings.Contains(output, "ambiguous") {
|
||||
t.Fatalf("ambiguous event error = %q", output)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(mapPath, []byte("space\tfeature/*\tintegrations/ck config\tprofiles/core release.json\n"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := runModuleSelector(t, helper, "tag", "feature/v1.2.3", mapPath)
|
||||
want := "space\tfeature/*\tintegrations/ck config\tprofiles/core release.json"
|
||||
if strings.TrimSpace(got) != want {
|
||||
t.Fatalf("paths with spaces = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiModuleLauncherUsesSelectedModuleAndProfile(t *testing.T) {
|
||||
example := filepath.Join(cliRepositoryRoot(t), "examples", "multi-module")
|
||||
repository := newCLIRepository(t)
|
||||
copyExampleTree(t, example, repository)
|
||||
|
||||
rootModule := "module example.com/multi-module-consumer\n\ngo 1.25.0\n\nrequire gitea.maximumdirect.net/eric/ReleaseKit v0.2.197\n"
|
||||
selectedModule := "module example.com/multi-module-consumer/integrations/ckconfig\n\ngo 1.25.0\n\nrequire gitea.maximumdirect.net/eric/ReleaseKit v0.2.198\n"
|
||||
writeRepositoryFile(t, repository, "go.mod", rootModule)
|
||||
writeRepositoryFile(t, repository, "integrations/ckconfig/go.mod", selectedModule)
|
||||
spaceModule := "module example.com/multi-module-consumer/integrations/ckconfig\n\ngo 1.25.0\n\nrequire gitea.maximumdirect.net/eric/ReleaseKit v0.2.299\n"
|
||||
writeRepositoryFile(t, repository, "integrations/ck config/go.mod", spaceModule)
|
||||
writeRepositoryFile(t, repository, "profiles/core release.json", `{"product":"Example Consumer Integration","module_dir":"integrations/ck config"}`)
|
||||
cliGitRun(t, repository, "add", ".")
|
||||
cliGitRun(t, repository, "commit", "-m", "prepare multi-module launcher fixture")
|
||||
|
||||
mapPath := filepath.Join(t.TempDir(), "release modules.tsv")
|
||||
if err := os.WriteFile(mapPath, []byte("with space\tfeature/*\tintegrations/ck config\tprofiles/core release.json\n"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
shimDir := t.TempDir()
|
||||
goLog := filepath.Join(t.TempDir(), "go commands")
|
||||
argsLog := filepath.Join(t.TempDir(), "tool arguments")
|
||||
shim := `#!/bin/sh
|
||||
set -eu
|
||||
printf 'cwd=%s args=%s\n' "$PWD" "$*" >> "$RELEASEKIT_TEST_GO_LOG"
|
||||
modfile=
|
||||
output=
|
||||
previous=
|
||||
for argument in "$@"; do
|
||||
case "$argument" in -modfile=*) modfile=${argument#-modfile=} ;; esac
|
||||
if [ "$previous" = -o ]; then output=$argument; fi
|
||||
previous=$argument
|
||||
done
|
||||
if [ -n "$modfile" ]; then cat "$modfile" >> "$RELEASEKIT_TEST_GO_LOG"; fi
|
||||
if [ "$1" = build ]; then
|
||||
cat > "$output" <<'TOOL'
|
||||
#!/bin/sh
|
||||
printf '<%s>\n' "$@" > "$RELEASEKIT_TEST_TOOL_ARGS"
|
||||
TOOL
|
||||
chmod +x "$output"
|
||||
fi
|
||||
`
|
||||
if err := os.WriteFile(filepath.Join(shimDir, "go"), []byte(shim), 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Setenv("PATH", shimDir+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||||
t.Setenv("RELEASEKIT_MODULE_MAP", mapPath)
|
||||
t.Setenv("RELEASEKIT_TEST_GO_LOG", goLog)
|
||||
t.Setenv("RELEASEKIT_TEST_TOOL_ARGS", argsLog)
|
||||
launcher := filepath.Join(repository, "release.sh")
|
||||
command := exec.Command(launcher, "with space", "v1.2.4")
|
||||
var stdout, stderr bytes.Buffer
|
||||
command.Stdout = &stdout
|
||||
command.Stderr = &stderr
|
||||
if err := command.Run(); err != nil {
|
||||
t.Fatalf("launcher error: %v stdout=%q stderr=%q", err, stdout.String(), stderr.String())
|
||||
}
|
||||
|
||||
goCommands, err := os.ReadFile(goLog)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wantModule := filepath.Join(repository, "integrations", "ck config")
|
||||
if !strings.Contains(string(goCommands), "cwd="+wantModule) || !strings.Contains(string(goCommands), "v0.2.299") || strings.Contains(string(goCommands), "v0.2.197") {
|
||||
t.Fatalf("tool bootstrap did not use selected module metadata: %s", goCommands)
|
||||
}
|
||||
toolArgs, err := os.ReadFile(argsLog)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, expected := range []string{"<publish>", "<--repo>", "<" + repository + ">", "<--config>", "<profiles/core release.json>", "<--expected-module>", "<integrations/ck config>", "<v1.2.4>"} {
|
||||
if !strings.Contains(string(toolArgs), expected) {
|
||||
t.Errorf("publication arguments %q omit %q", toolArgs, expected)
|
||||
}
|
||||
}
|
||||
if strings.Contains(string(goCommands), repository+"/.releasekit-bootstrap.") {
|
||||
t.Fatalf("temporary tool metadata was placed inside the repository: %s", goCommands)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiModuleLauncherBuildsToolFromSelectedDependency(t *testing.T) {
|
||||
example := filepath.Join(cliRepositoryRoot(t), "examples", "multi-module")
|
||||
repository := newCLIRepository(t)
|
||||
copyExampleTree(t, example, repository)
|
||||
writeRepositoryFile(t, repository, "go.mod", "module example.com/multi-module-consumer\n\ngo 1.25.0\n\nrequire gitea.maximumdirect.net/eric/ReleaseKit v0.2.197\n")
|
||||
writeRepositoryFile(t, repository, "integrations/ckconfig/go.mod", "module example.com/multi-module-consumer/integrations/ckconfig\n\ngo 1.25.0\n\nrequire gitea.maximumdirect.net/eric/ReleaseKit v0.2.198\n")
|
||||
cliGitRun(t, repository, "add", ".")
|
||||
cliGitRun(t, repository, "commit", "-m", "prepare selected tool module")
|
||||
|
||||
proxy := writeReleaseKitModuleProxy(t)
|
||||
moduleCache := t.TempDir()
|
||||
t.Setenv("GOMODCACHE", moduleCache)
|
||||
t.Cleanup(func() {
|
||||
_ = filepath.WalkDir(moduleCache, func(path string, entry fs.DirEntry, err error) error {
|
||||
if err == nil && entry.IsDir() {
|
||||
_ = os.Chmod(path, 0o700)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
})
|
||||
t.Setenv("GOPROXY", "file://"+filepath.ToSlash(proxy))
|
||||
t.Setenv("GOSUMDB", "off")
|
||||
t.Setenv("GOTOOLCHAIN", "local")
|
||||
moduleBefore, err := os.ReadFile(filepath.Join(repository, "integrations", "ckconfig", "go.mod"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
command := exec.Command(filepath.Join(repository, "release.sh"), "ckconfig", "v1.2.3")
|
||||
output, err := command.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("launcher error: %v output=%q", err, output)
|
||||
}
|
||||
for _, expected := range []string{
|
||||
"tool-version=v0.2.198 helper=tool-only dependency",
|
||||
`"publish"`, `"--repo"`, `"` + repository + `"`,
|
||||
`"--config"`, `"releasekit-ckconfig.json"`,
|
||||
`"--expected-module"`, `"integrations/ckconfig"`, `"v1.2.3"`,
|
||||
} {
|
||||
if !strings.Contains(string(output), expected) {
|
||||
t.Errorf("launcher output %q omits %q", output, expected)
|
||||
}
|
||||
}
|
||||
if strings.Contains(string(output), "tool-version=v0.2.197") {
|
||||
t.Fatalf("launcher built the root-selected ReleaseKit version: %s", output)
|
||||
}
|
||||
moduleAfter, err := os.ReadFile(filepath.Join(repository, "integrations", "ckconfig", "go.mod"))
|
||||
if err != nil || !bytes.Equal(moduleAfter, moduleBefore) {
|
||||
t.Fatalf("selected module go.mod changed: got %q want %q error=%v", moduleAfter, moduleBefore, err)
|
||||
}
|
||||
}
|
||||
|
||||
func runModuleSelector(t *testing.T, helper, mode, value, mapPath string) string {
|
||||
t.Helper()
|
||||
command := exec.Command("sh", helper, mode, value)
|
||||
command.Env = os.Environ()
|
||||
if mapPath != "" {
|
||||
command.Env = append(command.Env, "RELEASEKIT_MODULE_MAP="+mapPath)
|
||||
}
|
||||
output, err := command.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("selector %s %q: %v: %s", mode, value, err, output)
|
||||
}
|
||||
return string(output)
|
||||
}
|
||||
|
||||
func runModuleSelectorError(t *testing.T, helper, mode, value, mapPath string) string {
|
||||
t.Helper()
|
||||
command := exec.Command("sh", helper, mode, value)
|
||||
command.Env = append(os.Environ(), "RELEASEKIT_MODULE_MAP="+mapPath)
|
||||
output, err := command.CombinedOutput()
|
||||
if err == nil {
|
||||
t.Fatalf("selector %s %q unexpectedly succeeded: %s", mode, value, output)
|
||||
}
|
||||
return string(output)
|
||||
}
|
||||
|
||||
func copyExampleTree(t *testing.T, source, destination string) {
|
||||
t.Helper()
|
||||
err := filepath.WalkDir(source, func(path string, entry fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
relative, err := filepath.Rel(source, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if relative == "." {
|
||||
return nil
|
||||
}
|
||||
target := filepath.Join(destination, relative)
|
||||
if entry.IsDir() {
|
||||
return os.MkdirAll(target, 0o700)
|
||||
}
|
||||
contents, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info, err := entry.Info()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mode := info.Mode().Perm()
|
||||
if err := os.WriteFile(target, contents, mode); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Chmod(target, mode)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(fmt.Errorf("copy multi-module example: %w", err))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user