help_test.go raw
1 package btcjson_test
2
3 import (
4 "reflect"
5 "testing"
6
7 "github.com/p9c/p9/pkg/btcjson"
8 )
9
10 // TestHelpReflectInternals ensures the various help functions which deal with reflect types work as expected for
11 // various Go types.
12 func TestHelpReflectInternals(t *testing.T) {
13 t.Parallel()
14 tests := []struct {
15 name string
16 reflectType reflect.Type
17 indentLevel int
18 key string
19 examples []string
20 isComplex bool
21 help string
22 isInvalid bool
23 }{
24 {
25 name: "int",
26 reflectType: reflect.TypeOf(0),
27 key: "json-type-numeric",
28 examples: []string{"n"},
29 help: "n (json-type-numeric) fdk",
30 },
31 {
32 name: "*int",
33 reflectType: reflect.TypeOf((*int)(nil)),
34 key: "json-type-value",
35 examples: []string{"n"},
36 help: "n (json-type-value) fdk",
37 isInvalid: true,
38 },
39 {
40 name: "int8",
41 reflectType: reflect.TypeOf(int8(0)),
42 key: "json-type-numeric",
43 examples: []string{"n"},
44 help: "n (json-type-numeric) fdk",
45 },
46 {
47 name: "int16",
48 reflectType: reflect.TypeOf(int16(0)),
49 key: "json-type-numeric",
50 examples: []string{"n"},
51 help: "n (json-type-numeric) fdk",
52 },
53 {
54 name: "int32",
55 reflectType: reflect.TypeOf(int32(0)),
56 key: "json-type-numeric",
57 examples: []string{"n"},
58 help: "n (json-type-numeric) fdk",
59 },
60 {
61 name: "int64",
62 reflectType: reflect.TypeOf(int64(0)),
63 key: "json-type-numeric",
64 examples: []string{"n"},
65 help: "n (json-type-numeric) fdk",
66 },
67 {
68 name: "uint",
69 reflectType: reflect.TypeOf(uint(0)),
70 key: "json-type-numeric",
71 examples: []string{"n"},
72 help: "n (json-type-numeric) fdk",
73 },
74 {
75 name: "uint8",
76 reflectType: reflect.TypeOf(uint8(0)),
77 key: "json-type-numeric",
78 examples: []string{"n"},
79 help: "n (json-type-numeric) fdk",
80 },
81 {
82 name: "uint16",
83 reflectType: reflect.TypeOf(uint16(0)),
84 key: "json-type-numeric",
85 examples: []string{"n"},
86 help: "n (json-type-numeric) fdk",
87 },
88 {
89 name: "uint32",
90 reflectType: reflect.TypeOf(uint32(0)),
91 key: "json-type-numeric",
92 examples: []string{"n"},
93 help: "n (json-type-numeric) fdk",
94 },
95 {
96 name: "uint64",
97 reflectType: reflect.TypeOf(uint64(0)),
98 key: "json-type-numeric",
99 examples: []string{"n"},
100 help: "n (json-type-numeric) fdk",
101 },
102 {
103 name: "float32",
104 reflectType: reflect.TypeOf(float32(0)),
105 key: "json-type-numeric",
106 examples: []string{"n.nnn"},
107 help: "n.nnn (json-type-numeric) fdk",
108 },
109 {
110 name: "float64",
111 reflectType: reflect.TypeOf(float64(0)),
112 key: "json-type-numeric",
113 examples: []string{"n.nnn"},
114 help: "n.nnn (json-type-numeric) fdk",
115 },
116 {
117 name: "string",
118 reflectType: reflect.TypeOf(""),
119 key: "json-type-string",
120 examples: []string{`"json-example-string"`},
121 help: "\"json-example-string\" (json-type-string) fdk",
122 },
123 {
124 name: "bool",
125 reflectType: reflect.TypeOf(true),
126 key: "json-type-bool",
127 examples: []string{"json-example-bool"},
128 help: "json-example-bool (json-type-bool) fdk",
129 },
130 {
131 name: "array of int",
132 reflectType: reflect.TypeOf([1]int{0}),
133 key: "json-type-arrayjson-type-numeric",
134 examples: []string{"[n,...]"},
135 help: "[n,...] (json-type-arrayjson-type-numeric) fdk",
136 },
137 {
138 name: "slice of int",
139 reflectType: reflect.TypeOf([]int{0}),
140 key: "json-type-arrayjson-type-numeric",
141 examples: []string{"[n,...]"},
142 help: "[n,...] (json-type-arrayjson-type-numeric) fdk",
143 },
144 {
145 name: "struct",
146 reflectType: reflect.TypeOf(struct{}{}),
147 key: "json-type-object",
148 examples: []string{"{", "}\t\t"},
149 isComplex: true,
150 help: "{\n} ",
151 },
152 {
153 name: "struct indent level 1",
154 reflectType: reflect.TypeOf(struct{ field int }{}),
155 indentLevel: 1,
156 key: "json-type-object",
157 examples: []string{
158 " \"field\": n,\t(json-type-numeric)\t-field",
159 " },\t\t",
160 },
161 help: "{\n" +
162 " \"field\": n, (json-type-numeric) -field\n" +
163 "} ",
164 isComplex: true,
165 },
166 {
167 name: "array of struct indent level 0",
168 reflectType: func() reflect.Type {
169 type s struct{ field int }
170 return reflect.TypeOf([]s{})
171 }(),
172 key: "json-type-arrayjson-type-object",
173 examples: []string{
174 "[{",
175 " \"field\": n,\t(json-type-numeric)\ts-field",
176 "},...]",
177 },
178 help: "[{\n" +
179 " \"field\": n, (json-type-numeric) s-field\n" +
180 "},...]",
181 isComplex: true,
182 },
183 {
184 name: "array of struct indent level 1",
185 reflectType: func() reflect.Type {
186 type s struct {
187 field int
188 }
189 return reflect.TypeOf([]s{})
190 }(),
191 indentLevel: 1,
192 key: "json-type-arrayjson-type-object",
193 examples: []string{
194 " \"field\": n,\t(json-type-numeric)\ts-field",
195 " },...],\t\t",
196 },
197 help: "[{\n" +
198 " \"field\": n, (json-type-numeric) s-field\n" +
199 "},...]",
200 isComplex: true,
201 },
202 {
203 name: "map",
204 reflectType: reflect.TypeOf(map[string]string{}),
205 key: "json-type-object",
206 examples: []string{"{",
207 " \"fdk--key\": fdk--value, (json-type-object) fdk--desc",
208 " ...", "}",
209 },
210 help: "{\n" +
211 " \"fdk--key\": fdk--value, (json-type-object) fdk--desc\n" +
212 " ...\n" +
213 "}",
214 isComplex: true,
215 },
216 {
217 name: "complex",
218 reflectType: reflect.TypeOf(complex64(0)),
219 key: "json-type-value",
220 examples: []string{"json-example-unknown"},
221 help: "json-example-unknown (json-type-value) fdk",
222 isInvalid: true,
223 },
224 }
225 xT := func(key string) string {
226 return key
227 }
228 t.Logf("Running %d tests", len(tests))
229 for i, test := range tests {
230 // Ensure the description key is the expected value.
231 key := btcjson.TstReflectTypeToJSONType(xT, test.reflectType)
232 if key != test.key {
233 t.Errorf("Test #%d (%s) unexpected key - got: %v, "+
234 "want: %v", i, test.name, key, test.key,
235 )
236 continue
237 }
238 // Ensure the generated example is as expected.
239 examples, isComplex := btcjson.TstReflectTypeToJSONExample(xT,
240 test.reflectType, test.indentLevel, "fdk",
241 )
242 if isComplex != test.isComplex {
243 t.Errorf("Test #%d (%s) unexpected isComplex - got: %v, "+
244 "want: %v", i, test.name, isComplex,
245 test.isComplex,
246 )
247 continue
248 }
249 if len(examples) != len(test.examples) {
250 t.Errorf("Test #%d (%s) unexpected result length - "+
251 "got: %v, want: %v", i, test.name, len(examples),
252 len(test.examples),
253 )
254 continue
255 }
256 for j, example := range examples {
257 if example != test.examples[j] {
258 t.Errorf("Test #%d (%s) example #%d unexpected "+
259 "example - got: %v, want: %v", i,
260 test.name, j, example, test.examples[j],
261 )
262 continue
263 }
264 }
265 // Ensure the generated result type help is as expected.
266 helpText := btcjson.TstResultTypeHelp(xT, test.reflectType, "fdk")
267 if helpText != test.help {
268 t.Errorf("Test #%d (%s) unexpected result help - "+
269 "got: %v, want: %v", i, test.name, helpText,
270 test.help,
271 )
272 continue
273 }
274 isValid := btcjson.TstIsValidResultType(test.reflectType.Kind())
275 if isValid != !test.isInvalid {
276 t.Errorf("Test #%d (%s) unexpected result type validity "+
277 "- got: %v", i, test.name, isValid,
278 )
279 continue
280 }
281 }
282 }
283
284 // TestResultStructHelp ensures the expected help text format is returned for various Go struct types.
285 func TestResultStructHelp(t *testing.T) {
286 t.Parallel()
287 tests := []struct {
288 name string
289 reflectType reflect.Type
290 expected []string
291 }{
292 {
293 name: "empty struct",
294 reflectType: func() reflect.Type {
295 type s struct{}
296 return reflect.TypeOf(s{})
297 }(),
298 expected: nil,
299 },
300 {
301 name: "struct with primitive field",
302 reflectType: func() reflect.Type {
303 type s struct{ field int }
304 return reflect.TypeOf(s{})
305 }(),
306 expected: []string{
307 "\"field\": n,\t(json-type-numeric)\ts-field",
308 },
309 },
310 {
311 name: "struct with primitive field and json tag",
312 reflectType: func() reflect.Type {
313 type s struct {
314 Field int `json:"f"`
315 }
316 return reflect.TypeOf(s{})
317 }(),
318 expected: []string{
319 "\"f\": n,\t(json-type-numeric)\ts-f",
320 },
321 },
322 {
323 name: "struct with array of primitive field",
324 reflectType: func() reflect.Type {
325 type s struct{ field []int }
326 return reflect.TypeOf(s{})
327 }(),
328 expected: []string{
329 "\"field\": [n,...],\t(json-type-arrayjson-type-numeric)\ts-field",
330 },
331 },
332 {
333 name: "struct with sub-struct field",
334 reflectType: func() reflect.Type {
335 type s2 struct {
336 subField int
337 }
338 type s struct {
339 field s2
340 }
341 return reflect.TypeOf(s{})
342 }(),
343 expected: []string{
344 "\"field\": {\t(json-type-object)\ts-field",
345 "{",
346 " \"subfield\": n,\t(json-type-numeric)\ts2-subfield",
347 "}\t\t",
348 },
349 },
350 {
351 name: "struct with sub-struct field pointer",
352 reflectType: func() reflect.Type {
353 type s2 struct {
354 subField int
355 }
356 type s struct {
357 field *s2
358 }
359 return reflect.TypeOf(s{})
360 }(),
361 expected: []string{
362 "\"field\": {\t(json-type-object)\ts-field",
363 "{",
364 " \"subfield\": n,\t(json-type-numeric)\ts2-subfield",
365 "}\t\t",
366 },
367 },
368 {
369 name: "struct with array of structs field",
370 reflectType: func() reflect.Type {
371 type s2 struct {
372 subField int
373 }
374 type s struct {
375 field []s2
376 }
377 return reflect.TypeOf(s{})
378 }(),
379 expected: []string{
380 "\"field\": [{\t(json-type-arrayjson-type-object)\ts-field",
381 "[{",
382 " \"subfield\": n,\t(json-type-numeric)\ts2-subfield",
383 "},...]",
384 },
385 },
386 }
387 xT := func(key string) string {
388 return key
389 }
390 t.Logf("Running %d tests", len(tests))
391 for i, test := range tests {
392 results := btcjson.TstResultStructHelp(xT, test.reflectType, 0)
393 if len(results) != len(test.expected) {
394 t.Errorf("Test #%d (%s) unexpected result length - "+
395 "got: %v, want: %v", i, test.name, len(results),
396 len(test.expected),
397 )
398 continue
399 }
400 for j, result := range results {
401 if result != test.expected[j] {
402 t.Errorf("Test #%d (%s) result #%d unexpected "+
403 "result - got: %v, want: %v", i,
404 test.name, j, result, test.expected[j],
405 )
406 continue
407 }
408 }
409 }
410 }
411
412 // TestHelpArgInternals ensures the various help functions which deal with arguments work as expected for various argument types.
413 func TestHelpArgInternals(t *testing.T) {
414 t.Parallel()
415 tests := []struct {
416 name string
417 method string
418 reflectType reflect.Type
419 defaults map[int]reflect.Value
420 help string
421 }{
422 {
423 name: "command with no args",
424 method: "test",
425 reflectType: func() reflect.Type {
426 type s struct{}
427 return reflect.TypeOf((*s)(nil))
428 }(),
429 defaults: nil,
430 help: "",
431 },
432 {
433 name: "command with one required arg",
434 method: "test",
435 reflectType: func() reflect.Type {
436 type s struct {
437 Field int
438 }
439 return reflect.TypeOf((*s)(nil))
440 }(),
441 defaults: nil,
442 help: "1. field (json-type-numeric, help-required) test-field\n",
443 },
444 {
445 name: "command with one optional arg, no default",
446 method: "test",
447 reflectType: func() reflect.Type {
448 type s struct {
449 Optional *int
450 }
451 return reflect.TypeOf((*s)(nil))
452 }(),
453 defaults: nil,
454 help: "1. optional (json-type-numeric, help-optional) test-optional\n",
455 },
456 {
457 name: "command with one optional arg with default",
458 method: "test",
459 reflectType: func() reflect.Type {
460 type s struct {
461 Optional *string
462 }
463 return reflect.TypeOf((*s)(nil))
464 }(),
465 defaults: func() map[int]reflect.Value {
466 defVal := "test"
467 return map[int]reflect.Value{
468 0: reflect.ValueOf(&defVal),
469 }
470 }(),
471 help: "1. optional (json-type-string, help-optional, help-default=\"test\") test-optional\n",
472 },
473 {
474 name: "command with struct field",
475 method: "test",
476 reflectType: func() reflect.Type {
477 type s2 struct {
478 F int8
479 }
480 type s struct {
481 Field s2
482 }
483 return reflect.TypeOf((*s)(nil))
484 }(),
485 defaults: nil,
486 help: "1. field (json-type-object, help-required) test-field\n" +
487 "{\n" +
488 " \"f\": n, (json-type-numeric) s2-f\n" +
489 "} \n",
490 },
491 {
492 name: "command with map field",
493 method: "test",
494 reflectType: func() reflect.Type {
495 type s struct {
496 Field map[string]float64
497 }
498 return reflect.TypeOf((*s)(nil))
499 }(),
500 defaults: nil,
501 help: "1. field (json-type-object, help-required) test-field\n" +
502 "{\n" +
503 " \"test-field--key\": test-field--value, (json-type-object) test-field--desc\n" +
504 " ...\n" +
505 "}\n",
506 },
507 {
508 name: "command with slice of primitives field",
509 method: "test",
510 reflectType: func() reflect.Type {
511 type s struct {
512 Field []int64
513 }
514 return reflect.TypeOf((*s)(nil))
515 }(),
516 defaults: nil,
517 help: "1. field (json-type-arrayjson-type-numeric, help-required) test-field\n",
518 },
519 {
520 name: "command with slice of structs field",
521 method: "test",
522 reflectType: func() reflect.Type {
523 type s2 struct {
524 F int64
525 }
526 type s struct {
527 Field []s2
528 }
529 return reflect.TypeOf((*s)(nil))
530 }(),
531 defaults: nil,
532 help: "1. field (json-type-arrayjson-type-object, help-required) test-field\n" +
533 "[{\n" +
534 " \"f\": n, (json-type-numeric) s2-f\n" +
535 "},...]\n",
536 },
537 }
538 xT := func(key string) string {
539 return key
540 }
541 t.Logf("Running %d tests", len(tests))
542 for i, test := range tests {
543 help := btcjson.TstArgHelp(xT, test.reflectType, test.defaults,
544 test.method,
545 )
546 if help != test.help {
547 t.Errorf("Test #%d (%s) unexpected help - got:\n%v\n"+
548 "want:\n%v", i, test.name, help, test.help,
549 )
550 continue
551 }
552 }
553 }
554
555 // TestMethodHelp ensures the method help function works as expected for various command structs.
556 func TestMethodHelp(t *testing.T) {
557 t.Parallel()
558 tests := []struct {
559 name string
560 method string
561 reflectType reflect.Type
562 defaults map[int]reflect.Value
563 resultTypes []interface{}
564 help string
565 }{
566 {
567 name: "command with no args or results",
568 method: "test",
569 reflectType: func() reflect.Type {
570 type s struct{}
571 return reflect.TypeOf((*s)(nil))
572 }(),
573 help: "test\n\ntest--synopsis\n" +
574 "help-arguments:\nhelp-arguments-none\n" +
575 "help-result:\nhelp-result-nothing\n",
576 },
577 {
578 name: "command with no args and one primitive result",
579 method: "test",
580 reflectType: func() reflect.Type {
581 type s struct{}
582 return reflect.TypeOf((*s)(nil))
583 }(),
584 resultTypes: []interface{}{(*int64)(nil)},
585 help: "test\n\ntest--synopsis\n" +
586 "help-arguments:\nhelp-arguments-none\n" +
587 "help-result:\nn (json-type-numeric) test--result0\n",
588 },
589 {
590 name: "command with no args and two results",
591 method: "test",
592 reflectType: func() reflect.Type {
593 type s struct{}
594 return reflect.TypeOf((*s)(nil))
595 }(),
596 resultTypes: []interface{}{(*int64)(nil), nil},
597 help: "test\n\ntest--synopsis\n" +
598 "help-arguments:\nhelp-arguments-none\n" +
599 "help-result (test--condition0):\nn (json-type-numeric) test--result0\n" +
600 "help-result (test--condition1):\nhelp-result-nothing\n",
601 },
602 {
603 name: "command with primitive arg and no results",
604 method: "test",
605 reflectType: func() reflect.Type {
606 type s struct {
607 Field bool
608 }
609 return reflect.TypeOf((*s)(nil))
610 }(),
611 help: "test field\n\ntest--synopsis\n" +
612 "help-arguments:\n1. field (json-type-bool, help-required) test-field\n" +
613 "help-result:\nhelp-result-nothing\n",
614 },
615 {
616 name: "command with primitive optional and no results",
617 method: "test",
618 reflectType: func() reflect.Type {
619 type s struct {
620 Field *bool
621 }
622 return reflect.TypeOf((*s)(nil))
623 }(),
624 help: "test (field)\n\ntest--synopsis\n" +
625 "help-arguments:\n1. field (json-type-bool, help-optional) test-field\n" +
626 "help-result:\nhelp-result-nothing\n",
627 },
628 }
629 xT := func(key string) string {
630 return key
631 }
632 t.Logf("Running %d tests", len(tests))
633 for i, test := range tests {
634 help := btcjson.TestMethodHelp(xT, test.reflectType,
635 test.defaults, test.method, test.resultTypes,
636 )
637 if help != test.help {
638 t.Errorf("Test #%d (%s) unexpected help - got:\n%v\n"+
639 "want:\n%v", i, test.name, help, test.help,
640 )
641 continue
642 }
643 }
644 }
645
646 // TestGenerateHelpErrors ensures the GenerateHelp function returns the expected errors.
647 func TestGenerateHelpErrors(t *testing.T) {
648 t.Parallel()
649 tests := []struct {
650 name string
651 method string
652 resultTypes []interface{}
653 e btcjson.GeneralError
654 }{
655 {
656 name: "unregistered command",
657 method: "boguscommand",
658 e: btcjson.GeneralError{ErrorCode: btcjson.ErrUnregisteredMethod},
659 },
660 {
661 name: "non-pointer result type",
662 method: "help",
663 resultTypes: []interface{}{0},
664 e: btcjson.GeneralError{ErrorCode: btcjson.ErrInvalidType},
665 },
666 {
667 name: "invalid result type",
668 method: "help",
669 resultTypes: []interface{}{(*complex64)(nil)},
670 e: btcjson.GeneralError{ErrorCode: btcjson.ErrInvalidType},
671 },
672 {
673 name: "missing description",
674 method: "help",
675 resultTypes: []interface{}{(*string)(nil), nil},
676 e: btcjson.GeneralError{ErrorCode: btcjson.ErrMissingDescription},
677 },
678 }
679 t.Logf("Running %d tests", len(tests))
680 for i, test := range tests {
681 _, e := btcjson.GenerateHelp(test.method, nil,
682 test.resultTypes...,
683 )
684 if reflect.TypeOf(e) != reflect.TypeOf(test.e) {
685 t.Errorf("Test #%d (%s) wrong error - got %T (%v), "+
686 "want %T", i, test.name, e, e, test.e,
687 )
688 continue
689 }
690 gotErrorCode := e.(btcjson.GeneralError).ErrorCode
691 if gotErrorCode != test.e.ErrorCode {
692 t.Errorf("Test #%d (%s) mismatched error code - got "+
693 "%v (%v), want %v", i, test.name, gotErrorCode,
694 e, test.e.ErrorCode,
695 )
696 continue
697 }
698 }
699 }
700
701 // TestGenerateHelp performs a very basic test to ensure GenerateHelp is working as expected. The internal are testd much more thoroughly in other tests, so there is no need to add more tests here.
702 func TestGenerateHelp(t *testing.T) {
703 t.Parallel()
704 descs := map[string]string{
705 "help--synopsis": "test",
706 "help-command": "test",
707 }
708 help, e := btcjson.GenerateHelp("help", descs)
709 if e != nil {
710 t.Fatalf("GenerateHelp: unexpected error: %v", e)
711 }
712 wantHelp := "help (\"command\")\n" +
713 "test\n\nArguments:\n1. command (string, optional) test\n" +
714 "Result:\nNothing\n"
715 if help != wantHelp {
716 t.Fatalf("GenerateHelp: unexpected help - got\n%v\nwant\n%v",
717 help, wantHelp,
718 )
719 }
720 }
721