-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathemit_test.go
More file actions
208 lines (191 loc) · 7.11 KB
/
Copy pathemit_test.go
File metadata and controls
208 lines (191 loc) · 7.11 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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
package quickcopy
import (
"go/format"
"strings"
"testing"
)
func TestGofmtFragmentNormalizes(t *testing.T) {
got, err := gofmtFragment("func f( ) {\nx:=1\n_=x\n}")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
want := "func f() {\n\tx := 1\n\t_ = x\n}\n"
if got != want {
t.Fatalf("got %q, want %q", got, want)
}
}
// 输出必须以单个换行结尾,拼接时才不会粘连。
func TestGofmtFragmentEndsWithSingleNewline(t *testing.T) {
got, err := gofmtFragment("func f() {}")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !strings.HasSuffix(got, "}\n") || strings.HasSuffix(got, "\n\n") {
t.Fatalf("want single trailing newline, got %q", got)
}
}
// 结果必须能被 format.Source 原样接受,即已经是规范格式。
func TestGofmtFragmentResultIsCanonical(t *testing.T) {
got, err := gofmtFragment("func f( ) {\nx:=1\n_=x\n}")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
again, err := format.Source([]byte("package p\n\n" + got))
if err != nil {
t.Fatalf("结果不是合法 Go: %v", err)
}
if string(again) != "package p\n\n"+got {
t.Fatalf("结果不是规范格式:\n%q", got)
}
}
// 语法错误必须报错,且错误信息带上出错的片段,方便定位是本工具的渲染 bug。
func TestGofmtFragmentRejectsInvalidGo(t *testing.T) {
_, err := gofmtFragment("func f( {")
if err == nil {
t.Fatal("want error for invalid Go, got nil")
}
if !strings.Contains(err.Error(), "func f( {") {
t.Fatalf("错误信息应带上出错的片段, got %v", err)
}
}
// 片段含 build 约束注释时,go/printer 会把它提升到 package 子句之前,
// 此时按「删第一行」去壳会静默泄漏 package 子句。必须报错而不是返回坏片段。
func TestGofmtFragmentRejectsBuildConstraint(t *testing.T) {
_, err := gofmtFragment("//go:build linux\n\nfunc f() {}")
if err == nil {
t.Fatal("want error for build constraint, got nil")
}
}
// 空输入返回空串,拼接时不贡献空行。
func TestGofmtFragmentEmptyInput(t *testing.T) {
got, err := gofmtFragment("")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != "" {
t.Fatalf("got %q, want empty", got)
}
}
// 纯空白输入同样返回空串,且不报错。
func TestGofmtFragmentWhitespaceOnlyInput(t *testing.T) {
got, err := gofmtFragment(" \n \n")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != "" {
t.Fatalf("got %q, want empty", got)
}
}
func sampleInfo() CopyFuncInfo {
return CopyFuncInfo{
FuncName: "CopyData",
SrcVar: "src",
DstVar: "dst",
SrcType: "Source",
DstType: "Destination",
Fields: []FieldMapping{
{SrcField: "Name", DstField: "Name", SrcType: "string", DstType: "string"},
{SrcField: "Age", DstField: "Age", Conversion: "fmt.Sprint", SrcType: "int", DstType: "string"},
{SrcField: "Person", DstField: "Person", ConversionFunc: "copyDestPersonFromSourcePerson", IsStructCopy: true, SrcType: "Person", DstType: "Person"},
{DstField: "Tags", Conversion: "copySliceStringFromSliceInt", IsSlice: true, SrcType: "[]int", DstType: "[]string"},
},
}
}
// 被标注的函数:doc 必须逐字保留,重跑才认得出来。
func TestRenderCopyFuncKeepsDoc(t *testing.T) {
got, err := renderCopyFunc(sampleInfo(), "// :quickcopy\n// FullName=FirstName\n")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !strings.HasPrefix(got, "// :quickcopy\n// FullName=FirstName\nfunc CopyData(") {
t.Fatalf("doc 未被逐字保留:\n%s", got)
}
}
// 注释密度规则:只有发生了转换的赋值才加行尾说明。
func TestRenderCopyFuncCommentDensity(t *testing.T) {
got, err := renderCopyFunc(sampleInfo(), "// :quickcopy\n")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !strings.Contains(got, "dst.Age = fmt.Sprint(src.Age) // int -> string") {
t.Errorf("转换字段应带行尾注释:\n%s", got)
}
if !strings.Contains(got, "copyDestPersonFromSourcePerson(&dst.Person, &src.Person)") {
t.Errorf("嵌入字段应调用转换函数:\n%s", got)
}
if strings.Contains(got, "copyDestPersonFromSourcePerson(&dst.Person, &src.Person) //") {
t.Errorf("嵌入字段的调用本身已说明意图, 不应再加注释:\n%s", got)
}
if !strings.Contains(got, "*dst = copySliceStringFromSliceInt(*src) // []int -> []string") {
t.Errorf("切片转换应带行尾注释:\n%s", got)
}
if strings.Contains(got, "dst.Name = src.Name //") {
t.Errorf("直接赋值不应带注释:\n%s", got)
}
}
// 渲染结果必须已经是规范格式。
func TestRenderCopyFuncIsCanonical(t *testing.T) {
got, err := renderCopyFunc(sampleInfo(), "// :quickcopy\n")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !strings.HasSuffix(got, "\n") || strings.HasSuffix(got, "\n\n") {
t.Fatalf("应以单个换行结尾, got %q", got)
}
if strings.Contains(got, "\n\n") {
t.Fatalf("不应有连续空行:\n%s", got)
}
}
// 助手函数带生成标识注释。
func TestRenderHelperAddsMarkerComment(t *testing.T) {
got, err := renderHelper(sampleInfo())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !strings.HasPrefix(got, "// CopyData 是自动生成的拷贝函数\nfunc CopyData(") {
t.Fatalf("助手函数应带生成标识注释:\n%s", got)
}
}
// 渲染不出合法 Go 时必须报错,而不是产出坏代码。
func TestRenderCopyFuncReportsBadConversion(t *testing.T) {
info := sampleInfo()
info.Fields = []FieldMapping{
{SrcField: "X", DstField: "X", Conversion: "this is not go (", SrcType: "int", DstType: "string"},
}
if _, err := renderCopyFunc(info, ""); err == nil {
t.Fatal("want error for invalid conversion, got nil")
}
}
// 缺少转换函数的切片映射必须报错。否则渲染出 *dst = (*src) —— 它语法合法,
// format.Source 查不出来,却是个类型错误,等于静默写出编译不过的文件。
func TestRenderFuncSourceRejectsSliceWithoutConversion(t *testing.T) {
info := CopyFuncInfo{
FuncName: "copyPersonsSlice", SrcVar: "src", DstVar: "dst",
SrcType: "[]SourcePerson", DstType: "[]DestPerson",
Fields: []FieldMapping{{IsSlice: true, SrcType: "[]SourcePerson", DstType: "[]DestPerson"}},
}
if _, err := renderFuncSource(info); err == nil {
t.Fatal("want error for slice mapping without conversion, got nil")
}
}
// 同理,嵌入映射缺转换函数名时会渲染出 (dst.X, src.X)。
func TestRenderFuncSourceRejectsEmbeddedWithoutConversionFunc(t *testing.T) {
info := CopyFuncInfo{
FuncName: "copyThing", SrcVar: "src", DstVar: "dst",
SrcType: "Source", DstType: "Dest",
Fields: []FieldMapping{{SrcField: "P", DstField: "P", IsStructCopy: true, SrcType: "Person", DstType: "Person"}},
}
if _, err := renderFuncSource(info); err == nil {
t.Fatal("want error for embedded mapping without conversion func, got nil")
}
}
// doc 不以换行结尾时要补一个,否则片段首行会被吞进注释行。
func TestRenderCopyFuncAcceptsDocWithoutTrailingNewline(t *testing.T) {
got, err := renderCopyFunc(sampleInfo(), "// :quickcopy")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !strings.HasPrefix(got, "// :quickcopy\nfunc CopyData(") {
t.Fatalf("doc 未正确换行:\n%s", got)
}
}