Test release wiring by its effective behavior
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
`3c405f0524ec6c098d00d4e371abbace7648d446`.
|
`3c405f0524ec6c098d00d4e371abbace7648d446`.
|
||||||
**Scope:** ReleaseKit adoption commits after `721365e`, including CLI version
|
**Scope:** ReleaseKit adoption commits after `721365e`, including CLI version
|
||||||
reporting, local release wiring, CI delivery, tests, and documentation.
|
reporting, local release wiring, CI delivery, tests, and documentation.
|
||||||
**Status:** R1–R2 addressed; R3 pending. The findings below describe the reviewed
|
**Status:** R1–R3 addressed. The findings below describe the reviewed
|
||||||
baseline; completed work is recorded under Resolution Record.
|
baseline; completed work is recorded under Resolution Record.
|
||||||
|
|
||||||
## Architecture Assessment
|
## Architecture Assessment
|
||||||
@@ -141,3 +141,11 @@ matrix.
|
|||||||
and an unintended target, while accepting equivalent reordered/repeated lists.
|
and an unintended target, while accepting equivalent reordered/repeated lists.
|
||||||
The cases reproduced the faulty membership behavior before the fix;
|
The cases reproduced the faulty membership behavior before the fix;
|
||||||
`go test ./scripts` passed afterward.
|
`go test ./scripts` passed afterward.
|
||||||
|
- **R3:** Wrapper coverage now checks the executable's actual destination, and
|
||||||
|
profile coverage checks the effective additional-check list. Equivalent output
|
||||||
|
paths and an explicit empty list are accepted. Fixture helpers use standard
|
||||||
|
library operations where appropriate. Disposable mutation checks accepted
|
||||||
|
`-o bin/releasekit`, `-o=bin/releasekit`, and `"checks": []`, while detecting a
|
||||||
|
wrong executable destination, changed product, nonempty checks, lost argument
|
||||||
|
boundaries, and lost tool status. The complete `go test ./...` suite passed
|
||||||
|
with all three remediations in place. No new release gates were added.
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -19,10 +21,13 @@ if [[ ${RELEASEKIT_BOOTSTRAP_STATUS:-0} != 0 ]]; then
|
|||||||
fi
|
fi
|
||||||
output=
|
output=
|
||||||
while (($#)); do
|
while (($#)); do
|
||||||
if [[ $1 == -o ]]; then
|
case $1 in
|
||||||
|
-o)
|
||||||
shift
|
shift
|
||||||
output=${1:?missing output path}
|
output=${1:?missing output path}
|
||||||
fi
|
;;
|
||||||
|
-o=*) output=${1#-o=} ;;
|
||||||
|
esac
|
||||||
shift
|
shift
|
||||||
done
|
done
|
||||||
[[ -n $output ]]
|
[[ -n $output ]]
|
||||||
@@ -36,8 +41,8 @@ chmod +x "$output"
|
|||||||
`
|
`
|
||||||
|
|
||||||
func TestReleaseWrapperForwardsNativeArgumentsAndStatus(t *testing.T) {
|
func TestReleaseWrapperForwardsNativeArgumentsAndStatus(t *testing.T) {
|
||||||
for _, toolStatus := range []string{"0", "3"} {
|
for _, toolStatus := range []int{0, 3} {
|
||||||
t.Run("status "+toolStatus, func(t *testing.T) {
|
t.Run("status "+strconv.Itoa(toolStatus), func(t *testing.T) {
|
||||||
layout := newReleaseTestLayout(t)
|
layout := newReleaseTestLayout(t)
|
||||||
args := []string{"publish", "v4.2.0", "argument with spaces"}
|
args := []string{"publish", "v4.2.0", "argument with spaces"}
|
||||||
cmd := exec.Command("bash", append([]string{layout.wrapper}, args...)...)
|
cmd := exec.Command("bash", append([]string{layout.wrapper}, args...)...)
|
||||||
@@ -45,11 +50,11 @@ func TestReleaseWrapperForwardsNativeArgumentsAndStatus(t *testing.T) {
|
|||||||
cmd.Env = layout.environment(map[string]string{
|
cmd.Env = layout.environment(map[string]string{
|
||||||
"RELEASEKIT_ARGS_LOG": layout.argsLog,
|
"RELEASEKIT_ARGS_LOG": layout.argsLog,
|
||||||
"RELEASEKIT_BUILD_LOG": layout.buildLog,
|
"RELEASEKIT_BUILD_LOG": layout.buildLog,
|
||||||
"RELEASEKIT_TOOL_STATUS": toolStatus,
|
"RELEASEKIT_TOOL_STATUS": strconv.Itoa(toolStatus),
|
||||||
})
|
})
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if got := commandStatus(err); got != toolStatusInt(toolStatus) {
|
if got := commandStatus(err); got != toolStatus {
|
||||||
t.Fatalf("exit=%d, want %s; output=%q, error=%v", got, toolStatus, output, err)
|
t.Fatalf("exit=%d, want %d; output=%q, error=%v", got, toolStatus, output, err)
|
||||||
}
|
}
|
||||||
if len(output) != 0 {
|
if len(output) != 0 {
|
||||||
t.Fatalf("unexpected output: %q", output)
|
t.Fatalf("unexpected output: %q", output)
|
||||||
@@ -63,11 +68,11 @@ func TestReleaseWrapperForwardsNativeArgumentsAndStatus(t *testing.T) {
|
|||||||
t.Fatalf("build environment=%q", buildInvocation[1:3])
|
t.Fatalf("build environment=%q", buildInvocation[1:3])
|
||||||
}
|
}
|
||||||
buildArgs := buildInvocation[3:]
|
buildArgs := buildInvocation[3:]
|
||||||
if buildArgs[0] != "build" || !containsArgument(buildArgs, "-mod=readonly") || !containsArgument(buildArgs, "gitea.maximumdirect.net/eric/ReleaseKit/cmd/releasekit") {
|
if buildArgs[0] != "build" || !slices.Contains(buildArgs, "-mod=readonly") || !slices.Contains(buildArgs, "gitea.maximumdirect.net/eric/ReleaseKit/cmd/releasekit") {
|
||||||
t.Fatalf("unexpected build arguments: %q", buildArgs)
|
t.Fatalf("unexpected build arguments: %q", buildArgs)
|
||||||
}
|
}
|
||||||
if !hasAdjacentArguments(buildArgs, "-o", "./bin/releasekit") {
|
if info, err := os.Stat(filepath.Join(layout.repo, "bin", "releasekit")); err != nil || !info.Mode().IsRegular() {
|
||||||
t.Fatalf("build output was not selected: %q", buildArgs)
|
t.Fatalf("ReleaseKit executable was not built in bin: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
toolInvocation := readNULArguments(t, layout.argsLog)
|
toolInvocation := readNULArguments(t, layout.argsLog)
|
||||||
@@ -106,9 +111,6 @@ func TestReleaseKitProfileDeclaresProductAndDeliverySettings(t *testing.T) {
|
|||||||
if err := json.Unmarshal(data, &raw); err != nil {
|
if err := json.Unmarshal(data, &raw); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if _, exists := raw["checks"]; exists {
|
|
||||||
t.Fatal("profile must not define additional checks")
|
|
||||||
}
|
|
||||||
var deliveryFields map[string]json.RawMessage
|
var deliveryFields map[string]json.RawMessage
|
||||||
if err := json.Unmarshal(raw["delivery"], &deliveryFields); err != nil {
|
if err := json.Unmarshal(raw["delivery"], &deliveryFields); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -119,6 +121,7 @@ func TestReleaseKitProfileDeclaresProductAndDeliverySettings(t *testing.T) {
|
|||||||
var profile struct {
|
var profile struct {
|
||||||
Product string `json:"product"`
|
Product string `json:"product"`
|
||||||
Remote string `json:"remote"`
|
Remote string `json:"remote"`
|
||||||
|
Checks []json.RawMessage `json:"checks"`
|
||||||
Build struct {
|
Build struct {
|
||||||
MainPackage string `json:"main_package"`
|
MainPackage string `json:"main_package"`
|
||||||
BinaryName string `json:"binary_name"`
|
BinaryName string `json:"binary_name"`
|
||||||
@@ -134,6 +137,9 @@ func TestReleaseKitProfileDeclaresProductAndDeliverySettings(t *testing.T) {
|
|||||||
if err := json.Unmarshal(data, &profile); err != nil {
|
if err := json.Unmarshal(data, &profile); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
if len(profile.Checks) != 0 {
|
||||||
|
t.Fatal("profile must not define additional checks")
|
||||||
|
}
|
||||||
if profile.Product != "Audita" || profile.Remote != "origin" {
|
if profile.Product != "Audita" || profile.Remote != "origin" {
|
||||||
t.Fatalf("product=%q remote=%q", profile.Product, profile.Remote)
|
t.Fatalf("product=%q remote=%q", profile.Product, profile.Remote)
|
||||||
}
|
}
|
||||||
@@ -211,7 +217,7 @@ func (layout releaseTestLayout) environment(values map[string]string) []string {
|
|||||||
filtered := make([]string, 0, len(os.Environ())+len(values)+1)
|
filtered := make([]string, 0, len(os.Environ())+len(values)+1)
|
||||||
for _, value := range os.Environ() {
|
for _, value := range os.Environ() {
|
||||||
key, _, _ := strings.Cut(value, "=")
|
key, _, _ := strings.Cut(value, "=")
|
||||||
if key == "PATH" || hasKey(values, key) {
|
if _, overridden := values[key]; key == "PATH" || overridden {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
filtered = append(filtered, value)
|
filtered = append(filtered, value)
|
||||||
@@ -253,31 +259,6 @@ func commandStatus(err error) int {
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
func toolStatusInt(value string) int {
|
|
||||||
if value == "3" {
|
|
||||||
return 3
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func containsArgument(args []string, want string) bool {
|
|
||||||
for _, arg := range args {
|
|
||||||
if arg == want {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasAdjacentArguments(args []string, first, second string) bool {
|
|
||||||
for i := 0; i+1 < len(args); i++ {
|
|
||||||
if args[i] == first && args[i+1] == second {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func sameMembers(got, want []string) bool {
|
func sameMembers(got, want []string) bool {
|
||||||
gotSet := make(map[string]struct{}, len(got))
|
gotSet := make(map[string]struct{}, len(got))
|
||||||
for _, value := range got {
|
for _, value := range got {
|
||||||
@@ -289,8 +270,3 @@ func sameMembers(got, want []string) bool {
|
|||||||
}
|
}
|
||||||
return maps.Equal(gotSet, wantSet)
|
return maps.Equal(gotSet, wantSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasKey(values map[string]string, key string) bool {
|
|
||||||
_, exists := values[key]
|
|
||||||
return exists
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user