Files
dingtalk-workspace-cli/test/smoke/cli_smoke_test.go
玉澜 20a4eb77a4 test(smoke): share one root command across --help subtests
TestCLISmoke_AllPublicCommandsSupportHelp 之前对 845+ 条命令路径每条
重建一次 NewRootCommand;Linux TSan 影子内存随树构建次数持续累积,
CI ubuntu runner 上 ~3 分钟即被 OOM SIGTERM(三次同形态失败)。
--help 不 mutate 命令状态,共享单个 root 即可:本地 race 峰值内存
4.1GB -> 844MB,无 race 全套耗时同时从分钟级降到 ~1.5s。
2026-08-03 01:58:12 +08:00

214 lines
6.3 KiB
Go

package smoke_test
import (
"bytes"
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
"testing"
"time"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/app"
"github.com/spf13/cobra"
)
const cliSmokeHelperEnv = "DWS_CLI_SMOKE_HELPER"
// TestCLIHelperProcess executes the real app entrypoint in a subprocess. Product
// routing inspects os.Args, so using Cobra.SetArgs would not exercise the same
// path as the dws binary.
func TestCLIHelperProcess(t *testing.T) {
if os.Getenv(cliSmokeHelperEnv) != "1" {
return
}
marker := -1
for i, arg := range os.Args {
if arg == "--" {
marker = i
break
}
}
if marker < 0 {
fmt.Fprintln(os.Stderr, "CLI smoke helper: missing -- argument marker")
os.Exit(2)
}
os.Args = append([]string{"dws"}, os.Args[marker+1:]...)
os.Exit(app.Execute())
}
func TestCLISmoke_AllPublicCommandsSupportHelp(t *testing.T) {
_ = isolatedCLIEnv(t)
root := app.NewRootCommand()
root.InitDefaultHelpCmd()
paths := publicCommandPaths(root)
if len(paths) == 0 {
t.Fatal("public command traversal returned no commands")
}
for _, path := range paths {
path := path
name := "root"
if len(path) > 0 {
name = strings.Join(path, "/")
}
t.Run(name, func(t *testing.T) {
args := append(append([]string(nil), path...), "--help")
// Help never executes a product handler, so SetArgs is sufficient here.
// Real product dispatch is covered below through app.Execute in a
// subprocess because that path intentionally inspects os.Args.
// Reuse one root: --help does not mutate command state, and rebuilding
// the tree per path balloons memory ~845x under the race detector.
var output bytes.Buffer
root.SetOut(&output)
root.SetErr(&output)
root.SetArgs(args)
err := root.Execute()
if err != nil {
t.Fatalf("dws %s failed: %v\noutput:\n%s", strings.Join(args, " "), err, output.String())
}
if strings.TrimSpace(output.String()) == "" {
t.Fatalf("dws %s returned empty help", strings.Join(args, " "))
}
})
}
t.Logf("validated --help for %d public Cobra command paths", len(paths))
}
func TestCLISmoke_RepresentativeStaticCommandsReturnMockJSON(t *testing.T) {
env := isolatedCLIEnv(t)
tests := []struct {
name string
args []string
wantTool string
}{
{
name: "contact search",
args: []string{"--mock", "--format", "json", "contact", "user", "search", "--query", "Ada"},
wantTool: "search_contact_by_key_word",
},
{
name: "calendar list",
args: []string{
"--mock", "--format", "json", "calendar", "event", "list",
"--start", "2026-07-10T09:00:00+08:00",
"--end", "2026-07-10T10:00:00+08:00",
},
wantTool: "list_calendar_events",
},
{
name: "ding list",
args: []string{"--mock", "--format", "json", "ding", "message", "list", "--type", "UNREAD"},
wantTool: "list_ding_messages",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
stdout, stderr, err := runCLI(t, env, tc.args...)
if err != nil {
t.Fatalf("dws %s failed: %v\nstdout:\n%s\nstderr:\n%s", strings.Join(tc.args, " "), err, stdout, stderr)
}
var payload map[string]any
if err := json.Unmarshal([]byte(stdout), &payload); err != nil {
t.Fatalf("dws %s returned non-JSON stdout: %v\nstdout:\n%s\nstderr:\n%s", strings.Join(tc.args, " "), err, stdout, stderr)
}
if payload["_mock"] != true {
t.Fatalf("dws %s _mock = %#v, want true; payload=%#v", strings.Join(tc.args, " "), payload["_mock"], payload)
}
if payload["_tool"] != tc.wantTool {
t.Fatalf("dws %s _tool = %#v, want %q; payload=%#v", strings.Join(tc.args, " "), payload["_tool"], tc.wantTool, payload)
}
})
}
}
// publicCommandPaths follows only canonical, user-visible Cobra commands.
// Hidden commands and commands marked Deprecated by Cobra are intentionally
// excluded; aliases are not separate command nodes. Parents and leaves are both
// checked, while positional placeholders in Use are never synthesized.
func publicCommandPaths(root *cobra.Command) [][]string {
var paths [][]string
var walk func(*cobra.Command, []string)
walk = func(cmd *cobra.Command, path []string) {
if cmd != root && (cmd.Hidden || cmd.Deprecated != "") {
return
}
paths = append(paths, append([]string(nil), path...))
for _, child := range cmd.Commands() {
walk(child, append(path, child.Name()))
}
}
walk(root, nil)
sort.Slice(paths, func(i, j int) bool {
return strings.Join(paths[i], " ") < strings.Join(paths[j], " ")
})
return paths
}
func isolatedCLIEnv(t *testing.T) []string {
t.Helper()
root := t.TempDir()
controlled := map[string]string{
"HOME": root,
"USERPROFILE": root,
"DWS_CONFIG_DIR": filepath.Join(root, "config"),
"DWS_KEYCHAIN_DIR": filepath.Join(root, "keychain"),
"DWS_DISABLE_KEYCHAIN": "1",
"HTTP_PROXY": "http://127.0.0.1:1",
"HTTPS_PROXY": "http://127.0.0.1:1",
"http_proxy": "http://127.0.0.1:1",
"https_proxy": "http://127.0.0.1:1",
"NO_PROXY": "127.0.0.1,localhost,::1",
"no_proxy": "127.0.0.1,localhost,::1",
cliSmokeHelperEnv: "1",
"DWS_ALLOW_HTTP_ENDPOINTS": "1",
"DWS_TRUSTED_DOMAINS": "127.0.0.1,localhost,::1",
}
for key, value := range controlled {
t.Setenv(key, value)
}
env := make([]string, 0, len(controlled)+8)
for _, key := range []string{"PATH", "TMPDIR", "TEMP", "TMP", "LANG", "LC_ALL", "TZ", "SYSTEMROOT"} {
if value := os.Getenv(key); value != "" {
env = append(env, key+"="+value)
}
}
for key, value := range controlled {
env = append(env, key+"="+value)
}
sort.Strings(env)
return env
}
func runCLI(t *testing.T, env []string, args ...string) (string, string, error) {
t.Helper()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
processArgs := append([]string{"-test.run=^TestCLIHelperProcess$", "--"}, args...)
cmd := exec.CommandContext(ctx, os.Args[0], processArgs...)
cmd.Env = env
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if ctx.Err() != nil {
t.Fatalf("dws %s timed out: %v\nstdout:\n%s\nstderr:\n%s", strings.Join(args, " "), ctx.Err(), stdout.String(), stderr.String())
}
return stdout.String(), stderr.String(), err
}