Repository navigation
Expand file tree
/
Copy pathpromoted_method_test.go
More file actions
149 lines (126 loc) · 4.7 KB
/
Copy pathpromoted_method_test.go
File metadata and controls
149 lines (126 loc) · 4.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package structpages
import (
"context"
"io"
"net/http"
"reflect"
"testing"
)
// These tests pin the behavior isPromotedMethod relies on and the invariant
// the call sites depend on. Note the caveat (golang/go#73883): a value-receiver
// method looked up through the *pointer* type also gets an autogenerated
// wrapper, so isPromotedMethod reports it as promoted. That is safe only
// because every call site scans both the value type and the pointer type —
// a non-promoted method always shows its real source location on at least
// one of the two.
type promotedEmbedded struct{}
func (promotedEmbedded) EmbeddedComp() component {
return fnComponent(func(context.Context, io.Writer) error { return nil })
}
type promotedEmbeddedPtr struct{}
func (*promotedEmbeddedPtr) EmbeddedPtrComp() component {
return fnComponent(func(context.Context, io.Writer) error { return nil })
}
type promotedHost struct {
promotedEmbedded
*promotedEmbeddedPtr
}
func (promotedHost) ValueComp() component {
return fnComponent(func(context.Context, io.Writer) error { return nil })
}
func (*promotedHost) PtrComp() component {
return fnComponent(func(context.Context, io.Writer) error { return nil })
}
func TestIsPromotedMethod(t *testing.T) {
st := reflect.TypeFor[promotedHost]()
pt := reflect.TypeFor[*promotedHost]()
get := func(t *testing.T, typ reflect.Type, name string) reflect.Method {
t.Helper()
m, ok := typ.MethodByName(name)
if !ok {
t.Fatalf("method %s not found on %s", name, typ)
}
return m
}
// Own methods report their real location on the type they're defined for.
if m := get(t, st, "ValueComp"); isPromotedMethod(&m) {
t.Error("value-receiver method on value type reported as promoted")
}
if m := get(t, pt, "PtrComp"); isPromotedMethod(&m) {
t.Error("pointer-receiver method on pointer type reported as promoted")
}
// Genuinely promoted methods are autogenerated on both types.
for _, typ := range []reflect.Type{st, pt} {
if m, ok := typ.MethodByName("EmbeddedComp"); ok && !isPromotedMethod(&m) {
t.Errorf("promoted EmbeddedComp on %s not detected as promoted", typ)
}
}
if m := get(t, pt, "EmbeddedPtrComp"); !isPromotedMethod(&m) {
t.Error("promoted pointer-receiver method not detected as promoted")
}
// The caveat itself: a value-receiver method seen through the pointer type
// has an autogenerated wrapper, so it reads as "promoted". If this ever
// changes, the two-type scan below becomes redundant (not broken).
if m := get(t, pt, "ValueComp"); !isPromotedMethod(&m) {
t.Log("note: value-receiver method via pointer type no longer autogenerated")
}
// The invariant all call sites rely on: every non-promoted method shows a
// real location on at least one of {value type, pointer type}.
for _, name := range []string{"ValueComp", "PtrComp"} {
found := false
for _, typ := range []reflect.Type{st, pt} {
if m, ok := typ.MethodByName(name); ok && !isPromotedMethod(&m) {
found = true
}
}
if !found {
t.Errorf("own method %s reads as promoted on both value and pointer type", name)
}
}
}
// TestProcessMethodsSkipsPromoted verifies the parse-level outcome: own
// component methods (value or pointer receiver) are registered, promoted ones
// are not.
func TestProcessMethodsSkipsPromoted(t *testing.T) {
type root struct {
p *promotedHost `route:"/host Host"`
}
pc, err := parsePageTree("/", &root{})
if err != nil {
t.Fatalf("parsePageTree failed: %v", err)
}
node := pc.root.Children[0]
for _, want := range []string{"ValueComp", "PtrComp"} {
if _, ok := node.Components[want]; !ok {
t.Errorf("own method %s missing from Components", want)
}
}
for _, skip := range []string{"EmbeddedComp", "EmbeddedPtrComp"} {
if _, ok := node.Components[skip]; ok {
t.Errorf("promoted method %s registered in Components", skip)
}
}
}
type valueServeHTTPPage struct{}
func (valueServeHTTPPage) ServeHTTP(http.ResponseWriter, *http.Request) {}
type embeddedHandler struct{}
func (embeddedHandler) ServeHTTP(http.ResponseWriter, *http.Request) {}
type promotedServeHTTPPage struct {
embeddedHandler
}
func TestHasServeHTTPWithReceiverVariants(t *testing.T) {
mk := func(v any) *PageNode { return &PageNode{Value: reflect.ValueOf(v)} }
// Value-receiver ServeHTTP must be detected even though its pointer-type
// wrapper reads as promoted (the caveat).
for _, v := range []any{valueServeHTTPPage{}, &valueServeHTTPPage{}} {
if !mk(v).hasServeHTTP() {
t.Errorf("hasServeHTTP() = false for %T with value-receiver ServeHTTP", v)
}
}
// Promoted ServeHTTP must not count.
for _, v := range []any{promotedServeHTTPPage{}, &promotedServeHTTPPage{}} {
if mk(v).hasServeHTTP() {
t.Errorf("hasServeHTTP() = true for %T with only promoted ServeHTTP", v)
}
}
}