mirror of
https://github.com/MobAI-App/simslim.git
synced 2026-09-30 18:16:29 +02:00
Replace the hand-rolled argument handling with the urfave/cli/v3 command tree (the project's only dependency; no runtime transitive deps). This removes the manual command switch, parseInterspersedFlags, jsonOption, and extractDeviceSets in favor of the library's dispatch and flag parsing. The command surface is unchanged: same commands, flags, arguments, JSON output, error format (simslim: <msg>, exit 1), and unknown-command/usage handling (exit 2). Flags may still appear before or after positional args (v3 parses flags anywhere by default). --set is now a global flag inherited by every subcommand, registered in its flag Action with last-wins, comma-separated semantics. main still owns version, help, and the macOS guard; the root usage text is preserved verbatim. The command tree lives in app.go (newApp); each subcommand's Action is a cmd* function in main.go reading flags via cmd.Bool/String and positionals via cmd.Args(). Unit tests for the removed helpers are replaced by app_test.go, which drives newApp through Run.
94 lines
3.7 KiB
Go
94 lines
3.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
// runApp runs the CLI with stdout/stderr discarded so command output does not
|
|
// pollute the test log. It uses `profiles`, which touches neither simctl nor the
|
|
// macOS-only guard, to exercise flag and argument parsing through the real tree.
|
|
func runApp(t *testing.T, args ...string) error {
|
|
t.Helper()
|
|
devnull, err := os.OpenFile(os.DevNull, os.O_WRONLY, 0)
|
|
if err != nil {
|
|
t.Fatalf("open %s: %v", os.DevNull, err)
|
|
}
|
|
defer devnull.Close()
|
|
oldStdout, oldStderr := os.Stdout, os.Stderr
|
|
os.Stdout, os.Stderr = devnull, devnull
|
|
defer func() { os.Stdout, os.Stderr = oldStdout, oldStderr }()
|
|
return newApp().Run(context.Background(), append([]string{"simslim"}, args...))
|
|
}
|
|
|
|
// TestAppRegistersDeviceSets verifies the global --set flag registers the extra
|
|
// device sets, whether it appears before or after the subcommand, in equals
|
|
// form, comma-separated, or repeated (last-wins), and that a blank value errors.
|
|
func TestAppRegistersDeviceSets(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
args []string
|
|
wantExtra []string // tokens registered beyond the well-known sets
|
|
wantError bool
|
|
}{
|
|
{name: "absent", args: []string{"profiles"}},
|
|
{name: "before command", args: []string{"--set", "/a", "profiles"}, wantExtra: []string{"/a"}},
|
|
{name: "after command", args: []string{"profiles", "--set", "/a"}, wantExtra: []string{"/a"}},
|
|
{name: "equals form", args: []string{"profiles", "--set=/a"}, wantExtra: []string{"/a"}},
|
|
{name: "comma-separated", args: []string{"--set", "/a,/b", "profiles"}, wantExtra: []string{"/a", "/b"}},
|
|
{name: "comma trims blanks", args: []string{"--set=/a, ,/b", "profiles"}, wantExtra: []string{"/a", "/b"}},
|
|
{name: "repeat is last-wins", args: []string{"--set", "/a", "--set", "/b", "profiles"}, wantExtra: []string{"/b"}},
|
|
{name: "well-known not duplicated", args: []string{"--set", "testing", "profiles"}},
|
|
{name: "blank value errors", args: []string{"--set", " ", "profiles"}, wantError: true},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
extraDeviceSets = nil
|
|
defer func() { extraDeviceSets = nil }()
|
|
err := runApp(t, tt.args...)
|
|
if (err != nil) != tt.wantError {
|
|
t.Fatalf("Run(%v) error = %v, wantError %v", tt.args, err, tt.wantError)
|
|
}
|
|
var gotExtra []string
|
|
for _, set := range extraDeviceSets {
|
|
gotExtra = append(gotExtra, set.token)
|
|
}
|
|
if !reflect.DeepEqual(gotExtra, tt.wantExtra) {
|
|
t.Errorf("registered extra sets = %v, want %v", gotExtra, tt.wantExtra)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestAppFlagParsing covers per-command flag parsing: a flag may appear before
|
|
// or after a positional argument, --json rejects duplicates, an unknown flag is
|
|
// rejected, and an unknown positional fails validation.
|
|
func TestAppFlagParsing(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
args []string
|
|
wantError bool
|
|
}{
|
|
{name: "json flag", args: []string{"profiles", "--json"}},
|
|
{name: "flag before positional", args: []string{"profiles", "--json", "siri"}},
|
|
{name: "flag after positional", args: []string{"profiles", "siri", "--json"}},
|
|
{name: "duplicate json rejected", args: []string{"profiles", "--json", "--json", "siri"}, wantError: true},
|
|
{name: "unknown flag rejected", args: []string{"profiles", "--nope"}, wantError: true},
|
|
{name: "unknown category rejected", args: []string{"profiles", "does-not-exist"}, wantError: true},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
extraDeviceSets = nil
|
|
defer func() { extraDeviceSets = nil }()
|
|
err := runApp(t, tt.args...)
|
|
if (err != nil) != tt.wantError {
|
|
t.Fatalf("Run(%v) error = %v, wantError %v", tt.args, err, tt.wantError)
|
|
}
|
|
})
|
|
}
|
|
}
|