Skip to content

Commit d8fd3f8

Browse files
hjothahjothamendix
authored andcommitted
test: prefer installed mx in integration harness
The roundtrip test helpers previously preferred a repo-local `reference/mxbuild/modeler/mx` (a developer convenience that was never checked into CI images), then fell back to cached downloads, then to `PATH`. On machines where CI installs `mx` via the system package (current setup) the harness still walked the fallback chain and either ran against a stale cached binary or a missing repo-local path, which silently changed the Mendix version exercised by the tests. Reorder `findMxBinary`: MX_BINARY env var -> PATH -> repo-local reference -> cached downloads `PATH` now wins over repo-local so whatever version CI installs is what the tests use. The cached-download fallback also stops returning the lexicographically last match — `9.24.40.80973` sorted after `11.9.0` and we ran integration tests on Mendix 9 by accident. Add `newestVersionedPath` which parses each path's version segment into `[major, minor, patch, build]` ints and picks the highest tuple. Two `//go:build integration` tests cover the new helper: - `TestNewestVersionedPath_PicksNewestNumericVersion` — version ordering - `TestFindMxBinary_PrefersPathOverCachedDownloads` — PATH wins when both PATH and cache have `mx` present
1 parent cf6860c commit d8fd3f8

2 files changed

Lines changed: 144 additions & 9 deletions

File tree

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
//go:build integration
4+
5+
package executor
6+
7+
import (
8+
"os"
9+
"path/filepath"
10+
"testing"
11+
)
12+
13+
func TestNewestVersionedPath_PicksNewestNumericVersion(t *testing.T) {
14+
t.Parallel()
15+
16+
paths := []string{
17+
"/tmp/.mxcli/mxbuild/11.9.0/modeler/mx",
18+
"/tmp/.mxcli/mxbuild/11.6.3/modeler/mx",
19+
"/tmp/.mxcli/mxbuild/9.24.40.80973/modeler/mx",
20+
}
21+
22+
got := newestVersionedPath(paths)
23+
want := "/tmp/.mxcli/mxbuild/11.9.0/modeler/mx"
24+
if got != want {
25+
t.Fatalf("newestVersionedPath() = %q, want %q", got, want)
26+
}
27+
}
28+
29+
func TestFindMxBinary_PrefersPathOverCachedDownloads(t *testing.T) {
30+
home := t.TempDir()
31+
pathDir := filepath.Join(home, "bin")
32+
if err := os.MkdirAll(pathDir, 0755); err != nil {
33+
t.Fatalf("mkdir path dir: %v", err)
34+
}
35+
36+
pathMx := filepath.Join(pathDir, "mx")
37+
if err := os.WriteFile(pathMx, []byte("#!/bin/sh\nexit 0\n"), 0755); err != nil {
38+
t.Fatalf("write PATH mx: %v", err)
39+
}
40+
41+
for _, version := range []string{"11.9.0", "11.6.3", "9.24.40.80973"} {
42+
cacheMx := filepath.Join(home, ".mxcli", "mxbuild", version, "modeler", "mx")
43+
if err := os.MkdirAll(filepath.Dir(cacheMx), 0755); err != nil {
44+
t.Fatalf("mkdir cache dir: %v", err)
45+
}
46+
if err := os.WriteFile(cacheMx, []byte("#!/bin/sh\nexit 0\n"), 0755); err != nil {
47+
t.Fatalf("write cached mx: %v", err)
48+
}
49+
}
50+
51+
t.Setenv("HOME", home)
52+
t.Setenv("PATH", pathDir)
53+
t.Setenv("MX_BINARY", "")
54+
55+
if got := findMxBinary(); got != pathMx {
56+
t.Fatalf("findMxBinary() = %q, want PATH binary %q", got, pathMx)
57+
}
58+
}

mdl/executor/roundtrip_helpers_test.go

Lines changed: 86 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"os"
1919
"os/exec"
2020
"path/filepath"
21+
"strconv"
2122
"strings"
2223
"testing"
2324

@@ -132,8 +133,8 @@ func copyTestProject(t *testing.T) string {
132133
}
133134

