Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions go/discover_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,15 +314,22 @@ func TestDiscover_Good_AbsolutePath(t *testing.T) {
}

func TestDiscover_Good_RelativeBaseDir(t *testing.T) {
base := t.TempDir()
createModelDir(t, core.JoinPath(base, "relative-model"), map[string]any{
"model_type": "gemma3",
}, 1)

cwdResult := core.Getwd()
checkResultOK(t, cwdResult)
cwd := cwdResult.Value.(string)

// The base must sit under the working directory, not in t.TempDir():
// PathRel cannot express a path that crosses volumes, and on Windows CI
// the checkout ("D:\a\...") and TEMP ("C:\Users\...") are different drives.
baseResult := core.MkdirTemp(cwd, "discover-relative")
checkResultOK(t, baseResult)
base := baseResult.Value.(string)
t.Cleanup(func() { core.RemoveAll(base) })

createModelDir(t, core.PathJoin(base, "relative-model"), map[string]any{
"model_type": "gemma3",
}, 1)

relBaseResult := core.PathRel(cwd, base)
checkResultOK(t, relBaseResult)
relBase := relBaseResult.Value.(string)
Expand Down
8 changes: 6 additions & 2 deletions go/eval/bench/matrix.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"time"

core "dappco.re/go"
"dappco.re/go/inference/internal/pathx"
)

