Skip to content

Commit b1c3718

Browse files
Implement randomized schema test
Resolves #183 This adds a new TestRandomizedSchema that generates a schema using random number generator, then compiles and runs the tests for the schema. The new test revealed a number of bugs. I fixed a few of the smaller ones. There are more bugs, but I don't want to overload this PR. For now TestRandomizedSchema does not fail the build when it finds a buggy schema, but merely logs it. After this PR is merged I will fix every failing schema one by one.
1 parent cc369ad commit b1c3718

File tree

7 files changed

+541
-50
lines changed

7 files changed

+541
-50
lines changed

go/pkg/schema/schema.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package schema
33
import (
44
"errors"
55
"fmt"
6+
"sort"
7+
"strings"
68
)
79

810
// Schema is a STEF schema description, serializable in JSON format.
@@ -471,6 +473,130 @@ func computeRecursiveType(typ FieldType, stack *recurseStack) {
471473
panic("unknown type")
472474
}
473475

476+
// PrettyPrint outputs the schema in SDL format.
477+
func (d *Schema) PrettyPrint() string {
478+
var out []string
479+
480+
// Print package declaration
481+
out = append(out, fmt.Sprintf("package %s", strings.Join(d.PackageName, ".")))
482+
483+
// Print enums
484+
for _, enum := range sortedList(d.Enums) {
485+
out = append(out, prettyPrintEnum(enum))
486+
}
487+
488+
// Print multimaps
489+
for _, mm := range sortedList(d.Multimaps) {
490+
out = append(out, prettyPrintMultimap(mm))
491+
}
492+
493+
// Print structs and oneofs
494+
for _, s := range sortedList(d.Structs) {
495+
out = append(out, prettyPrintStruct(s))
496+
}
497+
498+
return strings.Join(out, "\n\n")
499+
}
500+
501+
func sortedList[T any](m map[string]*T) []*T {
502+
var names []string
503+
for k := range m {
504+
names = append(names, k)
505+
}
506+
sort.Strings(names)
507+
var out []*T
508+
for _, n := range names {
509+
out = append(out, m[n])
510+
}
511+
return out
512+
}
513+
514+
func prettyPrintEnum(e *Enum) string {
515+
out := fmt.Sprintf("enum %s {", e.Name)
516+
for _, f := range e.Fields {
517+
out += fmt.Sprintf("\n %s = %d", f.Name, f.Value)
518+
}
519+
out += "\n}"
520+
return out
521+
}
522+
523+
func prettyPrintMultimap(m *Multimap) string {
524+
out := fmt.Sprintf("multimap %s {\n", m.Name)
525+
out += " key " + prettyPrintFieldType(&m.Key.Type)
526+
if dict := m.Key.Type.DictName; dict != "" {
527+
out += fmt.Sprintf(" dict(%s)", dict)
528+
}
529+
out += "\n"
530+
out += " value " + prettyPrintFieldType(&m.Value.Type)
531+
if dict := m.Value.Type.DictName; dict != "" {
532+
out += fmt.Sprintf(" dict(%s)", dict)
533+
}
534+
out += "\n}"
535+
return out
536+
}
537+
538+
func prettyPrintStruct(s *Struct) string {
539+
var out string
540+
if s.OneOf {
541+
out = fmt.Sprintf("oneof %s {", s.Name)
542+
} else {
543+
out = fmt.Sprintf("struct %s", s.Name)
544+
if s.DictName != "" {
545+
out += fmt.Sprintf(" dict(%s)", s.DictName)
546+
}
547+
if s.IsRoot {
548+
out += " root"
549+
}
550+
out += " {"
551+
}
552+
for _, f := range s.Fields {
553+
out += "\n " + prettyPrintStructField(f)
554+
}
555+
out += "\n}"
556+
return out
557+
}
558+
559+
func prettyPrintStructField(f *StructField) string {
560+
ft := prettyPrintFieldType(&f.FieldType)
561+
out := fmt.Sprintf("%s %s", f.Name, ft)
562+
if f.DictName != "" {
563+
out += fmt.Sprintf(" dict(%s)", f.DictName)
564+
}
565+
if f.Optional {
566+
out += " optional"
567+
}
568+
return out
569+
}
570+
571+
func prettyPrintFieldType(ft *FieldType) string {
572+
switch {
573+
case ft.Primitive != nil:
574+
switch ft.Primitive.Type {
575+
case PrimitiveTypeInt64:
576+
return "int64"
577+
case PrimitiveTypeUint64:
578+
return "uint64"
579+
case PrimitiveTypeFloat64:
580+
return "float64"
581+
case PrimitiveTypeBool:
582+
return "bool"
583+
case PrimitiveTypeString:
584+
return "string"
585+
case PrimitiveTypeBytes:
586+
return "bytes"
587+
}
588+
case ft.Array != nil:
589+
return "[]" + prettyPrintFieldType(&ft.Array.ElemType)
590+
case ft.Struct != "":
591+
return ft.Struct
592+
case ft.MultiMap != "":
593+
return ft.MultiMap
594+
case ft.Enum != "":
595+
return ft.Enum
596+
}
597+
return "unknown"
598+
}
599+
474600
type Struct struct {
475601
Name string `json:"name,omitempty"`
476602
OneOf bool `json:"oneof,omitempty"`
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package schema
2+
3+
import (
4+
"strings"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestPrettyPrint_SimpleStruct(t *testing.T) {
11+
schema := &Schema{
12+
PackageName: []string{"com", "example", "test"},
13+
Structs: map[string]*Struct{
14+
"Person": {
15+
Name: "Person",
16+
Fields: []*StructField{
17+
{Name: "Name", FieldType: FieldType{Primitive: &PrimitiveType{Type: PrimitiveTypeString}}},
18+
{Name: "Age", FieldType: FieldType{Primitive: &PrimitiveType{Type: PrimitiveTypeUint64}}},
19+
},
20+
},
21+
},
22+
}
23+
24+
expected := `package com.example.test
25+
26+
struct Person {
27+
Name string
28+
Age uint64
29+
}`
30+
actual := strings.TrimSpace(schema.PrettyPrint())
31+
require.Equal(t, expected, actual)
32+
}
33+
34+
func TestPrettyPrint_EnumAndMultimap(t *testing.T) {
35+
schema := &Schema{
36+
PackageName: []string{"com", "example", "test"},
37+
Enums: map[string]*Enum{
38+
"MetricType": {
39+
Name: "MetricType",
40+
Fields: []EnumField{
41+
{Name: "Gauge", Value: 0},
42+
{Name: "Counter", Value: 1},
43+
},
44+
},
45+
},
46+
Multimaps: map[string]*Multimap{
47+
"Labels": {
48+
Name: "Labels",
49+
Key: MultimapField{
50+
Type: FieldType{
51+
Primitive: &PrimitiveType{Type: PrimitiveTypeString}, DictName: "LabelKeys",
52+
},
53+
},
54+
Value: MultimapField{
55+
Type: FieldType{
56+
Primitive: &PrimitiveType{Type: PrimitiveTypeString}, DictName: "LabelValues",
57+
},
58+
},
59+
},
60+
},
61+
}
62+
63+
expected := `package com.example.test
64+
65+
enum MetricType {
66+
Gauge = 0
67+
Counter = 1
68+
}
69+
70+
multimap Labels {
71+
key string dict(LabelKeys)
72+
value string dict(LabelValues)
73+
}`
74+
actual := strings.TrimSpace(schema.PrettyPrint())
75+
require.Equal(t, expected, actual)
76+
}
77+
78+
func TestPrettyPrint_OneofAndArray(t *testing.T) {
79+
schema := &Schema{
80+
PackageName: []string{"example"},
81+
Structs: map[string]*Struct{
82+
"JsonValue": {
83+
Name: "JsonValue",
84+
OneOf: true,
85+
Fields: []*StructField{
86+
{Name: "String", FieldType: FieldType{Primitive: &PrimitiveType{Type: PrimitiveTypeString}}},
87+
{Name: "Number", FieldType: FieldType{Primitive: &PrimitiveType{Type: PrimitiveTypeFloat64}}},
88+
{Name: "Array", FieldType: FieldType{Array: &ArrayType{ElemType: FieldType{Struct: "JsonValue"}}}},
89+
},
90+
},
91+
},
92+
}
93+
expected := `package example
94+
95+
oneof JsonValue {
96+
String string
97+
Number float64
98+
Array []JsonValue
99+
}`
100+
actual := strings.TrimSpace(schema.PrettyPrint())
101+
require.Equal(t, expected, actual)
102+
}
103+
104+
func TestPrettyPrint_OptionalAndDict(t *testing.T) {
105+
schema := &Schema{
106+
PackageName: []string{"example"},
107+
Structs: map[string]*Struct{
108+
"User": {
109+
Name: "User",
110+
Fields: []*StructField{
111+
{Name: "Name", FieldType: FieldType{Primitive: &PrimitiveType{Type: PrimitiveTypeString}}},
112+
{
113+
Name: "Email",
114+
FieldType: FieldType{Primitive: &PrimitiveType{Type: PrimitiveTypeString}, DictName: "Emails"},
115+
Optional: true,
116+
},
117+
},
118+
},
119+
},
120+
}
121+
expected := `package example
122+
123+
struct User {
124+
Name string
125+
Email string dict(Emails) optional
126+
}`
127+
actual := strings.TrimSpace(schema.PrettyPrint())
128+
require.Equal(t, expected, actual)
129+
}

stefc/generator/compileschema.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,8 @@ func (s *genSchema) resolveType(typ genFieldTypeRef) error {
5252
}
5353

5454
if ref, ok := typ.(*genArrayTypeRef); ok {
55-
if refStr, ok := ref.ElemType.(*genStructTypeRef); ok {
56-
refStr.Def = s.Structs[refStr.Name]
57-
if refStr.Def == nil {
58-
return fmt.Errorf("struct %s not found", refStr.Name)
59-
}
55+
if err := s.resolveType(ref.ElemType); err != nil {
56+
return err
6057
}
6158
}
6259

@@ -70,6 +67,9 @@ func (s *genSchema) resolveType(typ genFieldTypeRef) error {
7067
ref, ok := typ.(*genPrimitiveTypeRef)
7168
if ok && ref.Enum != "" {
7269
ref.EnumDef = s.Enums[ref.Enum]
70+
if ref.EnumDef == nil {
71+
return fmt.Errorf("enum %s not found", ref.Enum)
72+
}
7373
}
7474

7575
return nil

0 commit comments

Comments
 (0)