1*1c12ee1eSDan Willemsen// Copyright 2018 The Go Authors. All rights reserved. 2*1c12ee1eSDan Willemsen// Use of this source code is governed by a BSD-style 3*1c12ee1eSDan Willemsen// license that can be found in the LICENSE file. 4*1c12ee1eSDan Willemsen 5*1c12ee1eSDan Willemsenpackage protoregistry_test 6*1c12ee1eSDan Willemsen 7*1c12ee1eSDan Willemsenimport ( 8*1c12ee1eSDan Willemsen "fmt" 9*1c12ee1eSDan Willemsen "strings" 10*1c12ee1eSDan Willemsen "testing" 11*1c12ee1eSDan Willemsen 12*1c12ee1eSDan Willemsen "github.com/google/go-cmp/cmp" 13*1c12ee1eSDan Willemsen "github.com/google/go-cmp/cmp/cmpopts" 14*1c12ee1eSDan Willemsen 15*1c12ee1eSDan Willemsen "google.golang.org/protobuf/encoding/prototext" 16*1c12ee1eSDan Willemsen pimpl "google.golang.org/protobuf/internal/impl" 17*1c12ee1eSDan Willemsen "google.golang.org/protobuf/reflect/protodesc" 18*1c12ee1eSDan Willemsen "google.golang.org/protobuf/reflect/protoreflect" 19*1c12ee1eSDan Willemsen "google.golang.org/protobuf/reflect/protoregistry" 20*1c12ee1eSDan Willemsen 21*1c12ee1eSDan Willemsen testpb "google.golang.org/protobuf/internal/testprotos/registry" 22*1c12ee1eSDan Willemsen "google.golang.org/protobuf/types/descriptorpb" 23*1c12ee1eSDan Willemsen) 24*1c12ee1eSDan Willemsen 25*1c12ee1eSDan Willemsenfunc mustMakeFile(s string) protoreflect.FileDescriptor { 26*1c12ee1eSDan Willemsen pb := new(descriptorpb.FileDescriptorProto) 27*1c12ee1eSDan Willemsen if err := prototext.Unmarshal([]byte(s), pb); err != nil { 28*1c12ee1eSDan Willemsen panic(err) 29*1c12ee1eSDan Willemsen } 30*1c12ee1eSDan Willemsen fd, err := protodesc.NewFile(pb, nil) 31*1c12ee1eSDan Willemsen if err != nil { 32*1c12ee1eSDan Willemsen panic(err) 33*1c12ee1eSDan Willemsen } 34*1c12ee1eSDan Willemsen return fd 35*1c12ee1eSDan Willemsen} 36*1c12ee1eSDan Willemsen 37*1c12ee1eSDan Willemsenfunc TestFiles(t *testing.T) { 38*1c12ee1eSDan Willemsen type ( 39*1c12ee1eSDan Willemsen file struct { 40*1c12ee1eSDan Willemsen Path string 41*1c12ee1eSDan Willemsen Pkg protoreflect.FullName 42*1c12ee1eSDan Willemsen } 43*1c12ee1eSDan Willemsen testFile struct { 44*1c12ee1eSDan Willemsen inFile protoreflect.FileDescriptor 45*1c12ee1eSDan Willemsen wantErr string 46*1c12ee1eSDan Willemsen } 47*1c12ee1eSDan Willemsen testFindDesc struct { 48*1c12ee1eSDan Willemsen inName protoreflect.FullName 49*1c12ee1eSDan Willemsen wantFound bool 50*1c12ee1eSDan Willemsen } 51*1c12ee1eSDan Willemsen testRangePkg struct { 52*1c12ee1eSDan Willemsen inPkg protoreflect.FullName 53*1c12ee1eSDan Willemsen wantFiles []file 54*1c12ee1eSDan Willemsen } 55*1c12ee1eSDan Willemsen testFindPath struct { 56*1c12ee1eSDan Willemsen inPath string 57*1c12ee1eSDan Willemsen wantFiles []file 58*1c12ee1eSDan Willemsen wantErr string 59*1c12ee1eSDan Willemsen } 60*1c12ee1eSDan Willemsen ) 61*1c12ee1eSDan Willemsen 62*1c12ee1eSDan Willemsen tests := []struct { 63*1c12ee1eSDan Willemsen files []testFile 64*1c12ee1eSDan Willemsen findDescs []testFindDesc 65*1c12ee1eSDan Willemsen rangePkgs []testRangePkg 66*1c12ee1eSDan Willemsen findPaths []testFindPath 67*1c12ee1eSDan Willemsen }{{ 68*1c12ee1eSDan Willemsen // Test that overlapping packages and files are permitted. 69*1c12ee1eSDan Willemsen files: []testFile{ 70*1c12ee1eSDan Willemsen {inFile: mustMakeFile(`syntax:"proto2" name:"test1.proto" package:"foo.bar"`)}, 71*1c12ee1eSDan Willemsen {inFile: mustMakeFile(`syntax:"proto2" name:"foo/bar/test.proto" package:"my.test"`)}, 72*1c12ee1eSDan Willemsen {inFile: mustMakeFile(`syntax:"proto2" name:"foo/bar/test.proto" package:"foo.bar.baz"`), wantErr: "already registered"}, 73*1c12ee1eSDan Willemsen {inFile: mustMakeFile(`syntax:"proto2" name:"test2.proto" package:"my.test.package"`)}, 74*1c12ee1eSDan Willemsen {inFile: mustMakeFile(`syntax:"proto2" name:"weird" package:"foo.bar"`)}, 75*1c12ee1eSDan Willemsen {inFile: mustMakeFile(`syntax:"proto2" name:"foo/bar/baz/../test.proto" package:"my.test"`)}, 76*1c12ee1eSDan Willemsen }, 77*1c12ee1eSDan Willemsen 78*1c12ee1eSDan Willemsen rangePkgs: []testRangePkg{{ 79*1c12ee1eSDan Willemsen inPkg: "nothing", 80*1c12ee1eSDan Willemsen }, { 81*1c12ee1eSDan Willemsen inPkg: "", 82*1c12ee1eSDan Willemsen }, { 83*1c12ee1eSDan Willemsen inPkg: ".", 84*1c12ee1eSDan Willemsen }, { 85*1c12ee1eSDan Willemsen inPkg: "foo", 86*1c12ee1eSDan Willemsen }, { 87*1c12ee1eSDan Willemsen inPkg: "foo.", 88*1c12ee1eSDan Willemsen }, { 89*1c12ee1eSDan Willemsen inPkg: "foo..", 90*1c12ee1eSDan Willemsen }, { 91*1c12ee1eSDan Willemsen inPkg: "foo.bar", 92*1c12ee1eSDan Willemsen wantFiles: []file{ 93*1c12ee1eSDan Willemsen {"test1.proto", "foo.bar"}, 94*1c12ee1eSDan Willemsen {"weird", "foo.bar"}, 95*1c12ee1eSDan Willemsen }, 96*1c12ee1eSDan Willemsen }, { 97*1c12ee1eSDan Willemsen inPkg: "my.test", 98*1c12ee1eSDan Willemsen wantFiles: []file{ 99*1c12ee1eSDan Willemsen {"foo/bar/baz/../test.proto", "my.test"}, 100*1c12ee1eSDan Willemsen {"foo/bar/test.proto", "my.test"}, 101*1c12ee1eSDan Willemsen }, 102*1c12ee1eSDan Willemsen }, { 103*1c12ee1eSDan Willemsen inPkg: "fo", 104*1c12ee1eSDan Willemsen }}, 105*1c12ee1eSDan Willemsen 106*1c12ee1eSDan Willemsen findPaths: []testFindPath{{ 107*1c12ee1eSDan Willemsen inPath: "nothing", 108*1c12ee1eSDan Willemsen wantErr: "not found", 109*1c12ee1eSDan Willemsen }, { 110*1c12ee1eSDan Willemsen inPath: "weird", 111*1c12ee1eSDan Willemsen wantFiles: []file{ 112*1c12ee1eSDan Willemsen {"weird", "foo.bar"}, 113*1c12ee1eSDan Willemsen }, 114*1c12ee1eSDan Willemsen }, { 115*1c12ee1eSDan Willemsen inPath: "foo/bar/test.proto", 116*1c12ee1eSDan Willemsen wantFiles: []file{ 117*1c12ee1eSDan Willemsen {"foo/bar/test.proto", "my.test"}, 118*1c12ee1eSDan Willemsen }, 119*1c12ee1eSDan Willemsen }}, 120*1c12ee1eSDan Willemsen }, { 121*1c12ee1eSDan Willemsen // Test when new enum conflicts with existing package. 122*1c12ee1eSDan Willemsen files: []testFile{{ 123*1c12ee1eSDan Willemsen inFile: mustMakeFile(`syntax:"proto2" name:"test1a.proto" package:"foo.bar.baz"`), 124*1c12ee1eSDan Willemsen }, { 125*1c12ee1eSDan Willemsen inFile: mustMakeFile(`syntax:"proto2" name:"test1b.proto" enum_type:[{name:"foo" value:[{name:"VALUE" number:0}]}]`), 126*1c12ee1eSDan Willemsen wantErr: `file "test1b.proto" has a name conflict over foo`, 127*1c12ee1eSDan Willemsen }}, 128*1c12ee1eSDan Willemsen }, { 129*1c12ee1eSDan Willemsen // Test when new package conflicts with existing enum. 130*1c12ee1eSDan Willemsen files: []testFile{{ 131*1c12ee1eSDan Willemsen inFile: mustMakeFile(`syntax:"proto2" name:"test2a.proto" enum_type:[{name:"foo" value:[{name:"VALUE" number:0}]}]`), 132*1c12ee1eSDan Willemsen }, { 133*1c12ee1eSDan Willemsen inFile: mustMakeFile(`syntax:"proto2" name:"test2b.proto" package:"foo.bar.baz"`), 134*1c12ee1eSDan Willemsen wantErr: `file "test2b.proto" has a package name conflict over foo`, 135*1c12ee1eSDan Willemsen }}, 136*1c12ee1eSDan Willemsen }, { 137*1c12ee1eSDan Willemsen // Test when new enum conflicts with existing enum in same package. 138*1c12ee1eSDan Willemsen files: []testFile{{ 139*1c12ee1eSDan Willemsen inFile: mustMakeFile(`syntax:"proto2" name:"test3a.proto" package:"foo" enum_type:[{name:"BAR" value:[{name:"VALUE" number:0}]}]`), 140*1c12ee1eSDan Willemsen }, { 141*1c12ee1eSDan Willemsen inFile: mustMakeFile(`syntax:"proto2" name:"test3b.proto" package:"foo" enum_type:[{name:"BAR" value:[{name:"VALUE2" number:0}]}]`), 142*1c12ee1eSDan Willemsen wantErr: `file "test3b.proto" has a name conflict over foo.BAR`, 143*1c12ee1eSDan Willemsen }}, 144*1c12ee1eSDan Willemsen }, { 145*1c12ee1eSDan Willemsen files: []testFile{{ 146*1c12ee1eSDan Willemsen inFile: mustMakeFile(` 147*1c12ee1eSDan Willemsen syntax: "proto2" 148*1c12ee1eSDan Willemsen name: "test1.proto" 149*1c12ee1eSDan Willemsen package: "fizz.buzz" 150*1c12ee1eSDan Willemsen message_type: [{ 151*1c12ee1eSDan Willemsen name: "Message" 152*1c12ee1eSDan Willemsen field: [ 153*1c12ee1eSDan Willemsen {name:"Field" number:1 label:LABEL_OPTIONAL type:TYPE_STRING oneof_index:0} 154*1c12ee1eSDan Willemsen ] 155*1c12ee1eSDan Willemsen oneof_decl: [{name:"Oneof"}] 156*1c12ee1eSDan Willemsen extension_range: [{start:1000 end:2000}] 157*1c12ee1eSDan Willemsen 158*1c12ee1eSDan Willemsen enum_type: [ 159*1c12ee1eSDan Willemsen {name:"Enum" value:[{name:"EnumValue" number:0}]} 160*1c12ee1eSDan Willemsen ] 161*1c12ee1eSDan Willemsen nested_type: [ 162*1c12ee1eSDan Willemsen {name:"Message" field:[{name:"Field" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}]} 163*1c12ee1eSDan Willemsen ] 164*1c12ee1eSDan Willemsen extension: [ 165*1c12ee1eSDan Willemsen {name:"Extension" number:1001 label:LABEL_OPTIONAL type:TYPE_STRING extendee:".fizz.buzz.Message"} 166*1c12ee1eSDan Willemsen ] 167*1c12ee1eSDan Willemsen }] 168*1c12ee1eSDan Willemsen enum_type: [{ 169*1c12ee1eSDan Willemsen name: "Enum" 170*1c12ee1eSDan Willemsen value: [{name:"EnumValue" number:0}] 171*1c12ee1eSDan Willemsen }] 172*1c12ee1eSDan Willemsen extension: [ 173*1c12ee1eSDan Willemsen {name:"Extension" number:1000 label:LABEL_OPTIONAL type:TYPE_STRING extendee:".fizz.buzz.Message"} 174*1c12ee1eSDan Willemsen ] 175*1c12ee1eSDan Willemsen service: [{ 176*1c12ee1eSDan Willemsen name: "Service" 177*1c12ee1eSDan Willemsen method: [{ 178*1c12ee1eSDan Willemsen name: "Method" 179*1c12ee1eSDan Willemsen input_type: ".fizz.buzz.Message" 180*1c12ee1eSDan Willemsen output_type: ".fizz.buzz.Message" 181*1c12ee1eSDan Willemsen client_streaming: true 182*1c12ee1eSDan Willemsen server_streaming: true 183*1c12ee1eSDan Willemsen }] 184*1c12ee1eSDan Willemsen }] 185*1c12ee1eSDan Willemsen `), 186*1c12ee1eSDan Willemsen }, { 187*1c12ee1eSDan Willemsen inFile: mustMakeFile(` 188*1c12ee1eSDan Willemsen syntax: "proto2" 189*1c12ee1eSDan Willemsen name: "test2.proto" 190*1c12ee1eSDan Willemsen package: "fizz.buzz.gazz" 191*1c12ee1eSDan Willemsen enum_type: [{ 192*1c12ee1eSDan Willemsen name: "Enum" 193*1c12ee1eSDan Willemsen value: [{name:"EnumValue" number:0}] 194*1c12ee1eSDan Willemsen }] 195*1c12ee1eSDan Willemsen `), 196*1c12ee1eSDan Willemsen }, { 197*1c12ee1eSDan Willemsen inFile: mustMakeFile(` 198*1c12ee1eSDan Willemsen syntax: "proto2" 199*1c12ee1eSDan Willemsen name: "test3.proto" 200*1c12ee1eSDan Willemsen package: "fizz.buzz" 201*1c12ee1eSDan Willemsen enum_type: [{ 202*1c12ee1eSDan Willemsen name: "Enum1" 203*1c12ee1eSDan Willemsen value: [{name:"EnumValue1" number:0}] 204*1c12ee1eSDan Willemsen }, { 205*1c12ee1eSDan Willemsen name: "Enum2" 206*1c12ee1eSDan Willemsen value: [{name:"EnumValue2" number:0}] 207*1c12ee1eSDan Willemsen }] 208*1c12ee1eSDan Willemsen `), 209*1c12ee1eSDan Willemsen }, { 210*1c12ee1eSDan Willemsen // Make sure we can register without package name. 211*1c12ee1eSDan Willemsen inFile: mustMakeFile(` 212*1c12ee1eSDan Willemsen name: "weird" 213*1c12ee1eSDan Willemsen syntax: "proto2" 214*1c12ee1eSDan Willemsen message_type: [{ 215*1c12ee1eSDan Willemsen name: "Message" 216*1c12ee1eSDan Willemsen nested_type: [{ 217*1c12ee1eSDan Willemsen name: "Message" 218*1c12ee1eSDan Willemsen nested_type: [{ 219*1c12ee1eSDan Willemsen name: "Message" 220*1c12ee1eSDan Willemsen }] 221*1c12ee1eSDan Willemsen }] 222*1c12ee1eSDan Willemsen }] 223*1c12ee1eSDan Willemsen `), 224*1c12ee1eSDan Willemsen }}, 225*1c12ee1eSDan Willemsen findDescs: []testFindDesc{ 226*1c12ee1eSDan Willemsen {inName: "fizz.buzz.message", wantFound: false}, 227*1c12ee1eSDan Willemsen {inName: "fizz.buzz.Message", wantFound: true}, 228*1c12ee1eSDan Willemsen {inName: "fizz.buzz.Message.X", wantFound: false}, 229*1c12ee1eSDan Willemsen {inName: "fizz.buzz.Field", wantFound: false}, 230*1c12ee1eSDan Willemsen {inName: "fizz.buzz.Oneof", wantFound: false}, 231*1c12ee1eSDan Willemsen {inName: "fizz.buzz.Message.Field", wantFound: true}, 232*1c12ee1eSDan Willemsen {inName: "fizz.buzz.Message.Field.X", wantFound: false}, 233*1c12ee1eSDan Willemsen {inName: "fizz.buzz.Message.Oneof", wantFound: true}, 234*1c12ee1eSDan Willemsen {inName: "fizz.buzz.Message.Oneof.X", wantFound: false}, 235*1c12ee1eSDan Willemsen {inName: "fizz.buzz.Message.Message", wantFound: true}, 236*1c12ee1eSDan Willemsen {inName: "fizz.buzz.Message.Message.X", wantFound: false}, 237*1c12ee1eSDan Willemsen {inName: "fizz.buzz.Message.Enum", wantFound: true}, 238*1c12ee1eSDan Willemsen {inName: "fizz.buzz.Message.Enum.X", wantFound: false}, 239*1c12ee1eSDan Willemsen {inName: "fizz.buzz.Message.EnumValue", wantFound: true}, 240*1c12ee1eSDan Willemsen {inName: "fizz.buzz.Message.EnumValue.X", wantFound: false}, 241*1c12ee1eSDan Willemsen {inName: "fizz.buzz.Message.Extension", wantFound: true}, 242*1c12ee1eSDan Willemsen {inName: "fizz.buzz.Message.Extension.X", wantFound: false}, 243*1c12ee1eSDan Willemsen {inName: "fizz.buzz.enum", wantFound: false}, 244*1c12ee1eSDan Willemsen {inName: "fizz.buzz.Enum", wantFound: true}, 245*1c12ee1eSDan Willemsen {inName: "fizz.buzz.Enum.X", wantFound: false}, 246*1c12ee1eSDan Willemsen {inName: "fizz.buzz.EnumValue", wantFound: true}, 247*1c12ee1eSDan Willemsen {inName: "fizz.buzz.EnumValue.X", wantFound: false}, 248*1c12ee1eSDan Willemsen {inName: "fizz.buzz.Enum.EnumValue", wantFound: false}, 249*1c12ee1eSDan Willemsen {inName: "fizz.buzz.Extension", wantFound: true}, 250*1c12ee1eSDan Willemsen {inName: "fizz.buzz.Extension.X", wantFound: false}, 251*1c12ee1eSDan Willemsen {inName: "fizz.buzz.service", wantFound: false}, 252*1c12ee1eSDan Willemsen {inName: "fizz.buzz.Service", wantFound: true}, 253*1c12ee1eSDan Willemsen {inName: "fizz.buzz.Service.X", wantFound: false}, 254*1c12ee1eSDan Willemsen {inName: "fizz.buzz.Method", wantFound: false}, 255*1c12ee1eSDan Willemsen {inName: "fizz.buzz.Service.Method", wantFound: true}, 256*1c12ee1eSDan Willemsen {inName: "fizz.buzz.Service.Method.X", wantFound: false}, 257*1c12ee1eSDan Willemsen 258*1c12ee1eSDan Willemsen {inName: "fizz.buzz.gazz", wantFound: false}, 259*1c12ee1eSDan Willemsen {inName: "fizz.buzz.gazz.Enum", wantFound: true}, 260*1c12ee1eSDan Willemsen {inName: "fizz.buzz.gazz.EnumValue", wantFound: true}, 261*1c12ee1eSDan Willemsen {inName: "fizz.buzz.gazz.Enum.EnumValue", wantFound: false}, 262*1c12ee1eSDan Willemsen 263*1c12ee1eSDan Willemsen {inName: "fizz.buzz", wantFound: false}, 264*1c12ee1eSDan Willemsen {inName: "fizz.buzz.Enum1", wantFound: true}, 265*1c12ee1eSDan Willemsen {inName: "fizz.buzz.EnumValue1", wantFound: true}, 266*1c12ee1eSDan Willemsen {inName: "fizz.buzz.Enum1.EnumValue1", wantFound: false}, 267*1c12ee1eSDan Willemsen {inName: "fizz.buzz.Enum2", wantFound: true}, 268*1c12ee1eSDan Willemsen {inName: "fizz.buzz.EnumValue2", wantFound: true}, 269*1c12ee1eSDan Willemsen {inName: "fizz.buzz.Enum2.EnumValue2", wantFound: false}, 270*1c12ee1eSDan Willemsen {inName: "fizz.buzz.Enum3", wantFound: false}, 271*1c12ee1eSDan Willemsen 272*1c12ee1eSDan Willemsen {inName: "", wantFound: false}, 273*1c12ee1eSDan Willemsen {inName: "Message", wantFound: true}, 274*1c12ee1eSDan Willemsen {inName: "Message.Message", wantFound: true}, 275*1c12ee1eSDan Willemsen {inName: "Message.Message.Message", wantFound: true}, 276*1c12ee1eSDan Willemsen {inName: "Message.Message.Message.Message", wantFound: false}, 277*1c12ee1eSDan Willemsen }, 278*1c12ee1eSDan Willemsen }} 279*1c12ee1eSDan Willemsen 280*1c12ee1eSDan Willemsen sortFiles := cmpopts.SortSlices(func(x, y file) bool { 281*1c12ee1eSDan Willemsen return x.Path < y.Path || (x.Path == y.Path && x.Pkg < y.Pkg) 282*1c12ee1eSDan Willemsen }) 283*1c12ee1eSDan Willemsen for _, tt := range tests { 284*1c12ee1eSDan Willemsen t.Run("", func(t *testing.T) { 285*1c12ee1eSDan Willemsen var files protoregistry.Files 286*1c12ee1eSDan Willemsen for i, tc := range tt.files { 287*1c12ee1eSDan Willemsen gotErr := files.RegisterFile(tc.inFile) 288*1c12ee1eSDan Willemsen if ((gotErr == nil) != (tc.wantErr == "")) || !strings.Contains(fmt.Sprint(gotErr), tc.wantErr) { 289*1c12ee1eSDan Willemsen t.Errorf("file %d, Register() = %v, want %v", i, gotErr, tc.wantErr) 290*1c12ee1eSDan Willemsen } 291*1c12ee1eSDan Willemsen } 292*1c12ee1eSDan Willemsen 293*1c12ee1eSDan Willemsen for _, tc := range tt.findDescs { 294*1c12ee1eSDan Willemsen d, _ := files.FindDescriptorByName(tc.inName) 295*1c12ee1eSDan Willemsen gotFound := d != nil 296*1c12ee1eSDan Willemsen if gotFound != tc.wantFound { 297*1c12ee1eSDan Willemsen t.Errorf("FindDescriptorByName(%v) find mismatch: got %v, want %v", tc.inName, gotFound, tc.wantFound) 298*1c12ee1eSDan Willemsen } 299*1c12ee1eSDan Willemsen } 300*1c12ee1eSDan Willemsen 301*1c12ee1eSDan Willemsen for _, tc := range tt.rangePkgs { 302*1c12ee1eSDan Willemsen var gotFiles []file 303*1c12ee1eSDan Willemsen var gotCnt int 304*1c12ee1eSDan Willemsen wantCnt := files.NumFilesByPackage(tc.inPkg) 305*1c12ee1eSDan Willemsen files.RangeFilesByPackage(tc.inPkg, func(fd protoreflect.FileDescriptor) bool { 306*1c12ee1eSDan Willemsen gotFiles = append(gotFiles, file{fd.Path(), fd.Package()}) 307*1c12ee1eSDan Willemsen gotCnt++ 308*1c12ee1eSDan Willemsen return true 309*1c12ee1eSDan Willemsen }) 310*1c12ee1eSDan Willemsen if gotCnt != wantCnt { 311*1c12ee1eSDan Willemsen t.Errorf("NumFilesByPackage(%v) = %v, want %v", tc.inPkg, gotCnt, wantCnt) 312*1c12ee1eSDan Willemsen } 313*1c12ee1eSDan Willemsen if diff := cmp.Diff(tc.wantFiles, gotFiles, sortFiles); diff != "" { 314*1c12ee1eSDan Willemsen t.Errorf("RangeFilesByPackage(%v) mismatch (-want +got):\n%v", tc.inPkg, diff) 315*1c12ee1eSDan Willemsen } 316*1c12ee1eSDan Willemsen } 317*1c12ee1eSDan Willemsen 318*1c12ee1eSDan Willemsen for _, tc := range tt.findPaths { 319*1c12ee1eSDan Willemsen var gotFiles []file 320*1c12ee1eSDan Willemsen fd, gotErr := files.FindFileByPath(tc.inPath) 321*1c12ee1eSDan Willemsen if gotErr == nil { 322*1c12ee1eSDan Willemsen gotFiles = append(gotFiles, file{fd.Path(), fd.Package()}) 323*1c12ee1eSDan Willemsen } 324*1c12ee1eSDan Willemsen if ((gotErr == nil) != (tc.wantErr == "")) || !strings.Contains(fmt.Sprint(gotErr), tc.wantErr) { 325*1c12ee1eSDan Willemsen t.Errorf("FindFileByPath(%v) = %v, want %v", tc.inPath, gotErr, tc.wantErr) 326*1c12ee1eSDan Willemsen } 327*1c12ee1eSDan Willemsen if diff := cmp.Diff(tc.wantFiles, gotFiles, sortFiles); diff != "" { 328*1c12ee1eSDan Willemsen t.Errorf("FindFileByPath(%v) mismatch (-want +got):\n%v", tc.inPath, diff) 329*1c12ee1eSDan Willemsen } 330*1c12ee1eSDan Willemsen } 331*1c12ee1eSDan Willemsen }) 332*1c12ee1eSDan Willemsen } 333*1c12ee1eSDan Willemsen} 334*1c12ee1eSDan Willemsen 335*1c12ee1eSDan Willemsenfunc TestTypes(t *testing.T) { 336*1c12ee1eSDan Willemsen mt1 := pimpl.Export{}.MessageTypeOf(&testpb.Message1{}) 337*1c12ee1eSDan Willemsen et1 := pimpl.Export{}.EnumTypeOf(testpb.Enum1_ONE) 338*1c12ee1eSDan Willemsen xt1 := testpb.E_StringField 339*1c12ee1eSDan Willemsen xt2 := testpb.E_Message4_MessageField 340*1c12ee1eSDan Willemsen registry := new(protoregistry.Types) 341*1c12ee1eSDan Willemsen if err := registry.RegisterMessage(mt1); err != nil { 342*1c12ee1eSDan Willemsen t.Fatalf("registry.RegisterMessage(%v) returns unexpected error: %v", mt1.Descriptor().FullName(), err) 343*1c12ee1eSDan Willemsen } 344*1c12ee1eSDan Willemsen if err := registry.RegisterEnum(et1); err != nil { 345*1c12ee1eSDan Willemsen t.Fatalf("registry.RegisterEnum(%v) returns unexpected error: %v", et1.Descriptor().FullName(), err) 346*1c12ee1eSDan Willemsen } 347*1c12ee1eSDan Willemsen if err := registry.RegisterExtension(xt1); err != nil { 348*1c12ee1eSDan Willemsen t.Fatalf("registry.RegisterExtension(%v) returns unexpected error: %v", xt1.TypeDescriptor().FullName(), err) 349*1c12ee1eSDan Willemsen } 350*1c12ee1eSDan Willemsen if err := registry.RegisterExtension(xt2); err != nil { 351*1c12ee1eSDan Willemsen t.Fatalf("registry.RegisterExtension(%v) returns unexpected error: %v", xt2.TypeDescriptor().FullName(), err) 352*1c12ee1eSDan Willemsen } 353*1c12ee1eSDan Willemsen 354*1c12ee1eSDan Willemsen t.Run("FindMessageByName", func(t *testing.T) { 355*1c12ee1eSDan Willemsen tests := []struct { 356*1c12ee1eSDan Willemsen name string 357*1c12ee1eSDan Willemsen messageType protoreflect.MessageType 358*1c12ee1eSDan Willemsen wantErr bool 359*1c12ee1eSDan Willemsen wantNotFound bool 360*1c12ee1eSDan Willemsen }{{ 361*1c12ee1eSDan Willemsen name: "testprotos.Message1", 362*1c12ee1eSDan Willemsen messageType: mt1, 363*1c12ee1eSDan Willemsen }, { 364*1c12ee1eSDan Willemsen name: "testprotos.NoSuchMessage", 365*1c12ee1eSDan Willemsen wantErr: true, 366*1c12ee1eSDan Willemsen wantNotFound: true, 367*1c12ee1eSDan Willemsen }, { 368*1c12ee1eSDan Willemsen name: "testprotos.Enum1", 369*1c12ee1eSDan Willemsen wantErr: true, 370*1c12ee1eSDan Willemsen }, { 371*1c12ee1eSDan Willemsen name: "testprotos.Enum2", 372*1c12ee1eSDan Willemsen wantErr: true, 373*1c12ee1eSDan Willemsen }, { 374*1c12ee1eSDan Willemsen name: "testprotos.Enum3", 375*1c12ee1eSDan Willemsen wantErr: true, 376*1c12ee1eSDan Willemsen }} 377*1c12ee1eSDan Willemsen for _, tc := range tests { 378*1c12ee1eSDan Willemsen got, err := registry.FindMessageByName(protoreflect.FullName(tc.name)) 379*1c12ee1eSDan Willemsen gotErr := err != nil 380*1c12ee1eSDan Willemsen if gotErr != tc.wantErr { 381*1c12ee1eSDan Willemsen t.Errorf("FindMessageByName(%v) = (_, %v), want error? %t", tc.name, err, tc.wantErr) 382*1c12ee1eSDan Willemsen continue 383*1c12ee1eSDan Willemsen } 384*1c12ee1eSDan Willemsen if tc.wantNotFound && err != protoregistry.NotFound { 385*1c12ee1eSDan Willemsen t.Errorf("FindMessageByName(%v) got error: %v, want NotFound error", tc.name, err) 386*1c12ee1eSDan Willemsen continue 387*1c12ee1eSDan Willemsen } 388*1c12ee1eSDan Willemsen if got != tc.messageType { 389*1c12ee1eSDan Willemsen t.Errorf("FindMessageByName(%v) got wrong value: %v", tc.name, got) 390*1c12ee1eSDan Willemsen } 391*1c12ee1eSDan Willemsen } 392*1c12ee1eSDan Willemsen }) 393*1c12ee1eSDan Willemsen 394*1c12ee1eSDan Willemsen t.Run("FindMessageByURL", func(t *testing.T) { 395*1c12ee1eSDan Willemsen tests := []struct { 396*1c12ee1eSDan Willemsen name string 397*1c12ee1eSDan Willemsen messageType protoreflect.MessageType 398*1c12ee1eSDan Willemsen wantErr bool 399*1c12ee1eSDan Willemsen wantNotFound bool 400*1c12ee1eSDan Willemsen }{{ 401*1c12ee1eSDan Willemsen name: "testprotos.Message1", 402*1c12ee1eSDan Willemsen messageType: mt1, 403*1c12ee1eSDan Willemsen }, { 404*1c12ee1eSDan Willemsen name: "type.googleapis.com/testprotos.Nada", 405*1c12ee1eSDan Willemsen wantErr: true, 406*1c12ee1eSDan Willemsen wantNotFound: true, 407*1c12ee1eSDan Willemsen }, { 408*1c12ee1eSDan Willemsen name: "testprotos.Enum1", 409*1c12ee1eSDan Willemsen wantErr: true, 410*1c12ee1eSDan Willemsen }} 411*1c12ee1eSDan Willemsen for _, tc := range tests { 412*1c12ee1eSDan Willemsen got, err := registry.FindMessageByURL(tc.name) 413*1c12ee1eSDan Willemsen gotErr := err != nil 414*1c12ee1eSDan Willemsen if gotErr != tc.wantErr { 415*1c12ee1eSDan Willemsen t.Errorf("FindMessageByURL(%v) = (_, %v), want error? %t", tc.name, err, tc.wantErr) 416*1c12ee1eSDan Willemsen continue 417*1c12ee1eSDan Willemsen } 418*1c12ee1eSDan Willemsen if tc.wantNotFound && err != protoregistry.NotFound { 419*1c12ee1eSDan Willemsen t.Errorf("FindMessageByURL(%v) got error: %v, want NotFound error", tc.name, err) 420*1c12ee1eSDan Willemsen continue 421*1c12ee1eSDan Willemsen } 422*1c12ee1eSDan Willemsen if got != tc.messageType { 423*1c12ee1eSDan Willemsen t.Errorf("FindMessageByURL(%v) got wrong value: %v", tc.name, got) 424*1c12ee1eSDan Willemsen } 425*1c12ee1eSDan Willemsen } 426*1c12ee1eSDan Willemsen }) 427*1c12ee1eSDan Willemsen 428*1c12ee1eSDan Willemsen t.Run("FindEnumByName", func(t *testing.T) { 429*1c12ee1eSDan Willemsen tests := []struct { 430*1c12ee1eSDan Willemsen name string 431*1c12ee1eSDan Willemsen enumType protoreflect.EnumType 432*1c12ee1eSDan Willemsen wantErr bool 433*1c12ee1eSDan Willemsen wantNotFound bool 434*1c12ee1eSDan Willemsen }{{ 435*1c12ee1eSDan Willemsen name: "testprotos.Enum1", 436*1c12ee1eSDan Willemsen enumType: et1, 437*1c12ee1eSDan Willemsen }, { 438*1c12ee1eSDan Willemsen name: "testprotos.None", 439*1c12ee1eSDan Willemsen wantErr: true, 440*1c12ee1eSDan Willemsen wantNotFound: true, 441*1c12ee1eSDan Willemsen }, { 442*1c12ee1eSDan Willemsen name: "testprotos.Message1", 443*1c12ee1eSDan Willemsen wantErr: true, 444*1c12ee1eSDan Willemsen }} 445*1c12ee1eSDan Willemsen for _, tc := range tests { 446*1c12ee1eSDan Willemsen got, err := registry.FindEnumByName(protoreflect.FullName(tc.name)) 447*1c12ee1eSDan Willemsen gotErr := err != nil 448*1c12ee1eSDan Willemsen if gotErr != tc.wantErr { 449*1c12ee1eSDan Willemsen t.Errorf("FindEnumByName(%v) = (_, %v), want error? %t", tc.name, err, tc.wantErr) 450*1c12ee1eSDan Willemsen continue 451*1c12ee1eSDan Willemsen } 452*1c12ee1eSDan Willemsen if tc.wantNotFound && err != protoregistry.NotFound { 453*1c12ee1eSDan Willemsen t.Errorf("FindEnumByName(%v) got error: %v, want NotFound error", tc.name, err) 454*1c12ee1eSDan Willemsen continue 455*1c12ee1eSDan Willemsen } 456*1c12ee1eSDan Willemsen if got != tc.enumType { 457*1c12ee1eSDan Willemsen t.Errorf("FindEnumByName(%v) got wrong value: %v", tc.name, got) 458*1c12ee1eSDan Willemsen } 459*1c12ee1eSDan Willemsen } 460*1c12ee1eSDan Willemsen }) 461*1c12ee1eSDan Willemsen 462*1c12ee1eSDan Willemsen t.Run("FindExtensionByName", func(t *testing.T) { 463*1c12ee1eSDan Willemsen tests := []struct { 464*1c12ee1eSDan Willemsen name string 465*1c12ee1eSDan Willemsen extensionType protoreflect.ExtensionType 466*1c12ee1eSDan Willemsen wantErr bool 467*1c12ee1eSDan Willemsen wantNotFound bool 468*1c12ee1eSDan Willemsen }{{ 469*1c12ee1eSDan Willemsen name: "testprotos.string_field", 470*1c12ee1eSDan Willemsen extensionType: xt1, 471*1c12ee1eSDan Willemsen }, { 472*1c12ee1eSDan Willemsen name: "testprotos.Message4.message_field", 473*1c12ee1eSDan Willemsen extensionType: xt2, 474*1c12ee1eSDan Willemsen }, { 475*1c12ee1eSDan Willemsen name: "testprotos.None", 476*1c12ee1eSDan Willemsen wantErr: true, 477*1c12ee1eSDan Willemsen wantNotFound: true, 478*1c12ee1eSDan Willemsen }, { 479*1c12ee1eSDan Willemsen name: "testprotos.Message1", 480*1c12ee1eSDan Willemsen wantErr: true, 481*1c12ee1eSDan Willemsen }} 482*1c12ee1eSDan Willemsen for _, tc := range tests { 483*1c12ee1eSDan Willemsen got, err := registry.FindExtensionByName(protoreflect.FullName(tc.name)) 484*1c12ee1eSDan Willemsen gotErr := err != nil 485*1c12ee1eSDan Willemsen if gotErr != tc.wantErr { 486*1c12ee1eSDan Willemsen t.Errorf("FindExtensionByName(%v) = (_, %v), want error? %t", tc.name, err, tc.wantErr) 487*1c12ee1eSDan Willemsen continue 488*1c12ee1eSDan Willemsen } 489*1c12ee1eSDan Willemsen if tc.wantNotFound && err != protoregistry.NotFound { 490*1c12ee1eSDan Willemsen t.Errorf("FindExtensionByName(%v) got error: %v, want NotFound error", tc.name, err) 491*1c12ee1eSDan Willemsen continue 492*1c12ee1eSDan Willemsen } 493*1c12ee1eSDan Willemsen if got != tc.extensionType { 494*1c12ee1eSDan Willemsen t.Errorf("FindExtensionByName(%v) got wrong value: %v", tc.name, got) 495*1c12ee1eSDan Willemsen } 496*1c12ee1eSDan Willemsen } 497*1c12ee1eSDan Willemsen }) 498*1c12ee1eSDan Willemsen 499*1c12ee1eSDan Willemsen t.Run("FindExtensionByNumber", func(t *testing.T) { 500*1c12ee1eSDan Willemsen tests := []struct { 501*1c12ee1eSDan Willemsen parent string 502*1c12ee1eSDan Willemsen number int32 503*1c12ee1eSDan Willemsen extensionType protoreflect.ExtensionType 504*1c12ee1eSDan Willemsen wantErr bool 505*1c12ee1eSDan Willemsen wantNotFound bool 506*1c12ee1eSDan Willemsen }{{ 507*1c12ee1eSDan Willemsen parent: "testprotos.Message1", 508*1c12ee1eSDan Willemsen number: 11, 509*1c12ee1eSDan Willemsen extensionType: xt1, 510*1c12ee1eSDan Willemsen }, { 511*1c12ee1eSDan Willemsen parent: "testprotos.Message1", 512*1c12ee1eSDan Willemsen number: 13, 513*1c12ee1eSDan Willemsen wantErr: true, 514*1c12ee1eSDan Willemsen wantNotFound: true, 515*1c12ee1eSDan Willemsen }, { 516*1c12ee1eSDan Willemsen parent: "testprotos.Message1", 517*1c12ee1eSDan Willemsen number: 21, 518*1c12ee1eSDan Willemsen extensionType: xt2, 519*1c12ee1eSDan Willemsen }, { 520*1c12ee1eSDan Willemsen parent: "testprotos.Message1", 521*1c12ee1eSDan Willemsen number: 23, 522*1c12ee1eSDan Willemsen wantErr: true, 523*1c12ee1eSDan Willemsen wantNotFound: true, 524*1c12ee1eSDan Willemsen }, { 525*1c12ee1eSDan Willemsen parent: "testprotos.NoSuchMessage", 526*1c12ee1eSDan Willemsen number: 11, 527*1c12ee1eSDan Willemsen wantErr: true, 528*1c12ee1eSDan Willemsen wantNotFound: true, 529*1c12ee1eSDan Willemsen }, { 530*1c12ee1eSDan Willemsen parent: "testprotos.Message1", 531*1c12ee1eSDan Willemsen number: 30, 532*1c12ee1eSDan Willemsen wantErr: true, 533*1c12ee1eSDan Willemsen wantNotFound: true, 534*1c12ee1eSDan Willemsen }, { 535*1c12ee1eSDan Willemsen parent: "testprotos.Message1", 536*1c12ee1eSDan Willemsen number: 99, 537*1c12ee1eSDan Willemsen wantErr: true, 538*1c12ee1eSDan Willemsen wantNotFound: true, 539*1c12ee1eSDan Willemsen }} 540*1c12ee1eSDan Willemsen for _, tc := range tests { 541*1c12ee1eSDan Willemsen got, err := registry.FindExtensionByNumber(protoreflect.FullName(tc.parent), protoreflect.FieldNumber(tc.number)) 542*1c12ee1eSDan Willemsen gotErr := err != nil 543*1c12ee1eSDan Willemsen if gotErr != tc.wantErr { 544*1c12ee1eSDan Willemsen t.Errorf("FindExtensionByNumber(%v, %d) = (_, %v), want error? %t", tc.parent, tc.number, err, tc.wantErr) 545*1c12ee1eSDan Willemsen continue 546*1c12ee1eSDan Willemsen } 547*1c12ee1eSDan Willemsen if tc.wantNotFound && err != protoregistry.NotFound { 548*1c12ee1eSDan Willemsen t.Errorf("FindExtensionByNumber(%v, %d) got error %v, want NotFound error", tc.parent, tc.number, err) 549*1c12ee1eSDan Willemsen continue 550*1c12ee1eSDan Willemsen } 551*1c12ee1eSDan Willemsen if got != tc.extensionType { 552*1c12ee1eSDan Willemsen t.Errorf("FindExtensionByNumber(%v, %d) got wrong value: %v", tc.parent, tc.number, got) 553*1c12ee1eSDan Willemsen } 554*1c12ee1eSDan Willemsen } 555*1c12ee1eSDan Willemsen }) 556*1c12ee1eSDan Willemsen 557*1c12ee1eSDan Willemsen sortTypes := cmp.Options{ 558*1c12ee1eSDan Willemsen cmpopts.SortSlices(func(x, y protoreflect.EnumType) bool { 559*1c12ee1eSDan Willemsen return x.Descriptor().FullName() < y.Descriptor().FullName() 560*1c12ee1eSDan Willemsen }), 561*1c12ee1eSDan Willemsen cmpopts.SortSlices(func(x, y protoreflect.MessageType) bool { 562*1c12ee1eSDan Willemsen return x.Descriptor().FullName() < y.Descriptor().FullName() 563*1c12ee1eSDan Willemsen }), 564*1c12ee1eSDan Willemsen cmpopts.SortSlices(func(x, y protoreflect.ExtensionType) bool { 565*1c12ee1eSDan Willemsen return x.TypeDescriptor().FullName() < y.TypeDescriptor().FullName() 566*1c12ee1eSDan Willemsen }), 567*1c12ee1eSDan Willemsen } 568*1c12ee1eSDan Willemsen compare := cmp.Options{ 569*1c12ee1eSDan Willemsen cmp.Comparer(func(x, y protoreflect.EnumType) bool { 570*1c12ee1eSDan Willemsen return x == y 571*1c12ee1eSDan Willemsen }), 572*1c12ee1eSDan Willemsen cmp.Comparer(func(x, y protoreflect.ExtensionType) bool { 573*1c12ee1eSDan Willemsen return x == y 574*1c12ee1eSDan Willemsen }), 575*1c12ee1eSDan Willemsen cmp.Comparer(func(x, y protoreflect.MessageType) bool { 576*1c12ee1eSDan Willemsen return x == y 577*1c12ee1eSDan Willemsen }), 578*1c12ee1eSDan Willemsen } 579*1c12ee1eSDan Willemsen 580*1c12ee1eSDan Willemsen t.Run("RangeEnums", func(t *testing.T) { 581*1c12ee1eSDan Willemsen want := []protoreflect.EnumType{et1} 582*1c12ee1eSDan Willemsen var got []protoreflect.EnumType 583*1c12ee1eSDan Willemsen var gotCnt int 584*1c12ee1eSDan Willemsen wantCnt := registry.NumEnums() 585*1c12ee1eSDan Willemsen registry.RangeEnums(func(et protoreflect.EnumType) bool { 586*1c12ee1eSDan Willemsen got = append(got, et) 587*1c12ee1eSDan Willemsen gotCnt++ 588*1c12ee1eSDan Willemsen return true 589*1c12ee1eSDan Willemsen }) 590*1c12ee1eSDan Willemsen 591*1c12ee1eSDan Willemsen if gotCnt != wantCnt { 592*1c12ee1eSDan Willemsen t.Errorf("NumEnums() = %v, want %v", gotCnt, wantCnt) 593*1c12ee1eSDan Willemsen } 594*1c12ee1eSDan Willemsen if diff := cmp.Diff(want, got, sortTypes, compare); diff != "" { 595*1c12ee1eSDan Willemsen t.Errorf("RangeEnums() mismatch (-want +got):\n%v", diff) 596*1c12ee1eSDan Willemsen } 597*1c12ee1eSDan Willemsen }) 598*1c12ee1eSDan Willemsen 599*1c12ee1eSDan Willemsen t.Run("RangeMessages", func(t *testing.T) { 600*1c12ee1eSDan Willemsen want := []protoreflect.MessageType{mt1} 601*1c12ee1eSDan Willemsen var got []protoreflect.MessageType 602*1c12ee1eSDan Willemsen var gotCnt int 603*1c12ee1eSDan Willemsen wantCnt := registry.NumMessages() 604*1c12ee1eSDan Willemsen registry.RangeMessages(func(mt protoreflect.MessageType) bool { 605*1c12ee1eSDan Willemsen got = append(got, mt) 606*1c12ee1eSDan Willemsen gotCnt++ 607*1c12ee1eSDan Willemsen return true 608*1c12ee1eSDan Willemsen }) 609*1c12ee1eSDan Willemsen 610*1c12ee1eSDan Willemsen if gotCnt != wantCnt { 611*1c12ee1eSDan Willemsen t.Errorf("NumMessages() = %v, want %v", gotCnt, wantCnt) 612*1c12ee1eSDan Willemsen } 613*1c12ee1eSDan Willemsen if diff := cmp.Diff(want, got, sortTypes, compare); diff != "" { 614*1c12ee1eSDan Willemsen t.Errorf("RangeMessages() mismatch (-want +got):\n%v", diff) 615*1c12ee1eSDan Willemsen } 616*1c12ee1eSDan Willemsen }) 617*1c12ee1eSDan Willemsen 618*1c12ee1eSDan Willemsen t.Run("RangeExtensions", func(t *testing.T) { 619*1c12ee1eSDan Willemsen want := []protoreflect.ExtensionType{xt1, xt2} 620*1c12ee1eSDan Willemsen var got []protoreflect.ExtensionType 621*1c12ee1eSDan Willemsen var gotCnt int 622*1c12ee1eSDan Willemsen wantCnt := registry.NumExtensions() 623*1c12ee1eSDan Willemsen registry.RangeExtensions(func(xt protoreflect.ExtensionType) bool { 624*1c12ee1eSDan Willemsen got = append(got, xt) 625*1c12ee1eSDan Willemsen gotCnt++ 626*1c12ee1eSDan Willemsen return true 627*1c12ee1eSDan Willemsen }) 628*1c12ee1eSDan Willemsen 629*1c12ee1eSDan Willemsen if gotCnt != wantCnt { 630*1c12ee1eSDan Willemsen t.Errorf("NumExtensions() = %v, want %v", gotCnt, wantCnt) 631*1c12ee1eSDan Willemsen } 632*1c12ee1eSDan Willemsen if diff := cmp.Diff(want, got, sortTypes, compare); diff != "" { 633*1c12ee1eSDan Willemsen t.Errorf("RangeExtensions() mismatch (-want +got):\n%v", diff) 634*1c12ee1eSDan Willemsen } 635*1c12ee1eSDan Willemsen }) 636*1c12ee1eSDan Willemsen 637*1c12ee1eSDan Willemsen t.Run("RangeExtensionsByMessage", func(t *testing.T) { 638*1c12ee1eSDan Willemsen want := []protoreflect.ExtensionType{xt1, xt2} 639*1c12ee1eSDan Willemsen var got []protoreflect.ExtensionType 640*1c12ee1eSDan Willemsen var gotCnt int 641*1c12ee1eSDan Willemsen wantCnt := registry.NumExtensionsByMessage("testprotos.Message1") 642*1c12ee1eSDan Willemsen registry.RangeExtensionsByMessage("testprotos.Message1", func(xt protoreflect.ExtensionType) bool { 643*1c12ee1eSDan Willemsen got = append(got, xt) 644*1c12ee1eSDan Willemsen gotCnt++ 645*1c12ee1eSDan Willemsen return true 646*1c12ee1eSDan Willemsen }) 647*1c12ee1eSDan Willemsen 648*1c12ee1eSDan Willemsen if gotCnt != wantCnt { 649*1c12ee1eSDan Willemsen t.Errorf("NumExtensionsByMessage() = %v, want %v", gotCnt, wantCnt) 650*1c12ee1eSDan Willemsen } 651*1c12ee1eSDan Willemsen if diff := cmp.Diff(want, got, sortTypes, compare); diff != "" { 652*1c12ee1eSDan Willemsen t.Errorf("RangeExtensionsByMessage() mismatch (-want +got):\n%v", diff) 653*1c12ee1eSDan Willemsen } 654*1c12ee1eSDan Willemsen }) 655*1c12ee1eSDan Willemsen} 656