2025-08-26 03:21:35 +02:00
|
|
|
package wand
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
2025-09-05 03:19:00 +02:00
|
|
|
"code.squareroundforest.org/arpio/wand/internal/tests/testlib"
|
2025-09-06 02:46:28 +02:00
|
|
|
"fmt"
|
2025-08-26 14:12:18 +02:00
|
|
|
"io"
|
|
|
|
|
"testing"
|
2025-09-05 03:19:00 +02:00
|
|
|
"time"
|
2025-08-26 03:21:35 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestReflect(t *testing.T) {
|
2025-09-04 01:09:24 +02:00
|
|
|
t.Run("unpack", func(t *testing.T) {
|
|
|
|
|
f := func(a []int) int { return a[0] }
|
|
|
|
|
t.Run("slice", testExec(testCase{impl: f, command: "foo 42"}, "", "42"))
|
|
|
|
|
ps := func(a *[]*[]int) int { return (*((*a)[0]))[0] }
|
|
|
|
|
t.Run("pointer and slice", testExec(testCase{impl: ps, command: "foo 42"}, "", "42"))
|
2025-09-05 03:19:00 +02:00
|
|
|
type s struct {
|
|
|
|
|
Foo int
|
|
|
|
|
Bar *s
|
|
|
|
|
}
|
2025-09-04 01:09:24 +02:00
|
|
|
c := func(v s) int { return v.Foo }
|
|
|
|
|
t.Run("circular type", testExec(testCase{impl: c, command: "foo --foo 42"}, "unsupported parameter type", ""))
|
|
|
|
|
fp := func(a int) int { return a }
|
|
|
|
|
t.Run("function pointer", testExec(testCase{impl: &fp, command: "foo 42"}, "", "42"))
|
|
|
|
|
var p any
|
|
|
|
|
p = &p
|
|
|
|
|
t.Run("circular reference", testExec(testCase{impl: p, command: "foo"}, "must be a function or a pointer to a function", ""))
|
|
|
|
|
var i any
|
|
|
|
|
t.Run("nil interface", testExec(testCase{impl: &i, command: "foo"}, "must be a function or a pointer to a function", ""))
|
2025-08-26 03:21:35 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("io params", func(t *testing.T) {
|
|
|
|
|
f := func(in io.Reader) string { b, _ := io.ReadAll(in); return string(b) }
|
|
|
|
|
t.Run("in", testExec(testCase{impl: f, stdin: "foo", command: "bar"}, "", "foo"))
|
|
|
|
|
g := func(a string) io.Reader { return bytes.NewBufferString(a) }
|
|
|
|
|
t.Run("out", testExec(testCase{impl: g, command: "foo bar"}, "", "bar"))
|
|
|
|
|
h := func(out io.Writer, a string) { out.Write([]byte(a)) }
|
|
|
|
|
t.Run("stdout param", testExec(testCase{impl: h, command: "foo bar"}, "", "bar"))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("struct param", func(t *testing.T) {
|
2025-08-26 14:12:18 +02:00
|
|
|
f := func(s struct{ Bar int }) int { return s.Bar }
|
2025-08-26 03:21:35 +02:00
|
|
|
t.Run("basic", testExec(testCase{impl: f, command: "foo --bar 42"}, "", "42"))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("basic types", func(t *testing.T) {
|
|
|
|
|
t.Run("signed int", func(t *testing.T) {
|
|
|
|
|
f := func(a int) int { return a }
|
|
|
|
|
t.Run("decimal", testExec(testCase{impl: f, command: "foo 42"}, "", "42"))
|
|
|
|
|
t.Run("hexa", testExec(testCase{impl: f, command: "foo 0x2a"}, "", "42"))
|
|
|
|
|
t.Run("octal", testExec(testCase{impl: f, command: "foo 052"}, "", "42"))
|
|
|
|
|
t.Run("binary", testExec(testCase{impl: f, command: "foo 0b101010"}, "", "42"))
|
|
|
|
|
t.Run("fail", testExec(testCase{impl: f, command: "foo bar"}, "expecting int", ""))
|
|
|
|
|
g := func(a int32) int32 { return a }
|
|
|
|
|
t.Run("sized", testExec(testCase{impl: g, command: "foo 42"}, "", "42"))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("unsigned int", func(t *testing.T) {
|
|
|
|
|
f := func(a uint) uint { return a }
|
|
|
|
|
t.Run("decimal", testExec(testCase{impl: f, command: "foo 42"}, "", "42"))
|
|
|
|
|
t.Run("hexa", testExec(testCase{impl: f, command: "foo 0x2a"}, "", "42"))
|
|
|
|
|
t.Run("octal", testExec(testCase{impl: f, command: "foo 052"}, "", "42"))
|
|
|
|
|
t.Run("binary", testExec(testCase{impl: f, command: "foo 0b101010"}, "", "42"))
|
|
|
|
|
t.Run("fail", testExec(testCase{impl: f, command: "foo bar"}, "expecting uint", ""))
|
|
|
|
|
g := func(a uint32) uint32 { return a }
|
|
|
|
|
t.Run("sized", testExec(testCase{impl: g, command: "foo 42"}, "", "42"))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("bool", func(t *testing.T) {
|
|
|
|
|
f := func(a bool) bool { return a }
|
|
|
|
|
t.Run("true", testExec(testCase{impl: f, command: "foo true"}, "", "true"))
|
|
|
|
|
t.Run("false", testExec(testCase{impl: f, command: "foo false"}, "", "false"))
|
|
|
|
|
t.Run("fail", testExec(testCase{impl: f, command: "foo yes"}, "expecting bool", ""))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("float", func(t *testing.T) {
|
|
|
|
|
f := func(a float64) float64 { return a }
|
|
|
|
|
t.Run("accept", testExec(testCase{impl: f, command: "foo 3.14"}, "", "3.14"))
|
|
|
|
|
t.Run("fail", testExec(testCase{impl: f, command: "foo bar"}, "expecting float", ""))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("string", func(t *testing.T) {
|
|
|
|
|
f := func(a string) string { return a }
|
|
|
|
|
t.Run("accept", testExec(testCase{impl: f, command: "foo bar"}, "", "bar"))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("interface", func(t *testing.T) {
|
|
|
|
|
f := func(a any) any { return a }
|
|
|
|
|
t.Run("any", testExec(testCase{impl: f, command: "foo bar"}, "", "bar"))
|
2025-08-26 14:12:18 +02:00
|
|
|
type i interface{ Foo() }
|
2025-08-26 03:21:35 +02:00
|
|
|
g := func(a i) any { return a }
|
2025-09-01 03:25:18 +02:00
|
|
|
t.Run("unscannable", testExec(testCase{impl: g, command: "foo bar"}, "unsupported parameter type", ""))
|
2025-08-26 03:21:35 +02:00
|
|
|
})
|
|
|
|
|
})
|
2025-09-04 01:09:24 +02:00
|
|
|
|
|
|
|
|
t.Run("compatible types", func(t *testing.T) {
|
2025-09-05 03:19:00 +02:00
|
|
|
type s0 struct {
|
|
|
|
|
FooBar int
|
2025-09-06 02:46:28 +02:00
|
|
|
Foo struct{ Bar bool }
|
2025-09-05 03:19:00 +02:00
|
|
|
}
|
2025-09-06 02:46:28 +02:00
|
|
|
|
|
|
|
|
type s0a struct {
|
|
|
|
|
FooBar bool
|
|
|
|
|
Foo struct{ Bar int }
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-05 03:19:00 +02:00
|
|
|
type s1 struct {
|
|
|
|
|
FooBar int
|
|
|
|
|
Foo struct{ Bar int }
|
|
|
|
|
}
|
2025-09-06 02:46:28 +02:00
|
|
|
|
|
|
|
|
f := func(a s0) string { return fmt.Sprint(a.FooBar) + fmt.Sprint(a.Foo.Bar) }
|
|
|
|
|
fa := func(a s0a) string { return fmt.Sprint(a.FooBar) + fmt.Sprint(a.Foo.Bar) }
|
2025-09-04 01:09:24 +02:00
|
|
|
g := func(a s1) int { return a.FooBar + a.Foo.Bar }
|
|
|
|
|
t.Run("incompatible", testExec(testCase{impl: f, command: "foo --foo-bar 42"}, "duplicate fields with different types", ""))
|
2025-09-06 02:46:28 +02:00
|
|
|
t.Run("incompatible", testExec(testCase{impl: fa, command: "foo --foo-bar 42"}, "duplicate fields with different types", ""))
|
2025-09-04 01:09:24 +02:00
|
|
|
t.Run("compatible", testExec(testCase{impl: g, command: "foo --foo-bar 42"}, "", "84"))
|
2025-09-06 02:46:28 +02:00
|
|
|
|
2025-09-05 03:19:00 +02:00
|
|
|
type s2 struct {
|
|
|
|
|
FooBar any
|
|
|
|
|
Foo struct{ Bar int }
|
|
|
|
|
}
|
2025-09-06 02:46:28 +02:00
|
|
|
|
|
|
|
|
type s2a struct {
|
|
|
|
|
FooBar int
|
|
|
|
|
Foo struct{ Bar any }
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-04 01:09:24 +02:00
|
|
|
h := func(a s2) int { return len(a.FooBar.(string)) + a.Foo.Bar }
|
|
|
|
|
t.Run("any interface", testExec(testCase{impl: h, command: "foo --foo-bar 42"}, "", "44"))
|
2025-09-06 02:46:28 +02:00
|
|
|
ha := func(a s2a) int { return a.FooBar + len(a.Foo.Bar.(string)) }
|
|
|
|
|
t.Run("any interface", testExec(testCase{impl: ha, command: "foo --foo-bar 42"}, "", "44"))
|
|
|
|
|
|
|
|
|
|
type s3 struct {
|
|
|
|
|
FooBar string
|
|
|
|
|
Foo struct{ Bar int }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
i := func(a s2) string { return fmt.Sprint(a.FooBar) + fmt.Sprint(a.Foo.Bar) }
|
|
|
|
|
t.Run("string", testExec(testCase{impl: i, command: "foo --foo-bar 42"}, "", "4242"))
|
|
|
|
|
|
|
|
|
|
type s4 struct {
|
|
|
|
|
FooBar bool
|
|
|
|
|
Foo struct{ Bar bool }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
j := func(a s4) string { return fmt.Sprint(a.FooBar) + fmt.Sprint(a.Foo.Bar) }
|
|
|
|
|
t.Run("bool", testExec(testCase{impl: j, command: "foo --foo-bar"}, "", "truetrue"))
|
|
|
|
|
|
|
|
|
|
type s5 struct {
|
|
|
|
|
FooBar time.Duration
|
|
|
|
|
Foo struct{ Bar time.Duration }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
k := func(a s5) string { return fmt.Sprint(a.FooBar) + fmt.Sprint(a.Foo.Bar) }
|
|
|
|
|
t.Run("duration and duration", testExec(testCase{impl: k, command: "foo --foo-bar 9s"}, "", "9s9s"))
|
|
|
|
|
|
|
|
|
|
type s6 struct {
|
|
|
|
|
FooBar time.Duration
|
|
|
|
|
Foo struct{ Bar int }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
l := func(a s6) string { return fmt.Sprint(a.FooBar) + fmt.Sprint(a.Foo.Bar) }
|
|
|
|
|
t.Run(
|
|
|
|
|
"duration and numeric",
|
|
|
|
|
testExec(testCase{impl: l, command: "foo --foo-bar 9000000000"}, "", "9s9000000000"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type s7 struct {
|
|
|
|
|
FooBar time.Time
|
|
|
|
|
Foo struct{ Bar time.Time }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m := func(a s7) string { return fmt.Sprint(a.FooBar.Unix()) + fmt.Sprint(a.Foo.Bar.Unix()) }
|
|
|
|
|
t.Run("time and time", testExec(testCase{impl: m, command: "foo --foo-bar 1757097011"}, "", "17570970111757097011"))
|
|
|
|
|
|
|
|
|
|
type s8 struct {
|
|
|
|
|
FooBar time.Time
|
|
|
|
|
Foo struct{ Bar int }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
n := func(a s8) string { return fmt.Sprint(a.FooBar.Unix()) + fmt.Sprint(a.Foo.Bar) }
|
|
|
|
|
t.Run(
|
|
|
|
|
"time and numeric",
|
|
|
|
|
testExec(testCase{impl: n, command: "foo --foo-bar 1757097011"}, "", "17570970111757097011"),
|
|
|
|
|
)
|
2025-09-04 01:09:24 +02:00
|
|
|
})
|
2025-09-05 03:19:00 +02:00
|
|
|
|
|
|
|
|
t.Run("bind failure", func(t *testing.T) {
|
|
|
|
|
f := func(s struct{ Foo time.Duration }) time.Duration { return s.Foo }
|
|
|
|
|
t.Run("no parse", testExec(testCase{impl: f, conf: "bar=baz", command: "foo"}, "", "0s"))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("indices of positional parameters", func(t *testing.T) {
|
|
|
|
|
t.Run(
|
|
|
|
|
"show function params",
|
|
|
|
|
testExec(testCase{impl: testlib.Foo, command: "foo help", contains: true}, "", "foo help"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
t.Run(
|
|
|
|
|
"skip non-positional params",
|
|
|
|
|
testExec(testCase{impl: testlib.Bar, command: "bar help", contains: true}, "", "bar help"),
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("scalar type string", func(t *testing.T) {
|
|
|
|
|
t.Run(
|
|
|
|
|
"show help with options",
|
|
|
|
|
testExec(
|
|
|
|
|
testCase{impl: testlib.Baz, command: "baz help", contains: true},
|
|
|
|
|
"",
|
|
|
|
|
"baz help",
|
|
|
|
|
"--foo int",
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
})
|
2025-08-26 03:21:35 +02:00
|
|
|
}
|