github.com/jhump/protocompile@v0.0.0-20221021153901-4f6f732835e8/compiler_test.go (about) 1 package protocompile 2 3 import ( 4 "context" 5 "errors" 6 "os" 7 "strings" 8 "testing" 9 10 "github.com/stretchr/testify/assert" 11 "google.golang.org/protobuf/reflect/protodesc" 12 "google.golang.org/protobuf/reflect/protoregistry" 13 14 _ "github.com/jhump/protocompile/internal/testprotos" 15 ) 16 17 func TestParseFilesMessageComments(t *testing.T) { 18 comp := Compiler{ 19 Resolver: &SourceResolver{}, 20 IncludeSourceInfo: true, 21 } 22 ctx := context.Background() 23 files, err := comp.Compile(ctx, "internal/testprotos/desc_test1.proto") 24 if !assert.Nil(t, err, "%v", err) { 25 t.FailNow() 26 } 27 comments := "" 28 expected := " Comment for TestMessage\n" 29 for _, fd := range files { 30 msg := fd.Messages().ByName("TestMessage") 31 if msg != nil { 32 si := fd.SourceLocations().ByDescriptor(msg) 33 if si.Path != nil { 34 comments = si.LeadingComments 35 } 36 break 37 } 38 } 39 assert.Equal(t, expected, comments) 40 } 41 42 func TestParseFilesWithImportsNoImportPath(t *testing.T) { 43 relFilePaths := []string{ 44 "a/b/b1.proto", 45 "a/b/b2.proto", 46 "c/c.proto", 47 } 48 49 pwd, err := os.Getwd() 50 assert.Nil(t, err, "%v", err) 51 52 err = os.Chdir("internal/testprotos/more") 53 assert.Nil(t, err, "%v", err) 54 defer func() { 55 // restore working directory 56 _ = os.Chdir(pwd) 57 }() 58 59 comp := Compiler{ 60 Resolver: WithStandardImports(&SourceResolver{}), 61 } 62 ctx := context.Background() 63 protos, err := comp.Compile(ctx, relFilePaths...) 64 if !assert.Nil(t, err, "%v", err) { 65 t.FailNow() 66 } 67 assert.Equal(t, len(relFilePaths), len(protos)) 68 } 69 70 func TestParseFilesWithDependencies(t *testing.T) { 71 // Create some file contents that import a non-well-known proto. 72 // (One of the protos in internal/testprotos is fine.) 73 contents := map[string]string{ 74 "test.proto": ` 75 syntax = "proto3"; 76 import "desc_test_wellknowntypes.proto"; 77 78 message TestImportedType { 79 testprotos.TestWellKnownTypes imported_field = 1; 80 } 81 `, 82 } 83 baseResolver := ResolverFunc(func(f string) (SearchResult, error) { 84 s, ok := contents[f] 85 if !ok { 86 return SearchResult{}, os.ErrNotExist 87 } 88 return SearchResult{Source: strings.NewReader(s)}, nil 89 }) 90 91 wktDesc, err := protoregistry.GlobalFiles.FindFileByPath("desc_test_wellknowntypes.proto") 92 assert.Nil(t, err) 93 wktDescProto := protodesc.ToFileDescriptorProto(wktDesc) 94 ctx := context.Background() 95 96 // Establish that we *can* parse the source file with a parser that 97 // registers the dependency. 98 t.Run("DependencyIncluded", func(t *testing.T) { 99 // Create a dependency-aware compiler. 100 compiler := Compiler{ 101 Resolver: ResolverFunc(func(f string) (SearchResult, error) { 102 if f == "desc_test_wellknowntypes.proto" { 103 return SearchResult{Desc: wktDesc}, nil 104 } 105 return baseResolver.FindFileByPath(f) 106 }), 107 } 108 _, err := compiler.Compile(ctx, "test.proto") 109 assert.Nil(t, err, "%v", err) 110 }) 111 t.Run("DependencyIncludedProto", func(t *testing.T) { 112 // Create a dependency-aware compiler. 113 compiler := Compiler{ 114 Resolver: WithStandardImports(ResolverFunc(func(f string) (SearchResult, error) { 115 if f == "desc_test_wellknowntypes.proto" { 116 return SearchResult{Proto: wktDescProto}, nil 117 } 118 return baseResolver.FindFileByPath(f) 119 })), 120 } 121 _, err := compiler.Compile(ctx, "test.proto") 122 assert.Nil(t, err, "%v", err) 123 }) 124 125 // Establish that we *can not* parse the source file with a parser that 126 // did not register the dependency. 127 t.Run("DependencyExcluded", func(t *testing.T) { 128 // Create a dependency-UNaware parser. 129 compiler := Compiler{Resolver: baseResolver} 130 _, err := compiler.Compile(ctx, "test.proto") 131 assert.NotNil(t, err, "expected parse to fail") 132 }) 133 134 // Establish that the accessor has precedence over LookupImport. 135 t.Run("AccessorWins", func(t *testing.T) { 136 // Create a dependency-aware parser that should never be called. 137 compiler := Compiler{ 138 Resolver: ResolverFunc(func(f string) (SearchResult, error) { 139 if f == "test.proto" { 140 return SearchResult{Source: strings.NewReader(`syntax = "proto3";`)}, nil 141 } 142 t.Errorf("resolved was called for unexpected filename %q", f) 143 return SearchResult{}, os.ErrNotExist 144 }), 145 } 146 _, err := compiler.Compile(ctx, "test.proto") 147 assert.Nil(t, err) 148 }) 149 } 150 151 func TestParseCommentsBeforeDot(t *testing.T) { 152 accessor := SourceAccessorFromMap(map[string]string{ 153 "test.proto": ` 154 syntax = "proto3"; 155 message Foo { 156 // leading comments 157 .Foo foo = 1; 158 } 159 `, 160 }) 161 162 compiler := Compiler{ 163 Resolver: &SourceResolver{Accessor: accessor}, 164 IncludeSourceInfo: true, 165 } 166 ctx := context.Background() 167 fds, err := compiler.Compile(ctx, "test.proto") 168 assert.Nil(t, err) 169 170 field := fds[0].Messages().Get(0).Fields().Get(0) 171 comment := fds[0].SourceLocations().ByDescriptor(field).LeadingComments 172 assert.Equal(t, " leading comments\n", comment) 173 } 174 175 func TestParseCustomOptions(t *testing.T) { 176 accessor := SourceAccessorFromMap(map[string]string{ 177 "test.proto": ` 178 syntax = "proto3"; 179 import "google/protobuf/descriptor.proto"; 180 extend google.protobuf.MessageOptions { 181 string foo = 30303; 182 int64 bar = 30304; 183 } 184 message Foo { 185 option (.foo) = "foo"; 186 option (bar) = 123; 187 } 188 `, 189 }) 190 191 compiler := Compiler{ 192 Resolver: WithStandardImports(&SourceResolver{Accessor: accessor}), 193 IncludeSourceInfo: true, 194 } 195 ctx := context.Background() 196 fds, err := compiler.Compile(ctx, "test.proto") 197 if !assert.Nil(t, err, "%v", err) { 198 t.FailNow() 199 } 200 201 ext := fds[0].Extensions().ByName("foo") 202 md := fds[0].Messages().Get(0) 203 fooVal := md.Options().ProtoReflect().Get(ext) 204 assert.Equal(t, "foo", fooVal.String()) 205 206 ext = fds[0].Extensions().ByName("bar") 207 barVal := md.Options().ProtoReflect().Get(ext) 208 assert.Equal(t, int64(123), barVal.Int()) 209 } 210 211 func TestPanicHandling(t *testing.T) { 212 c := Compiler{ 213 Resolver: ResolverFunc(func(string) (SearchResult, error) { 214 panic(errors.New("mui mui bad")) 215 }), 216 } 217 _, err := c.Compile(context.Background(), "test.proto") 218 panicErr := err.(PanicError) 219 t.Logf("%v\n\n%v", panicErr, panicErr.Stack) 220 }