Files
homeagent-sdk/tools/hmapdev/sdk_resolve_test.go
JianFeeeee 9206353858 feat(hmapdev): 项目声明 SDK 版本,工具链据此自动选(plg.json 的 sdk 字段)
此前项目里没有任何「我要哪版 SDK」的声明:go.mod 的 require 是个 Go 模块版本,
而工具链实际用的是存储里的 current——谁改过 current 就拿谁的版本编,出错时
表现为莫名其妙的编译错误(本轮就踩过:存储里只有陈旧的 v0.8.0,模板项目
首次构建报 undefined: sdk.InjectOptions)。

- `plg.json` 新增 `sdk` 字段:本插件针对的 SDK 版本。`hmapdev init` 生成时写入
  **完整版本号**(如 "1.2.0")。
- `hmapdev build` 按声明的版本在本地 SDK 存储里定位:命中则用它并把 go.mod 的
  require/replace 同步到该版本;未命中则报**可执行**的错误(列出已装版本 +
  `hmapdev sdk install vX.Y.Z`),绝不静默退化成 current。
- **区间写法("1.2")被拒绝**并说明规矩:SDK 版本跟随内核中版本、patch 位恒为 .0,
  一条内核线只有一个 SDK 版本(写区间会让人误以为同一条线里还能挑不同 SDK)。
- 产物 `plugin.json` 记录实际选中的版本(`sdk`),便于追溯「这个 .hmap 是哪版编的」。
- 显式 `--sdk-path` / `plg.json sdk_path` 优先(本机改 SDK 联调的路径),此路径下也尽力记录版本。
- 存量项目(plg.json 无 sdk 字段)行为不变,向后兼容。

顺带修一处自相矛盾:解析出 1.2.0 之外的版本时,原先只改 replace 而 require 保持旧版本,
一旦有人删掉 replace 就会静默用回旧 SDK 编译(`go list -m` 报的也是假版本)。

验证:单测 10 例(精确命中/带 v 前缀目录/区间写法被拒并说明规矩/未命中给可执行命令/
空存储给安装指引/非法值拒绝/杂项目录不干扰)+ 反向验证(把版本比较退化成字典序,
「1.2.x 取最新」用例立刻变红,证明判据能发现缺陷)。
E2E:init → plg.json `"sdk": "1.2.0"`;build → 精确解析、go.mod require/replace 一致、
产物 plugin.json 记录 sdk;声明不存在的版本 → 可执行报错;无 sdk 字段 → 照旧构建。
2026-09-12 15:51:23 +08:00

104 lines
3.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"os"
"path/filepath"
"strings"
"testing"
)
// withSDKStore 把 SDK 存储指到临时目录sdkStore 读 HOME并造出给定版本目录。
func withSDKStore(t *testing.T, versions ...string) {
t.Helper()
home := t.TempDir()
t.Setenv("HOME", home)
store := filepath.Join(home, ".homeagent", sdkDirName)
if err := os.MkdirAll(store, 0755); err != nil {
t.Fatal(err)
}
for _, v := range versions {
if err := os.MkdirAll(filepath.Join(store, v), 0755); err != nil {
t.Fatal(err)
}
}
}
// 项目声明中版本 → 挑该接口线里最新的补丁;声明完整版本 → 精确命中。
//
// 为什么允许中版本是关键判据patch 位只含工具链/打包修复(接口不变),
// 让项目声明 "1.2" 而不是死钉 "1.2.0",才能既跟得上工具链修复又不跨接口线。
func TestResolveSDKForProject(t *testing.T) {
t.Run("区间写法1.2)被拒并说明版本纪律", func(t *testing.T) {
// 判据SDK 的 patch 位恒为 .0 → 一条内核线只有一个 SDK 版本,
// 区间写法会让人误以为「同一条线里还能挑版本」,所以直接拒。
withSDKStore(t, "1.2.0")
_, _, err := ResolveSDKForProject("1.2")
if err == nil {
t.Fatal("1.2 这种区间写法应被拒绝")
}
for _, want := range []string{"完整版本号", "patch 位恒为 .0"} {
if !strings.Contains(err.Error(), want) {
t.Fatalf("拒绝理由里应说清规矩(缺少 %q: %v", want, err)
}
}
})
t.Run("完整版本精确命中", func(t *testing.T) {
withSDKStore(t, "1.2.0", "1.3.0")
_, ver, err := ResolveSDKForProject("1.2.0")
if err != nil || ver != "1.2.0" {
t.Fatalf("精确命中失败: ver=%s err=%v", ver, err)
}
})
t.Run("存储目录带 v 前缀也能命中", func(t *testing.T) {
withSDKStore(t, "v1.2.0")
dir, ver, err := ResolveSDKForProject("1.2.0")
if err != nil {
t.Fatal(err)
}
if ver != "1.2.0" || !strings.HasSuffix(dir, "v1.2.0") {
t.Fatalf("带 v 前缀的目录名未被识别: dir=%s ver=%s", dir, ver)
}
})
t.Run("未命中要给出可执行命令与已装清单", func(t *testing.T) {
withSDKStore(t, "1.2.0")
_, _, err := ResolveSDKForProject("2.0.0")
if err == nil {
t.Fatal("应报错")
}
msg := err.Error()
for _, want := range []string{"sdk=2.0.0", "hmapdev sdk install", "hmapdev sdk list", "1.2.0"} {
if !strings.Contains(msg, want) {
t.Fatalf("错误信息缺少 %q要可执行不能只说 not found:\n%s", want, msg)
}
}
})
t.Run("空存储也能给出安装指引", func(t *testing.T) {
withSDKStore(t)
_, _, err := ResolveSDKForProject("1.2.0")
if err == nil || !strings.Contains(err.Error(), "hmapdev sdk install v1.2.0") {
t.Fatalf("空存储时应提示装哪个版本: %v", err)
}
})
t.Run("非法声明直接拒绝", func(t *testing.T) {
withSDKStore(t, "1.2.0")
for _, bad := range []string{"abc", "1", "1.2", "1.2.3.4", "-1.2.0"} {
if _, _, err := ResolveSDKForProject(bad); err == nil {
t.Fatalf("非法版本 %q 应被拒绝(宁可报错也不许当通配符)", bad)
}
}
})
t.Run("非版本目录不参与匹配", func(t *testing.T) {
withSDKStore(t, "1.2.0", "backup-old", ".hidden", "current")
_, ver, err := ResolveSDKForProject("1.2.0")
if err != nil || ver != "1.2.0" {
t.Fatalf("杂项目录不应干扰: ver=%s err=%v", ver, err)
}
})
}