| // Copyright 2024 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package proto_test |
| |
| import ( |
| "reflect" |
| "testing" |
| "unsafe" |
| |
| "github.com/google/go-cmp/cmp" |
| "google.golang.org/protobuf/internal/impl" |
| testopaquepb "google.golang.org/protobuf/internal/testprotos/testeditions/testeditions_opaque" |
| "google.golang.org/protobuf/proto" |
| "google.golang.org/protobuf/testing/protocmp" |
| ) |
| |
| func fillLazyRequiredMessage() *testopaquepb.TestRequiredLazy { |
| return testopaquepb.TestRequiredLazy_builder{ |
| OptionalLazyMessage: testopaquepb.TestRequired_builder{ |
| RequiredField: proto.Int32(12), |
| }.Build(), |
| }.Build() |
| } |
| |
| func expandedLazy(m *testopaquepb.TestRequiredLazy) bool { |
| v := reflect.ValueOf(m).Elem() |
| rf := v.FieldByName("xxx_hidden_OptionalLazyMessage") |
| rf = reflect.NewAt(rf.Type(), unsafe.Pointer(rf.UnsafeAddr())).Elem() |
| return rf.Pointer() != 0 |
| } |
| |
| // This test ensures that a lazy field keeps being lazy when marshalling |
| // even if it has required fields (as they have already been checked on |
| // unmarshal) |
| func TestLazyRequiredRoundtrip(t *testing.T) { |
| if !impl.LazyEnabled() { |
| t.Skipf("this test requires lazy decoding to be enabled") |
| } |
| m := fillLazyRequiredMessage() |
| b, _ := proto.MarshalOptions{}.Marshal(m) |
| ml := &testopaquepb.TestRequiredLazy{} |
| err := proto.UnmarshalOptions{}.Unmarshal(b, ml) |
| if err != nil { |
| t.Fatalf("Error while unmarshaling: %v", err) |
| } |
| // Sanity check, we should have all unexpanded fields in the proto |
| if expandedLazy(ml) { |
| t.Fatalf("Proto is not lazy: %#v", ml) |
| } |
| // Now we marshal the lazy field. It should still be unexpanded |
| _, _ = proto.MarshalOptions{}.Marshal(ml) |
| |
| if expandedLazy(ml) { |
| t.Errorf("Proto got expanded by marshal: %#v", ml) |
| } |
| |
| // The following tests the current behavior for cases where we |
| // cannot guarantee the integrity of the lazy unmarshalled buffer |
| // because of required fields. This would have to be updated if |
| // we find another way to check required fields than simply |
| // unmarshalling everything that has them when we're not sure. |
| |
| ml = &testopaquepb.TestRequiredLazy{} |
| err = proto.UnmarshalOptions{AllowPartial: true}.Unmarshal(b, ml) |
| if err != nil { |
| t.Fatalf("Error while unmarshaling: %v", err) |
| } |
| // Sanity check, we should have all unexpanded fields in the proto. |
| if expandedLazy(ml) { |
| t.Fatalf("Proto is not lazy: %#v", ml) |
| } |
| // Now we marshal the proto. The lazy fields will be expanded to |
| // check required fields. |
| _, _ = proto.MarshalOptions{}.Marshal(ml) |
| |
| if !expandedLazy(ml) { |
| t.Errorf("Proto did not get expanded by marshal: %#v", ml) |
| } |
| |
| // Finally, we test to see that the fields to not get expanded |
| // if we are consistently using AllowPartial both for marshal |
| // and unmarshal. |
| ml = &testopaquepb.TestRequiredLazy{} |
| err = proto.UnmarshalOptions{AllowPartial: true}.Unmarshal(b, ml) |
| if err != nil { |
| t.Fatalf("Error while unmarshaling: %v", err) |
| } |
| // Sanity check, we should have all unexpanded fields in the proto. |
| if expandedLazy(ml) { |
| t.Fatalf("Proto is not lazy: %#v", ml) |
| } |
| // Now we marshal the proto. The lazy fields will be expanded to |
| // check required fields. |
| _, _ = proto.MarshalOptions{AllowPartial: true}.Marshal(ml) |
| |
| if expandedLazy(ml) { |
| t.Errorf("Proto did not get expanded by marshal: %#v", ml) |
| } |
| |
| } |
| |
| func TestRoundtripMap(t *testing.T) { |
| m := testopaquepb.TestAllTypes_builder{ |
| OptionalLazyNestedMessage: testopaquepb.TestAllTypes_NestedMessage_builder{ |
| Corecursive: testopaquepb.TestAllTypes_builder{ |
| MapStringString: map[string]string{ |
| "a": "b", |
| }, |
| }.Build(), |
| }.Build(), |
| }.Build() |
| b, err := proto.Marshal(m) |
| if err != nil { |
| t.Fatalf("proto.Marshal: %v", err) |
| } |
| got := &testopaquepb.TestAllTypes{} |
| if err := proto.Unmarshal(b, got); err != nil { |
| t.Fatalf("proto.Unmarshal: %v", err) |
| } |
| if diff := cmp.Diff(m, got, protocmp.Transform()); diff != "" { |
| t.Errorf("not the same: diff (-want +got):\n%s", diff) |
| } |
| } |