134135
// findMxBinary searches for the mx command in known locations.
135-
// Search order: MX_BINARY env var, reference/mxbuild/modeler/mx (repo-local),
136-
// ~/.mxcli/mxbuild/*/modeler/mx (cached downloads), PATH lookup.
136+
// Search order: MX_BINARY env var, PATH lookup, reference/mxbuild/modeler/mx
137+
// (repo-local), ~/.mxcli/mxbuild/*/modeler/mx (cached downloads, newest numeric version).
137138
func findMxBinary() string {
138139
// 0. Explicit override via environment variable
139140
if p := os.Getenv("MX_BINARY"); p != "" {
@@ -142,28 +143,104 @@ func findMxBinary() string {
142143
}
143144
}
144145

145-
// 1. Repo-local reference path
146+
// 1. PATH lookup
147+
if p, err := exec.LookPath("mx"); err == nil {
148+
return p
149+
}
150+
151+
// 2. Repo-local reference path
146152
repoPath, err := filepath.Abs("../../reference/mxbuild/modeler/mx")
147153
if err == nil {
148154
if _, err := os.Stat(repoPath); err == nil {
149155
return repoPath
150156
}
151157
}
152158

153-
// 2. Cached downloads (~/.mxcli/mxbuild/*/modeler/mx)
159+
// 3. Cached downloads (~/.mxcli/mxbuild/*/modeler/mx)
154160
if home, err := os.UserHomeDir(); err == nil {
155161
pattern := filepath.Join(home, ".mxcli", "mxbuild", "*", "modeler", "mx")
156162
if matches, _ := filepath.Glob(pattern); len(matches) > 0 {
157-
return matches[len(matches)-1]
163+
return newestVersionedPath(matches)
158164
}
159165
}
160166

161-
// 3. PATH lookup
162-
if p, err := exec.LookPath("mx"); err == nil {
163-
return p
167+
return ""
168+
}
169+
170+
func newestVersionedPath(paths []string) string {
171+
var best string
172+
var bestVersion []int
173+
bestValid := false
174+
175+
for _, path := range paths {
176+
versionParts, ok := parseVersionParts(versionFromPath(path))
177+
switch {
178+
case best == "":
179+
best = path
180+
bestVersion = versionParts
181+
bestValid = ok
182+
case ok && !bestValid:
183+
best = path
184+
bestVersion = versionParts
185+
bestValid = true
186+
case ok && bestValid:
187+
if cmp := compareVersionParts(versionParts, bestVersion); cmp > 0 || (cmp == 0 && path > best) {
188+
best = path
189+
bestVersion = versionParts
190+
}
191+
case !ok && !bestValid && path > best:
192+
best = path
193+
}
164194
}
165195

166-
return ""
196+
return best
197+
}
198+
199+
func versionFromPath(path string) string {
200+
versionDir := filepath.Dir(filepath.Dir(path))
201+
return filepath.Base(versionDir)
202+
}
203+
204+
func parseVersionParts(version string) ([]int, bool) {
205+
if version == "" {
206+
return nil, false
207+
}
208+
parts := strings.Split(version, ".")
209+
values := make([]int, 0, len(parts))
210+
for _, part := range parts {
211+
if part == "" {
212+
return nil, false
213+
}
214+
value, err := strconv.Atoi(part)
215+
if err != nil {
216+
return nil, false
217+
}
218+
values = append(values, value)
219+
}
220+
return values, true
221+
}
222+
223+
func compareVersionParts(left, right []int) int {
224+
maxLen := len(left)
225+
if len(right) > maxLen {
226+
maxLen = len(right)
227+
}
228+
for i := 0; i < maxLen; i++ {
229+
var l, r int
230+
if i < len(left) {
231+
l = left[i]
232+
}
233+
if i < len(right) {
234+
r = right[i]
235+
}
236+
switch {
237+
case l < r:
238+
return -1
239+
case l > r:
240+
return 1
241+
}
242+
}
243+
return 0
167244
}
168245

169246
// copyFile copies a single file from src to dst.

0 commit comments

Comments
 (0)