1*1c12ee1eSDan Willemsen// Copyright 2019 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 proto_test 6*1c12ee1eSDan Willemsen 7*1c12ee1eSDan Willemsenimport ( 8*1c12ee1eSDan Willemsen "fmt" 9*1c12ee1eSDan Willemsen "reflect" 10*1c12ee1eSDan Willemsen "sync" 11*1c12ee1eSDan Willemsen "testing" 12*1c12ee1eSDan Willemsen 13*1c12ee1eSDan Willemsen "github.com/google/go-cmp/cmp" 14*1c12ee1eSDan Willemsen 15*1c12ee1eSDan Willemsen "google.golang.org/protobuf/proto" 16*1c12ee1eSDan Willemsen "google.golang.org/protobuf/reflect/protoreflect" 17*1c12ee1eSDan Willemsen "google.golang.org/protobuf/runtime/protoimpl" 18*1c12ee1eSDan Willemsen "google.golang.org/protobuf/testing/protocmp" 19*1c12ee1eSDan Willemsen 20*1c12ee1eSDan Willemsen legacy1pb "google.golang.org/protobuf/internal/testprotos/legacy/proto2_20160225_2fc053c5" 21*1c12ee1eSDan Willemsen testpb "google.golang.org/protobuf/internal/testprotos/test" 22*1c12ee1eSDan Willemsen test3pb "google.golang.org/protobuf/internal/testprotos/test3" 23*1c12ee1eSDan Willemsen descpb "google.golang.org/protobuf/types/descriptorpb" 24*1c12ee1eSDan Willemsen) 25*1c12ee1eSDan Willemsen 26*1c12ee1eSDan Willemsenfunc TestExtensionFuncs(t *testing.T) { 27*1c12ee1eSDan Willemsen for _, test := range []struct { 28*1c12ee1eSDan Willemsen message proto.Message 29*1c12ee1eSDan Willemsen ext protoreflect.ExtensionType 30*1c12ee1eSDan Willemsen wantDefault interface{} 31*1c12ee1eSDan Willemsen value interface{} 32*1c12ee1eSDan Willemsen }{ 33*1c12ee1eSDan Willemsen { 34*1c12ee1eSDan Willemsen message: &testpb.TestAllExtensions{}, 35*1c12ee1eSDan Willemsen ext: testpb.E_OptionalInt32, 36*1c12ee1eSDan Willemsen wantDefault: int32(0), 37*1c12ee1eSDan Willemsen value: int32(1), 38*1c12ee1eSDan Willemsen }, 39*1c12ee1eSDan Willemsen { 40*1c12ee1eSDan Willemsen message: &testpb.TestAllExtensions{}, 41*1c12ee1eSDan Willemsen ext: testpb.E_RepeatedString, 42*1c12ee1eSDan Willemsen wantDefault: ([]string)(nil), 43*1c12ee1eSDan Willemsen value: []string{"a", "b", "c"}, 44*1c12ee1eSDan Willemsen }, 45*1c12ee1eSDan Willemsen { 46*1c12ee1eSDan Willemsen message: protoimpl.X.MessageOf(&legacy1pb.Message{}).Interface(), 47*1c12ee1eSDan Willemsen ext: legacy1pb.E_Message_ExtensionOptionalBool, 48*1c12ee1eSDan Willemsen wantDefault: false, 49*1c12ee1eSDan Willemsen value: true, 50*1c12ee1eSDan Willemsen }, 51*1c12ee1eSDan Willemsen } { 52*1c12ee1eSDan Willemsen desc := fmt.Sprintf("Extension %v, value %v", test.ext.TypeDescriptor().FullName(), test.value) 53*1c12ee1eSDan Willemsen if proto.HasExtension(test.message, test.ext) { 54*1c12ee1eSDan Willemsen t.Errorf("%v:\nbefore setting extension HasExtension(...) = true, want false", desc) 55*1c12ee1eSDan Willemsen } 56*1c12ee1eSDan Willemsen got := proto.GetExtension(test.message, test.ext) 57*1c12ee1eSDan Willemsen if d := cmp.Diff(test.wantDefault, got); d != "" { 58*1c12ee1eSDan Willemsen t.Errorf("%v:\nbefore setting extension GetExtension(...) returns unexpected value (-want,+got):\n%v", desc, d) 59*1c12ee1eSDan Willemsen } 60*1c12ee1eSDan Willemsen proto.SetExtension(test.message, test.ext, test.value) 61*1c12ee1eSDan Willemsen if !proto.HasExtension(test.message, test.ext) { 62*1c12ee1eSDan Willemsen t.Errorf("%v:\nafter setting extension HasExtension(...) = false, want true", desc) 63*1c12ee1eSDan Willemsen } 64*1c12ee1eSDan Willemsen got = proto.GetExtension(test.message, test.ext) 65*1c12ee1eSDan Willemsen if d := cmp.Diff(test.value, got); d != "" { 66*1c12ee1eSDan Willemsen t.Errorf("%v:\nafter setting extension GetExtension(...) returns unexpected value (-want,+got):\n%v", desc, d) 67*1c12ee1eSDan Willemsen } 68*1c12ee1eSDan Willemsen proto.ClearExtension(test.message, test.ext) 69*1c12ee1eSDan Willemsen if proto.HasExtension(test.message, test.ext) { 70*1c12ee1eSDan Willemsen t.Errorf("%v:\nafter clearing extension HasExtension(...) = true, want false", desc) 71*1c12ee1eSDan Willemsen } 72*1c12ee1eSDan Willemsen } 73*1c12ee1eSDan Willemsen} 74*1c12ee1eSDan Willemsen 75*1c12ee1eSDan Willemsenfunc TestIsValid(t *testing.T) { 76*1c12ee1eSDan Willemsen tests := []struct { 77*1c12ee1eSDan Willemsen xt protoreflect.ExtensionType 78*1c12ee1eSDan Willemsen vi interface{} 79*1c12ee1eSDan Willemsen want bool 80*1c12ee1eSDan Willemsen }{ 81*1c12ee1eSDan Willemsen {testpb.E_OptionalBool, nil, false}, 82*1c12ee1eSDan Willemsen {testpb.E_OptionalBool, bool(true), true}, 83*1c12ee1eSDan Willemsen {testpb.E_OptionalBool, new(bool), false}, 84*1c12ee1eSDan Willemsen {testpb.E_OptionalInt32, nil, false}, 85*1c12ee1eSDan Willemsen {testpb.E_OptionalInt32, int32(0), true}, 86*1c12ee1eSDan Willemsen {testpb.E_OptionalInt32, new(int32), false}, 87*1c12ee1eSDan Willemsen {testpb.E_OptionalInt64, nil, false}, 88*1c12ee1eSDan Willemsen {testpb.E_OptionalInt64, int64(0), true}, 89*1c12ee1eSDan Willemsen {testpb.E_OptionalInt64, new(int64), false}, 90*1c12ee1eSDan Willemsen {testpb.E_OptionalUint32, nil, false}, 91*1c12ee1eSDan Willemsen {testpb.E_OptionalUint32, uint32(0), true}, 92*1c12ee1eSDan Willemsen {testpb.E_OptionalUint32, new(uint32), false}, 93*1c12ee1eSDan Willemsen {testpb.E_OptionalUint64, nil, false}, 94*1c12ee1eSDan Willemsen {testpb.E_OptionalUint64, uint64(0), true}, 95*1c12ee1eSDan Willemsen {testpb.E_OptionalUint64, new(uint64), false}, 96*1c12ee1eSDan Willemsen {testpb.E_OptionalFloat, nil, false}, 97*1c12ee1eSDan Willemsen {testpb.E_OptionalFloat, float32(0), true}, 98*1c12ee1eSDan Willemsen {testpb.E_OptionalFloat, new(float32), false}, 99*1c12ee1eSDan Willemsen {testpb.E_OptionalDouble, nil, false}, 100*1c12ee1eSDan Willemsen {testpb.E_OptionalDouble, float64(0), true}, 101*1c12ee1eSDan Willemsen {testpb.E_OptionalDouble, new(float32), false}, 102*1c12ee1eSDan Willemsen {testpb.E_OptionalString, nil, false}, 103*1c12ee1eSDan Willemsen {testpb.E_OptionalString, string(""), true}, 104*1c12ee1eSDan Willemsen {testpb.E_OptionalString, new(string), false}, 105*1c12ee1eSDan Willemsen {testpb.E_OptionalNestedEnum, nil, false}, 106*1c12ee1eSDan Willemsen {testpb.E_OptionalNestedEnum, testpb.TestAllTypes_BAZ, true}, 107*1c12ee1eSDan Willemsen {testpb.E_OptionalNestedEnum, testpb.TestAllTypes_BAZ.Enum(), false}, 108*1c12ee1eSDan Willemsen {testpb.E_OptionalNestedMessage, nil, false}, 109*1c12ee1eSDan Willemsen {testpb.E_OptionalNestedMessage, (*testpb.TestAllExtensions_NestedMessage)(nil), true}, 110*1c12ee1eSDan Willemsen {testpb.E_OptionalNestedMessage, new(testpb.TestAllExtensions_NestedMessage), true}, 111*1c12ee1eSDan Willemsen {testpb.E_OptionalNestedMessage, new(testpb.TestAllExtensions), false}, 112*1c12ee1eSDan Willemsen {testpb.E_RepeatedBool, nil, false}, 113*1c12ee1eSDan Willemsen {testpb.E_RepeatedBool, []bool(nil), true}, 114*1c12ee1eSDan Willemsen {testpb.E_RepeatedBool, []bool{}, true}, 115*1c12ee1eSDan Willemsen {testpb.E_RepeatedBool, []bool{false}, true}, 116*1c12ee1eSDan Willemsen {testpb.E_RepeatedBool, []*bool{}, false}, 117*1c12ee1eSDan Willemsen {testpb.E_RepeatedInt32, nil, false}, 118*1c12ee1eSDan Willemsen {testpb.E_RepeatedInt32, []int32(nil), true}, 119*1c12ee1eSDan Willemsen {testpb.E_RepeatedInt32, []int32{}, true}, 120*1c12ee1eSDan Willemsen {testpb.E_RepeatedInt32, []int32{0}, true}, 121*1c12ee1eSDan Willemsen {testpb.E_RepeatedInt32, []*int32{}, false}, 122*1c12ee1eSDan Willemsen {testpb.E_RepeatedInt64, nil, false}, 123*1c12ee1eSDan Willemsen {testpb.E_RepeatedInt64, []int64(nil), true}, 124*1c12ee1eSDan Willemsen {testpb.E_RepeatedInt64, []int64{}, true}, 125*1c12ee1eSDan Willemsen {testpb.E_RepeatedInt64, []int64{0}, true}, 126*1c12ee1eSDan Willemsen {testpb.E_RepeatedInt64, []*int64{}, false}, 127*1c12ee1eSDan Willemsen {testpb.E_RepeatedUint32, nil, false}, 128*1c12ee1eSDan Willemsen {testpb.E_RepeatedUint32, []uint32(nil), true}, 129*1c12ee1eSDan Willemsen {testpb.E_RepeatedUint32, []uint32{}, true}, 130*1c12ee1eSDan Willemsen {testpb.E_RepeatedUint32, []uint32{0}, true}, 131*1c12ee1eSDan Willemsen {testpb.E_RepeatedUint32, []*uint32{}, false}, 132*1c12ee1eSDan Willemsen {testpb.E_RepeatedUint64, nil, false}, 133*1c12ee1eSDan Willemsen {testpb.E_RepeatedUint64, []uint64(nil), true}, 134*1c12ee1eSDan Willemsen {testpb.E_RepeatedUint64, []uint64{}, true}, 135*1c12ee1eSDan Willemsen {testpb.E_RepeatedUint64, []uint64{0}, true}, 136*1c12ee1eSDan Willemsen {testpb.E_RepeatedUint64, []*uint64{}, false}, 137*1c12ee1eSDan Willemsen {testpb.E_RepeatedFloat, nil, false}, 138*1c12ee1eSDan Willemsen {testpb.E_RepeatedFloat, []float32(nil), true}, 139*1c12ee1eSDan Willemsen {testpb.E_RepeatedFloat, []float32{}, true}, 140*1c12ee1eSDan Willemsen {testpb.E_RepeatedFloat, []float32{0}, true}, 141*1c12ee1eSDan Willemsen {testpb.E_RepeatedFloat, []*float32{}, false}, 142*1c12ee1eSDan Willemsen {testpb.E_RepeatedDouble, nil, false}, 143*1c12ee1eSDan Willemsen {testpb.E_RepeatedDouble, []float64(nil), true}, 144*1c12ee1eSDan Willemsen {testpb.E_RepeatedDouble, []float64{}, true}, 145*1c12ee1eSDan Willemsen {testpb.E_RepeatedDouble, []float64{0}, true}, 146*1c12ee1eSDan Willemsen {testpb.E_RepeatedDouble, []*float64{}, false}, 147*1c12ee1eSDan Willemsen {testpb.E_RepeatedString, nil, false}, 148*1c12ee1eSDan Willemsen {testpb.E_RepeatedString, []string(nil), true}, 149*1c12ee1eSDan Willemsen {testpb.E_RepeatedString, []string{}, true}, 150*1c12ee1eSDan Willemsen {testpb.E_RepeatedString, []string{""}, true}, 151*1c12ee1eSDan Willemsen {testpb.E_RepeatedString, []*string{}, false}, 152*1c12ee1eSDan Willemsen {testpb.E_RepeatedNestedEnum, nil, false}, 153*1c12ee1eSDan Willemsen {testpb.E_RepeatedNestedEnum, []testpb.TestAllTypes_NestedEnum(nil), true}, 154*1c12ee1eSDan Willemsen {testpb.E_RepeatedNestedEnum, []testpb.TestAllTypes_NestedEnum{}, true}, 155*1c12ee1eSDan Willemsen {testpb.E_RepeatedNestedEnum, []testpb.TestAllTypes_NestedEnum{0}, true}, 156*1c12ee1eSDan Willemsen {testpb.E_RepeatedNestedEnum, []*testpb.TestAllTypes_NestedEnum{}, false}, 157*1c12ee1eSDan Willemsen {testpb.E_RepeatedNestedMessage, nil, false}, 158*1c12ee1eSDan Willemsen {testpb.E_RepeatedNestedMessage, []*testpb.TestAllExtensions_NestedMessage(nil), true}, 159*1c12ee1eSDan Willemsen {testpb.E_RepeatedNestedMessage, []*testpb.TestAllExtensions_NestedMessage{}, true}, 160*1c12ee1eSDan Willemsen {testpb.E_RepeatedNestedMessage, []*testpb.TestAllExtensions_NestedMessage{{}}, true}, 161*1c12ee1eSDan Willemsen {testpb.E_RepeatedNestedMessage, []*testpb.TestAllExtensions{}, false}, 162*1c12ee1eSDan Willemsen } 163*1c12ee1eSDan Willemsen 164*1c12ee1eSDan Willemsen for _, tt := range tests { 165*1c12ee1eSDan Willemsen // Check the results of IsValidInterface. 166*1c12ee1eSDan Willemsen got := tt.xt.IsValidInterface(tt.vi) 167*1c12ee1eSDan Willemsen if got != tt.want { 168*1c12ee1eSDan Willemsen t.Errorf("%v.IsValidInterface() = %v, want %v", tt.xt.TypeDescriptor().FullName(), got, tt.want) 169*1c12ee1eSDan Willemsen } 170*1c12ee1eSDan Willemsen if !got { 171*1c12ee1eSDan Willemsen continue 172*1c12ee1eSDan Willemsen } 173*1c12ee1eSDan Willemsen 174*1c12ee1eSDan Willemsen // Set the extension value and verify the results of Has. 175*1c12ee1eSDan Willemsen wantHas := true 176*1c12ee1eSDan Willemsen pv := tt.xt.ValueOf(tt.vi) 177*1c12ee1eSDan Willemsen switch v := pv.Interface().(type) { 178*1c12ee1eSDan Willemsen case protoreflect.List: 179*1c12ee1eSDan Willemsen wantHas = v.Len() > 0 180*1c12ee1eSDan Willemsen case protoreflect.Message: 181*1c12ee1eSDan Willemsen wantHas = v.IsValid() 182*1c12ee1eSDan Willemsen } 183*1c12ee1eSDan Willemsen m := &testpb.TestAllExtensions{} 184*1c12ee1eSDan Willemsen proto.SetExtension(m, tt.xt, tt.vi) 185*1c12ee1eSDan Willemsen gotHas := proto.HasExtension(m, tt.xt) 186*1c12ee1eSDan Willemsen if gotHas != wantHas { 187*1c12ee1eSDan Willemsen t.Errorf("HasExtension(%q) = %v, want %v", tt.xt.TypeDescriptor().FullName(), gotHas, wantHas) 188*1c12ee1eSDan Willemsen } 189*1c12ee1eSDan Willemsen 190*1c12ee1eSDan Willemsen // Check consistency of IsValidInterface and IsValidValue. 191*1c12ee1eSDan Willemsen got = tt.xt.IsValidValue(pv) 192*1c12ee1eSDan Willemsen if got != tt.want { 193*1c12ee1eSDan Willemsen t.Errorf("%v.IsValidValue() = %v, want %v", tt.xt.TypeDescriptor().FullName(), got, tt.want) 194*1c12ee1eSDan Willemsen } 195*1c12ee1eSDan Willemsen if !got { 196*1c12ee1eSDan Willemsen continue 197*1c12ee1eSDan Willemsen } 198*1c12ee1eSDan Willemsen 199*1c12ee1eSDan Willemsen // Use of reflect.DeepEqual is intentional. 200*1c12ee1eSDan Willemsen // We really do want to ensure that the memory layout is identical. 201*1c12ee1eSDan Willemsen vi := tt.xt.InterfaceOf(pv) 202*1c12ee1eSDan Willemsen if !reflect.DeepEqual(vi, tt.vi) { 203*1c12ee1eSDan Willemsen t.Errorf("InterfaceOf(ValueOf(...)) round-trip mismatch: got %v, want %v", vi, tt.vi) 204*1c12ee1eSDan Willemsen } 205*1c12ee1eSDan Willemsen } 206*1c12ee1eSDan Willemsen} 207*1c12ee1eSDan Willemsen 208*1c12ee1eSDan Willemsenfunc TestExtensionRanger(t *testing.T) { 209*1c12ee1eSDan Willemsen tests := []struct { 210*1c12ee1eSDan Willemsen msg proto.Message 211*1c12ee1eSDan Willemsen want map[protoreflect.ExtensionType]interface{} 212*1c12ee1eSDan Willemsen }{{ 213*1c12ee1eSDan Willemsen msg: &testpb.TestAllExtensions{}, 214*1c12ee1eSDan Willemsen want: map[protoreflect.ExtensionType]interface{}{ 215*1c12ee1eSDan Willemsen testpb.E_OptionalInt32: int32(5), 216*1c12ee1eSDan Willemsen testpb.E_OptionalString: string("hello"), 217*1c12ee1eSDan Willemsen testpb.E_OptionalNestedMessage: &testpb.TestAllExtensions_NestedMessage{}, 218*1c12ee1eSDan Willemsen testpb.E_OptionalNestedEnum: testpb.TestAllTypes_BAZ, 219*1c12ee1eSDan Willemsen testpb.E_RepeatedFloat: []float32{+32.32, -32.32}, 220*1c12ee1eSDan Willemsen testpb.E_RepeatedNestedMessage: []*testpb.TestAllExtensions_NestedMessage{{}}, 221*1c12ee1eSDan Willemsen testpb.E_RepeatedNestedEnum: []testpb.TestAllTypes_NestedEnum{testpb.TestAllTypes_BAZ}, 222*1c12ee1eSDan Willemsen }, 223*1c12ee1eSDan Willemsen }, { 224*1c12ee1eSDan Willemsen msg: &descpb.MessageOptions{}, 225*1c12ee1eSDan Willemsen want: map[protoreflect.ExtensionType]interface{}{ 226*1c12ee1eSDan Willemsen test3pb.E_OptionalInt32: int32(5), 227*1c12ee1eSDan Willemsen test3pb.E_OptionalString: string("hello"), 228*1c12ee1eSDan Willemsen test3pb.E_OptionalForeignMessage: &test3pb.ForeignMessage{}, 229*1c12ee1eSDan Willemsen test3pb.E_OptionalForeignEnum: test3pb.ForeignEnum_FOREIGN_BAR, 230*1c12ee1eSDan Willemsen 231*1c12ee1eSDan Willemsen test3pb.E_OptionalOptionalInt32: int32(5), 232*1c12ee1eSDan Willemsen test3pb.E_OptionalOptionalString: string("hello"), 233*1c12ee1eSDan Willemsen test3pb.E_OptionalOptionalForeignMessage: &test3pb.ForeignMessage{}, 234*1c12ee1eSDan Willemsen test3pb.E_OptionalOptionalForeignEnum: test3pb.ForeignEnum_FOREIGN_BAR, 235*1c12ee1eSDan Willemsen }, 236*1c12ee1eSDan Willemsen }} 237*1c12ee1eSDan Willemsen 238*1c12ee1eSDan Willemsen for _, tt := range tests { 239*1c12ee1eSDan Willemsen for xt, v := range tt.want { 240*1c12ee1eSDan Willemsen proto.SetExtension(tt.msg, xt, v) 241*1c12ee1eSDan Willemsen } 242*1c12ee1eSDan Willemsen 243*1c12ee1eSDan Willemsen got := make(map[protoreflect.ExtensionType]interface{}) 244*1c12ee1eSDan Willemsen proto.RangeExtensions(tt.msg, func(xt protoreflect.ExtensionType, v interface{}) bool { 245*1c12ee1eSDan Willemsen got[xt] = v 246*1c12ee1eSDan Willemsen return true 247*1c12ee1eSDan Willemsen }) 248*1c12ee1eSDan Willemsen 249*1c12ee1eSDan Willemsen if diff := cmp.Diff(tt.want, got, protocmp.Transform()); diff != "" { 250*1c12ee1eSDan Willemsen t.Errorf("proto.RangeExtensions mismatch (-want +got):\n%s", diff) 251*1c12ee1eSDan Willemsen } 252*1c12ee1eSDan Willemsen } 253*1c12ee1eSDan Willemsen} 254*1c12ee1eSDan Willemsen 255*1c12ee1eSDan Willemsenfunc TestExtensionGetRace(t *testing.T) { 256*1c12ee1eSDan Willemsen // Concurrently fetch an extension value while marshaling the message containing it. 257*1c12ee1eSDan Willemsen // Create the message with proto.Unmarshal to give lazy extension decoding (if present) 258*1c12ee1eSDan Willemsen // a chance to occur. 259*1c12ee1eSDan Willemsen want := int32(42) 260*1c12ee1eSDan Willemsen m1 := &testpb.TestAllExtensions{} 261*1c12ee1eSDan Willemsen proto.SetExtension(m1, testpb.E_OptionalNestedMessage, &testpb.TestAllExtensions_NestedMessage{A: proto.Int32(want)}) 262*1c12ee1eSDan Willemsen b, err := proto.Marshal(m1) 263*1c12ee1eSDan Willemsen if err != nil { 264*1c12ee1eSDan Willemsen t.Fatal(err) 265*1c12ee1eSDan Willemsen } 266*1c12ee1eSDan Willemsen m := &testpb.TestAllExtensions{} 267*1c12ee1eSDan Willemsen if err := proto.Unmarshal(b, m); err != nil { 268*1c12ee1eSDan Willemsen t.Fatal(err) 269*1c12ee1eSDan Willemsen } 270*1c12ee1eSDan Willemsen var wg sync.WaitGroup 271*1c12ee1eSDan Willemsen for i := 0; i < 3; i++ { 272*1c12ee1eSDan Willemsen wg.Add(1) 273*1c12ee1eSDan Willemsen go func() { 274*1c12ee1eSDan Willemsen defer wg.Done() 275*1c12ee1eSDan Willemsen if _, err := proto.Marshal(m); err != nil { 276*1c12ee1eSDan Willemsen t.Error(err) 277*1c12ee1eSDan Willemsen } 278*1c12ee1eSDan Willemsen }() 279*1c12ee1eSDan Willemsen wg.Add(1) 280*1c12ee1eSDan Willemsen go func() { 281*1c12ee1eSDan Willemsen defer wg.Done() 282*1c12ee1eSDan Willemsen got := proto.GetExtension(m, testpb.E_OptionalNestedMessage).(*testpb.TestAllExtensions_NestedMessage).GetA() 283*1c12ee1eSDan Willemsen if got != want { 284*1c12ee1eSDan Willemsen t.Errorf("GetExtension(optional_nested_message).a = %v, want %v", got, want) 285*1c12ee1eSDan Willemsen } 286*1c12ee1eSDan Willemsen }() 287*1c12ee1eSDan Willemsen } 288*1c12ee1eSDan Willemsen wg.Wait() 289*1c12ee1eSDan Willemsen} 290