// matrix.go is the multi-model layer over the single-model harness: a bench MATRIX is a list of
Expand Down Expand Up @@ -94,7 +95,10 @@ func LoadMatrixConfig(data []byte) (MatrixConfig, error) {
return cfg, core.NewError(core.Sprintf("bench.LoadMatrixConfig: run %d has no model", i))
}
if run.Name == "" {
run.Name = core.PathBase(core.Trim(run.Model))
// pathx.Base: run.Model is a hub ref as often as a path
// ("org/model-a"), and those are '/'-separated everywhere —
// core.PathBase would name the whole ref on Windows.
run.Name = pathx.Base(core.Trim(run.Model))
}
if len(run.Lanes) == 0 {
run.Lanes = []string{MatrixLanePlain}
Expand Down Expand Up @@ -229,7 +233,7 @@ func RunMatrix(ctx context.Context, cfg MatrixConfig, load MatrixLoad, out io.Wr
}
name := run.Name
if name == "" {
name = core.PathBase(core.Trim(run.Model)) // positional runs bypass LoadMatrixConfig's defaulting
name = pathx.Base(core.Trim(run.Model)) // positional runs bypass LoadMatrixConfig's defaulting
}
row := MatrixRow{Name: name, Lane: lane, Model: run.Model, Draft: run.Draft}
row = runMatrixLane(ctx, row, run, lane, tokens, cfg, load)
Expand Down
4 changes: 3 additions & 1 deletion go/gguf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ func TestGGUF_ReadGGUFInfo_Bad(t *testing.T) {

func TestGGUF_DiscoverModels_Ugly(t *testing.T) {
dir := t.TempDir()
path := writeMinimalGGUFAt(t, core.JoinPath(dir, "model.gguf"), map[string]any{
// PathJoin (filepath.Join), not JoinPath (always '/'): the expectation is
// compared against a path DiscoverModels built with native separators.
path := writeMinimalGGUFAt(t, core.PathJoin(dir, "model.gguf"), map[string]any{
"general.architecture": "gemma4_text",
"general.file_type": uint32(7),
})
Expand Down
142 changes: 142 additions & 0 deletions go/internal/pathx/pathx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// SPDX-Licence-Identifier: EUPL-1.2

// Package pathx splits paths on either separator convention.
//
// core.PathBase and core.PathDir match only the platform's own separator
// (core.Env("DS")), so on Windows a '/'-separated path reads as having no
// separator at all: PathBase("/models/gemma3-1b") hands back the whole string
// and PathDir("/models/x/adapter.safetensors") hands back ".". Go itself
// accepts '/' in paths on every platform — it arrives from config files, CLI
// flags, HTTP model ids and Hugging Face refs — so any code deriving a name or
// a parent directory from a caller-supplied path needs both conventions.
//
// Base and Dir here are the both-separator forms. They do not clean, resolve
// or otherwise normalise: callers that need a real filesystem path still build
// it with core.PathJoin.
package pathx

import (
"sync"

core "dappco.re/go"
)

var (
separatorOnce sync.Once
separatorCache string
)

// nativeSeparator resolves the directory separator once per process and caches
// it, mirroring discover.go's pathSeparator. core.Env walks a map and falls
// back to os.Getenv when the key is unset, and these helpers sit on hot paths
// (lora's Inspect resolves an adapter sidecar per call). The override is set
// once at process start, typically by tests, and never mutates.
func nativeSeparator() string {
separatorOnce.Do(func() {
if separator := core.Env("DS"); separator != "" {
separatorCache = separator
return
}
separatorCache = "/"
})
return separatorCache
}

// Base returns the last element of p, treating both '/' and the platform
// separator as boundaries. Trailing separators are ignored. An empty path, or
// one that is nothing but separators, returns "".
//
// '\' is a boundary only where the platform says so: on POSIX it is an
// ordinary filename character, and treating it as a separator there would
// corrupt legitimate names.
//
// pathx.Base("/models/gemma3-1b") // "gemma3-1b" — every platform
// pathx.Base("org/model-a") // "model-a" — every platform
// pathx.Base(`C:\models\gemma3-1b\`) // "gemma3-1b" on Windows
func Base(p string) string {
p = trimTrailingSeparators(p)
if p == "" {
return ""
}
if i := lastSeparator(p); i >= 0 {
return p[i+1:]
}
return p
}

// Dir returns all but the last element of p, treating both '/' and the
// platform separator as boundaries. A path with no separator returns "",
// letting callers distinguish "no parent recorded" from the "." that
// core.PathDir reports.
//
// pathx.Dir("/models/my-lora/adapter.safetensors") // "/models/my-lora"
// pathx.Dir("adapter.safetensors") // ""
// pathx.Dir(`C:\models\my-lora\adapter.safetensors`) // `C:\models\my-lora` on Windows
func Dir(p string) string {
p = trimTrailingSeparators(p)
i := lastSeparator(p)
if i < 0 {
return ""
}
if i == 0 {
return p[:1] // a root-anchored path keeps its leading separator
}
return p[:i]
}

// Join appends child to dir using the separator dir is already written with,
// falling back to the platform's own when dir carries none.
//
// Unlike core.PathJoin it does not clean the result — callers on a hot path
// feed already-canonical directories — and it preserves the caller's
// convention rather than rewriting a '/'-spelled Windows path into a mixed
// one. An empty dir yields the bare child, matching PathJoin's "empty root =
// relative result".
//
// pathx.Join("/models/my-lora", "adapter_config.json") // "/models/my-lora/adapter_config.json"
// pathx.Join("/models/my-lora/", "adapter_config.json") // "/models/my-lora/adapter_config.json"
func Join(dir, child string) string {
if dir == "" {
return child
}
separator := separatorOf(dir)
if core.HasSuffix(dir, separator) {
return dir + child
}
return dir + separator + child
}

// separatorOf reports the separator p is written with — the last one it
// contains — or the platform's own when it contains none.
func separatorOf(p string) string {
if i := lastSeparator(p); i >= 0 {
return p[i : i+1]
}
return nativeSeparator()
}

// lastSeparator reports the index of the final '/' or platform separator in p,
// or -1 when p carries neither.
func lastSeparator(p string) int {
best := core.LastIndex(p, "/")
if separator := nativeSeparator(); separator != "/" {
if i := core.LastIndex(p, separator); i > best {
best = i
}
}
return best
}

// trimTrailingSeparators drops any run of trailing separators, so that a path
// written with a trailing slash names the same element as one without.
func trimTrailingSeparators(p string) string {
separator := nativeSeparator()
for len(p) > 1 {
tail := p[len(p)-1:]
if tail != "/" && tail != separator {
break
}
p = p[:len(p)-1]
}
return p
}
146 changes: 146 additions & 0 deletions go/internal/pathx/pathx_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// SPDX-Licence-Identifier: EUPL-1.2

package pathx

import (
"runtime"
"testing"
)

// TestPathx_Base_Good pins the ordinary shapes: a '/'-separated absolute path,
// a Hugging Face style ref, and a bare name with no separator at all. All three
// answer identically on every platform, which is the point of the helper —
// core.PathBase hands back the whole string for these on Windows.
func TestPathx_Base_Good(t *testing.T) {
for _, tc := range []struct{ in, want string }{
{"/models/gemma3-1b", "gemma3-1b"},
{"/models/gemma4/model.gguf", "model.gguf"},
{"org/model-a", "model-a"},
{"model.gguf", "model.gguf"},
} {
if got := Base(tc.in); got != tc.want {
t.Errorf("Base(%q) = %q, want %q", tc.in, got, tc.want)
}
}
}

// TestPathx_Base_Bad covers the inputs that name no element: empty, a lone
// separator, and a run of them.
func TestPathx_Base_Bad(t *testing.T) {
for _, in := range []string{"", "/", "///"} {
if got := Base(in); got != "" {
t.Errorf("Base(%q) = %q, want %q", in, got, "")
}
}
}

// TestPathx_Base_Ugly covers the surprising-but-valid cases: trailing
// separators name the element before them, and a backslash is a separator only
// where the platform agrees — on POSIX it is a legitimate filename character
// and must survive intact.
func TestPathx_Base_Ugly(t *testing.T) {
if got := Base("/models/gemma3-1b/"); got != "gemma3-1b" {
t.Errorf("Base(trailing slash) = %q, want %q", got, "gemma3-1b")
}
if got := Base("/models/gemma3-1b//"); got != "gemma3-1b" {
t.Errorf("Base(trailing slashes) = %q, want %q", got, "gemma3-1b")
}

backslashed := `C:\models\gemma3-1b`
got := Base(backslashed)
if runtime.GOOS == "windows" {
if got != "gemma3-1b" {
t.Errorf("Base(%q) = %q, want %q", backslashed, got, "gemma3-1b")
}
return
}
if got != backslashed {
t.Errorf("Base(%q) = %q, want it unchanged — '\\' is a filename character on %s",
backslashed, got, runtime.GOOS)
}
}

// TestPathx_Join_Good pins the ordinary join and the trailing-separator
// collapse, both of which keep the separator the caller already used.
func TestPathx_Join_Good(t *testing.T) {
for _, tc := range []struct{ dir, child, want string }{
{"/models/my-lora", "adapter_config.json", "/models/my-lora/adapter_config.json"},
{"/models/my-lora/", "adapter_config.json", "/models/my-lora/adapter_config.json"},
{"/", "adapter_config.json", "/adapter_config.json"},
} {
if got := Join(tc.dir, tc.child); got != tc.want {
t.Errorf("Join(%q, %q) = %q, want %q", tc.dir, tc.child, got, tc.want)
}
}
}

// TestPathx_Join_Bad covers the empty dir, which yields the bare child rather
// than an accidentally root-anchored path.
func TestPathx_Join_Bad(t *testing.T) {
if got := Join("", "adapter_config.json"); got != "adapter_config.json" {
t.Errorf("Join(empty dir) = %q, want %q", got, "adapter_config.json")
}
}

// TestPathx_Join_Ugly covers a separator-less dir, which takes the platform's
// own separator — the only case where the result's shape depends on the host.
func TestPathx_Join_Ugly(t *testing.T) {
got := Join(".", "adapter_config.json")
want := "." + separatorOf(".") + "adapter_config.json"
if got != want {
t.Errorf("Join(%q, %q) = %q, want %q", ".", "adapter_config.json", got, want)
}
if runtime.GOOS != "windows" && got != "./adapter_config.json" {
t.Errorf("Join(%q, ...) = %q, want %q on %s", ".", got, "./adapter_config.json", runtime.GOOS)
}
}

// TestPathx_Dir_Good pins the parent of a nested path and of a path anchored
// directly at the root.
func TestPathx_Dir_Good(t *testing.T) {
for _, tc := range []struct{ in, want string }{
{"/models/my-lora/adapter.safetensors", "/models/my-lora"},
{"/models/my-lora", "/models"},
{"/models", "/"},
{"org/model-a", "org"},
} {
if got := Dir(tc.in); got != tc.want {
t.Errorf("Dir(%q) = %q, want %q", tc.in, got, tc.want)
}
}
}

// TestPathx_Dir_Bad covers the inputs with no parent to report. Unlike
// core.PathDir these answer "" rather than ".", so a caller can tell "no
// directory component" from "the current directory".
func TestPathx_Dir_Bad(t *testing.T) {
for _, in := range []string{"", "adapter.safetensors"} {
if got := Dir(in); got != "" {
t.Errorf("Dir(%q) = %q, want %q", in, got, "")
}
}
}

// TestPathx_Dir_Ugly covers trailing separators — "/models/x/" has the same
// parent as "/models/x" — and the platform-dependent backslash.
func TestPathx_Dir_Ugly(t *testing.T) {
if got := Dir("/models/my-lora/"); got != "/models" {
t.Errorf("Dir(trailing slash) = %q, want %q", got, "/models")
}
if got := Dir("/"); got != "/" {
t.Errorf("Dir(root) = %q, want %q — the root is its own parent, as filepath.Dir has it", got, "/")
}

backslashed := `C:\models\my-lora\adapter.safetensors`
got := Dir(backslashed)
if runtime.GOOS == "windows" {
if got != `C:\models\my-lora` {
t.Errorf("Dir(%q) = %q, want %q", backslashed, got, `C:\models\my-lora`)
}
return
}
if got != "" {
t.Errorf("Dir(%q) = %q, want %q — '\\' is a filename character on %s",
backslashed, got, "", runtime.GOOS)
}
}
1 change: 1 addition & 0 deletions go/kv/blockcache/blockcache_branch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ func TestBlockcache_Service_WriteDiskBlockMkdirFailure(t *testing.T) {
// passes; the subsequent MkdirAll that recreates the cache directory then
// fails on the read-only parent, surfacing the recreate error.
func TestBlockcache_Service_ClearDiskRecreateFailure(t *testing.T) {
requirePOSIXDirPermissions(t)
parent := core.PathJoin(t.TempDir(), "parent")
if result := core.MkdirAll(parent, 0o700); !result.OK {
t.Fatalf("MkdirAll(parent) error = %s", result.Error())
Expand Down
Loading
Loading