func BenchmarkQuery(b *testing.B) { dir1, dir2, ps, clog, err := prepare() if err != nil { b.Error(err) return } defer closeAll(dir1, dir2, clog) b.ResetTimer() for i := 0; i < b.N; i++ { gq, _, err := gql.Parse(q1) if err != nil { b.Error(err) return } g, err := query.ToSubGraph(gq, ps) if err != nil { b.Error(err) return } ch := make(chan error) go query.ProcessGraph(g, ch, ps) if err := <-ch; err != nil { b.Error(err) return } var l query.Latency _, err = g.ToJson(&l) if err != nil { b.Error(err) return } } }
func TestToProtoMultiRoot(t *testing.T) { dir, dir2, _ := populateGraph(t) defer os.RemoveAll(dir) defer os.RemoveAll(dir2) query := ` { me(anyof("name", "Michonne Rick Glenn")) { name } } ` gq, _, err := gql.Parse(query) require.NoError(t, err) ctx := context.Background() sg, err := ToSubGraph(ctx, gq) require.NoError(t, err) ch := make(chan error) go ProcessGraph(ctx, sg, nil, ch) err = <-ch require.NoError(t, err) var l Latency pb, err := sg.ToProtocolBuffer(&l) require.NoError(t, err) expectedPb := `attribute: "_root_" children: < attribute: "me" properties: < prop: "name" value: < str_val: "Michonne" > > > children: < attribute: "me" properties: < prop: "name" value: < str_val: "Rick Grimes" > > > children: < attribute: "me" properties: < prop: "name" value: < str_val: "Glenn Rhee" > > > ` require.EqualValues(t, expectedPb, proto.MarshalTextString(pb)) }
func TestQuery(t *testing.T) { dir1, dir2, _, err := prepare() require.NoError(t, err) defer closeAll(dir1, dir2) // Parse GQL into internal query representation. gq, _, err := gql.Parse(q0) require.NoError(t, err) ctx := context.Background() g, err := query.ToSubGraph(ctx, gq) require.NoError(t, err) // Test internal query representation. require.EqualValues(t, childAttrs(g), []string{"follows", "_xid_", "status"}) require.EqualValues(t, childAttrs(g.Children[0]), []string{"_xid_", "status"}) ch := make(chan error) go query.ProcessGraph(ctx, g, nil, ch) err = <-ch require.NoError(t, err) var l query.Latency js, err := g.ToJSON(&l) require.NoError(t, err) fmt.Println(string(js)) }
func queryHandler(w http.ResponseWriter, r *http.Request) { addCorsHeaders(w) if r.Method == "OPTIONS" { return } if r.Method != "POST" { x.SetStatus(w, x.E_INVALID_METHOD, "Invalid method") return } var l query.Latency l.Start = time.Now() defer r.Body.Close() q, err := ioutil.ReadAll(r.Body) if err != nil || len(q) == 0 { x.Err(glog, err).Error("While reading query") x.SetStatus(w, x.E_INVALID_REQUEST, "Invalid request encountered.") return } glog.WithField("q", string(q)).Debug("Query received.") sg, err := gql.Parse(string(q)) if err != nil { x.Err(glog, err).Error("While parsing query") x.SetStatus(w, x.E_INVALID_REQUEST, err.Error()) return } l.Parsing = time.Since(l.Start) glog.WithField("q", string(q)).Debug("Query parsed.") rch := make(chan error) go query.ProcessGraph(sg, rch) err = <-rch if err != nil { x.Err(glog, err).Error("While executing query") x.SetStatus(w, x.E_ERROR, err.Error()) return } l.Processing = time.Since(l.Start) - l.Parsing glog.WithField("q", string(q)).Debug("Graph processed.") js, err := sg.ToJson(&l) if err != nil { x.Err(glog, err).Error("While converting to Json.") x.SetStatus(w, x.E_ERROR, err.Error()) return } glog.WithFields(logrus.Fields{ "total": time.Since(l.Start), "parsing": l.Parsing, "process": l.Processing, "json": l.Json, }).Info("Query Latencies") w.Header().Set("Content-Type", "application/json") fmt.Fprint(w, string(js)) }
func TestToProtoFilterAnd(t *testing.T) { dir, dir2, _ := populateGraph(t) defer os.RemoveAll(dir) defer os.RemoveAll(dir2) // Alright. Now we have everything set up. Let's create the query. query := ` { me(_uid_:0x01) { name gender friend @filter(anyof("name", "Andrea") && anyof("name", "Glenn Rhee")) { name } } } ` gq, _, err := gql.Parse(query) require.NoError(t, err) ctx := context.Background() sg, err := ToSubGraph(ctx, gq) require.NoError(t, err) ch := make(chan error) go ProcessGraph(ctx, sg, nil, ch) err = <-ch require.NoError(t, err) var l Latency pb, err := sg.ToProtocolBuffer(&l) require.NoError(t, err) expectedPb := `attribute: "_root_" children: < attribute: "me" properties: < prop: "name" value: < str_val: "Michonne" > > properties: < prop: "gender" value: < bytes_val: "female" > > > ` require.EqualValues(t, expectedPb, proto.MarshalTextString(pb)) }
func TestProcessGraph(t *testing.T) { dir, dir2, _ := populateGraph(t) defer os.RemoveAll(dir) defer os.RemoveAll(dir2) // Alright. Now we have everything set up. Let's create the query. query := ` { me(_uid_: 0x01) { friend { name } name gender alive } } ` gq, _, err := gql.Parse(query) require.NoError(t, err) ctx := context.Background() sg, err := ToSubGraph(ctx, gq) require.NoError(t, err) ch := make(chan error) go ProcessGraph(ctx, sg, nil, ch) err = <-ch require.NoError(t, err) require.EqualValues(t, childAttrs(sg), []string{"friend", "name", "gender", "alive"}) require.EqualValues(t, childAttrs(sg.Children[0]), []string{"name"}) child := sg.Children[0] require.EqualValues(t, [][]uint64{ []uint64{23, 24, 25, 31, 101}, }, algo.ToUintsListForTest(child.uidMatrix)) require.EqualValues(t, []string{"name"}, childAttrs(child)) child = child.Children[0] require.EqualValues(t, []string{"Rick Grimes", "Glenn Rhee", "Daryl Dixon", "Andrea", ""}, taskValues(t, child.values)) require.EqualValues(t, []string{"Michonne"}, taskValues(t, sg.Children[1].values)) require.EqualValues(t, []string{"female"}, taskValues(t, sg.Children[2].values)) }
func TestAssignUid(t *testing.T) { dir1, dir2, _, err := prepare() require.NoError(t, err) defer closeAll(dir1, dir2) time.Sleep(5 * time.Second) // Wait for ME to become leader. // Parse GQL into internal query representation. _, mu, err := gql.Parse(qm) require.NoError(t, err) ctx := context.Background() allocIds, err := mutationHandler(ctx, mu) require.NoError(t, err) require.EqualValues(t, len(allocIds), 2, "Expected two UIDs to be allocated") _, ok := allocIds["x"] require.True(t, ok) _, ok = allocIds["y"] require.True(t, ok) }
func processToJSON(t *testing.T, query string) string { gq, _, err := gql.Parse(query) require.NoError(t, err) ctx := context.Background() sg, err := ToSubGraph(ctx, gq) require.NoError(t, err) sg.DebugPrint("") ch := make(chan error) go ProcessGraph(ctx, sg, nil, ch) err = <-ch require.NoError(t, err) sg.DebugPrint("") var l Latency js, err := sg.ToJSON(&l) require.NoError(t, err) return string(js) }
func TestToJson(t *testing.T) { dir, ps := populateGraph(t) defer os.RemoveAll(dir) // Alright. Now we have everything set up. Let's create the query. query := ` { me(_uid_:0x01) { name gender status friend { name } } } ` gq, _, err := gql.Parse(query) if err != nil { t.Error(err) } sg, err := ToSubGraph(gq, ps) if err != nil { t.Error(err) } ch := make(chan error) go ProcessGraph(sg, ch, ps) err = <-ch if err != nil { t.Error(err) } var l Latency js, err := sg.ToJson(&l) if err != nil { t.Error(err) } fmt.Printf(string(js)) }
func TestCountError1(t *testing.T) { // Alright. Now we have everything set up. Let's create the query. query := ` { me(_uid_: 0x01) { friend { name _count_ } name gender alive } } ` gq, _, err := gql.Parse(query) require.NoError(t, err) ctx := context.Background() _, err = ToSubGraph(ctx, gq) require.Error(t, err) }
func BenchmarkQuery(b *testing.B) { dir1, dir2, _, err := prepare() if err != nil { b.Error(err) return } defer closeAll(dir1, dir2) b.ResetTimer() for i := 0; i < b.N; i++ { gq, _, err := gql.Parse(q1) if err != nil { b.Error(err) return } ctx := context.Background() g, err := query.ToSubGraph(ctx, gq) if err != nil { b.Error(err) return } ch := make(chan error) go query.ProcessGraph(ctx, g, nil, ch) if err := <-ch; err != nil { b.Error(err) return } var l query.Latency _, err = g.ToJSON(&l) if err != nil { b.Error(err) return } } }
// This method is used to execute the query and return the response to the // client as a protocol buffer message. func (s *grpcServer) Query(ctx context.Context, req *graph.Request) (*graph.Response, error) { var allocIds map[string]uint64 if rand.Float64() < *tracing { tr := trace.New("Dgraph", "GrpcQuery") defer tr.Finish() ctx = trace.NewContext(ctx, tr) } resp := new(graph.Response) if len(req.Query) == 0 && req.Mutation == nil { x.TraceError(ctx, x.Errorf("Empty query and mutation.")) return resp, fmt.Errorf("Empty query and mutation.") } var l query.Latency l.Start = time.Now() x.Trace(ctx, "Query received: %v", req.Query) gq, mu, err := gql.Parse(req.Query) if err != nil { x.TraceError(ctx, x.Wrapf(err, "Error while parsing query")) return resp, err } // If mutations are part of the query, we run them through the mutation handler // same as the http client. if mu != nil && (len(mu.Set) > 0 || len(mu.Del) > 0) { if allocIds, err = mutationHandler(ctx, mu); err != nil { x.TraceError(ctx, x.Wrapf(err, "Error while handling mutations")) return resp, err } } // If mutations are sent as part of the mutation object in the request we run // them here. if req.Mutation != nil && (len(req.Mutation.Set) > 0 || len(req.Mutation.Del) > 0) { if allocIds, err = runMutations(ctx, req.Mutation); err != nil { x.TraceError(ctx, x.Wrapf(err, "Error while handling mutations")) return resp, err } } resp.AssignedUids = allocIds if gq == nil || (gq.UID == 0 && len(gq.XID) == 0) { return resp, err } sg, err := query.ToSubGraph(ctx, gq) if err != nil { x.TraceError(ctx, x.Wrapf(err, "Error while conversion to internal format")) return resp, err } l.Parsing = time.Since(l.Start) x.Trace(ctx, "Query parsed") rch := make(chan error) go query.ProcessGraph(ctx, sg, nil, rch) err = <-rch if err != nil { x.TraceError(ctx, x.Wrapf(err, "Error while executing query")) return resp, err } l.Processing = time.Since(l.Start) - l.Parsing x.Trace(ctx, "Graph processed") node, err := sg.ToProtocolBuffer(&l) if err != nil { x.TraceError(ctx, x.Wrapf(err, "Error while converting to ProtocolBuffer")) return resp, err } resp.N = node gl := new(graph.Latency) gl.Parsing, gl.Processing, gl.Pb = l.Parsing.String(), l.Processing.String(), l.ProtocolBuffer.String() resp.L = gl return resp, err }
func queryHandler(w http.ResponseWriter, r *http.Request) { // Add a limit on how many pending queries can be run in the system. pendingQueries <- struct{}{} defer func() { <-pendingQueries }() addCorsHeaders(w) if r.Method == "OPTIONS" { return } if r.Method != "POST" { x.SetStatus(w, x.ErrorInvalidMethod, "Invalid method") return } ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() if rand.Float64() < *tracing { tr := trace.New("Dgraph", "Query") defer tr.Finish() ctx = trace.NewContext(ctx, tr) } var l query.Latency l.Start = time.Now() defer r.Body.Close() req, err := ioutil.ReadAll(r.Body) q := string(req) if err != nil || len(q) == 0 { x.TraceError(ctx, x.Wrapf(err, "Error while reading query")) x.SetStatus(w, x.ErrorInvalidRequest, "Invalid request encountered.") return } x.Trace(ctx, "Query received: %v", q) gq, mu, err := gql.Parse(q) if err != nil { x.TraceError(ctx, x.Wrapf(err, "Error while parsing query")) x.SetStatus(w, x.ErrorInvalidRequest, err.Error()) return } var allocIds map[string]uint64 var allocIdsStr map[string]string // If we have mutations, run them first. if mu != nil && (len(mu.Set) > 0 || len(mu.Del) > 0) { if allocIds, err = mutationHandler(ctx, mu); err != nil { x.TraceError(ctx, x.Wrapf(err, "Error while handling mutations")) x.SetStatus(w, x.Error, err.Error()) return } // convert the new UIDs to hex string. allocIdsStr = make(map[string]string) for k, v := range allocIds { allocIdsStr[k] = fmt.Sprintf("%#x", v) } } if gq == nil || (gq.UID == 0 && gq.Func == nil && len(gq.XID) == 0) { mp := map[string]interface{}{ "code": x.ErrorOk, "message": "Done", "uids": allocIdsStr, } if js, err := json.Marshal(mp); err == nil { w.Write(js) } else { x.SetStatus(w, "Error", "Unable to marshal map") } return } sg, err := query.ToSubGraph(ctx, gq) if err != nil { x.TraceError(ctx, x.Wrapf(err, "Error while conversion o internal format")) x.SetStatus(w, x.ErrorInvalidRequest, err.Error()) return } l.Parsing = time.Since(l.Start) x.Trace(ctx, "Query parsed") rch := make(chan error) go query.ProcessGraph(ctx, sg, nil, rch) err = <-rch if err != nil { x.TraceError(ctx, x.Wrapf(err, "Error while executing query")) x.SetStatus(w, x.Error, err.Error()) return } l.Processing = time.Since(l.Start) - l.Parsing x.Trace(ctx, "Graph processed") if len(*dumpSubgraph) > 0 { x.Checkf(os.MkdirAll(*dumpSubgraph, 0700), *dumpSubgraph) s := time.Now().Format("20060102.150405.000000.gob") filename := path.Join(*dumpSubgraph, s) f, err := os.Create(filename) x.Checkf(err, filename) enc := gob.NewEncoder(f) x.Check(enc.Encode(sg)) x.Checkf(f.Close(), filename) } js, err := sg.ToJSON(&l) if err != nil { x.TraceError(ctx, x.Wrapf(err, "Error while converting to Json")) x.SetStatus(w, x.Error, err.Error()) return } x.Trace(ctx, "Latencies: Total: %v Parsing: %v Process: %v Json: %v", time.Since(l.Start), l.Parsing, l.Processing, l.Json) w.Header().Set("Content-Type", "application/json") w.Write(js) }
func TestFieldAliasProto(t *testing.T) { dir, dir2, _ := populateGraph(t) defer os.RemoveAll(dir) defer os.RemoveAll(dir2) // Alright. Now we have everything set up. Let's create the query. query := ` { me(_uid_:0x01) { MyName:name gender alive Buddies:friend { BudName:name } } } ` gq, _, err := gql.Parse(query) require.NoError(t, err) ctx := context.Background() sg, err := ToSubGraph(ctx, gq) require.NoError(t, err) ch := make(chan error) go ProcessGraph(ctx, sg, nil, ch) err = <-ch require.NoError(t, err) var l Latency pb, err := sg.ToProtocolBuffer(&l) require.NoError(t, err) fmt.Println(proto.MarshalTextString(pb)) expectedPb := `attribute: "_root_" children: < attribute: "me" properties: < prop: "MyName" value: < str_val: "Michonne" > > properties: < prop: "gender" value: < bytes_val: "female" > > properties: < prop: "alive" value: < bool_val: true > > children: < attribute: "Buddies" properties: < prop: "BudName" value: < str_val: "Rick Grimes" > > > children: < attribute: "Buddies" properties: < prop: "BudName" value: < str_val: "Glenn Rhee" > > > children: < attribute: "Buddies" properties: < prop: "BudName" value: < str_val: "Daryl Dixon" > > > children: < attribute: "Buddies" properties: < prop: "BudName" value: < str_val: "Andrea" > > > > ` require.EqualValues(t, expectedPb, proto.MarshalTextString(pb)) }
func TestProcessGraph(t *testing.T) { dir, ps := populateGraph(t) defer os.RemoveAll(dir) // Alright. Now we have everything set up. Let's create the query. query := ` { me(_uid_: 0x01) { friend { name } name gender status } } ` gq, _, err := gql.Parse(query) if err != nil { t.Error(err) } sg, err := ToSubGraph(gq, ps) if err != nil { t.Error(err) } ch := make(chan error) go ProcessGraph(sg, ch, ps) err = <-ch if err != nil { t.Error(err) } if len(sg.Children) != 4 { t.Errorf("Expected len 4. Got: %v", len(sg.Children)) } child := sg.Children[0] if child.Attr != "friend" { t.Errorf("Expected attr friend. Got: %v", child.Attr) } if len(child.result) == 0 { t.Errorf("Expected some result.") return } uo := flatbuffers.GetUOffsetT(child.result) r := new(task.Result) r.Init(child.result, uo) if r.UidmatrixLength() != 1 { t.Errorf("Expected 1 matrix. Got: %v", r.UidmatrixLength()) } var ul task.UidList if ok := r.Uidmatrix(&ul, 0); !ok { t.Errorf("While parsing uidlist") } if ul.UidsLength() != 5 { t.Errorf("Expected 5 friends. Got: %v", ul.UidsLength()) } if ul.Uids(0) != 23 || ul.Uids(1) != 24 || ul.Uids(2) != 25 || ul.Uids(3) != 31 || ul.Uids(4) != 101 { t.Errorf("Friend ids don't match") } if len(child.Children) != 1 || child.Children[0].Attr != "name" { t.Errorf("Expected attr name") } child = child.Children[0] uo = flatbuffers.GetUOffsetT(child.result) r.Init(child.result, uo) if r.ValuesLength() != 5 { t.Errorf("Expected 5 names of 5 friends") } checkName(t, r, 0, "Rick Grimes") checkName(t, r, 1, "Glenn Rhee") checkName(t, r, 2, "Daryl Dixon") checkName(t, r, 3, "Andrea") { var tv task.Value if ok := r.Values(&tv, 4); !ok { t.Error("Unable to retrieve value") } if tv.ValLength() != 1 || tv.ValBytes()[0] != 0x00 { t.Error("Expected a null byte") } } checkSingleValue(t, sg.Children[1], "name", "Michonne") checkSingleValue(t, sg.Children[2], "gender", "female") checkSingleValue(t, sg.Children[3], "status", "alive") }
func TestSchema(t *testing.T) { dir, dir2, _ := populateGraph(t) defer os.RemoveAll(dir) defer os.RemoveAll(dir2) query := ` { debug(_uid_:0x1) { _xid_ name gender alive loc friend { name } friend { } } } ` gq, _, err := gql.Parse(query) require.NoError(t, err) ctx := context.Background() sg, err := ToSubGraph(ctx, gq) require.NoError(t, err) ch := make(chan error) go ProcessGraph(ctx, sg, nil, ch) err = <-ch require.NoError(t, err) var l Latency gr, err := sg.ToProtocolBuffer(&l) require.NoError(t, err) require.EqualValues(t, "debug", gr.Children[0].Attribute) require.EqualValues(t, 1, gr.Children[0].Uid) require.EqualValues(t, "mich", gr.Children[0].Xid) require.Len(t, gr.Children[0].Properties, 4) require.EqualValues(t, "Michonne", getProperty(gr.Children[0].Properties, "name").GetStrVal()) var g types.Geo x.Check(g.UnmarshalBinary(getProperty(gr.Children[0].Properties, "loc").GetGeoVal())) received, err := g.MarshalText() require.EqualValues(t, "{'type':'Point','coordinates':[1.1,2]}", string(received)) require.Len(t, gr.Children[0].Children, 5) child := gr.Children[0].Children[0] require.EqualValues(t, 23, child.Uid) require.EqualValues(t, "friend", child.Attribute) require.Len(t, child.Properties, 1) require.EqualValues(t, "Rick Grimes", getProperty(child.Properties, "name").GetStrVal()) require.Empty(t, child.Children) child = gr.Children[0].Children[1] require.EqualValues(t, 24, child.Uid) require.EqualValues(t, "friend", child.Attribute) require.Len(t, child.Properties, 1) require.EqualValues(t, "Glenn Rhee", getProperty(child.Properties, "name").GetStrVal()) require.Empty(t, child.Children) child = gr.Children[0].Children[4] require.EqualValues(t, 101, child.Uid) require.EqualValues(t, "friend", child.Attribute) require.Len(t, child.Properties, 0) }
func TestToProto(t *testing.T) { dir, dir2, _ := populateGraph(t) defer os.RemoveAll(dir) defer os.RemoveAll(dir2) query := ` { debug(_uid_:0x1) { _xid_ name gender alive friend { name } friend { } } } ` gq, _, err := gql.Parse(query) require.NoError(t, err) ctx := context.Background() sg, err := ToSubGraph(ctx, gq) require.NoError(t, err) ch := make(chan error) go ProcessGraph(ctx, sg, nil, ch) err = <-ch require.NoError(t, err) var l Latency gr, err := sg.ToProtocolBuffer(&l) require.NoError(t, err) require.EqualValues(t, `attribute: "_root_" children: < uid: 1 xid: "mich" attribute: "debug" properties: < prop: "name" value: < str_val: "Michonne" > > properties: < prop: "gender" value: < bytes_val: "female" > > properties: < prop: "alive" value: < bool_val: true > > children: < uid: 23 attribute: "friend" properties: < prop: "name" value: < str_val: "Rick Grimes" > > > children: < uid: 24 attribute: "friend" properties: < prop: "name" value: < str_val: "Glenn Rhee" > > > children: < uid: 25 attribute: "friend" properties: < prop: "name" value: < str_val: "Daryl Dixon" > > > children: < uid: 31 attribute: "friend" properties: < prop: "name" value: < str_val: "Andrea" > > > children: < uid: 101 attribute: "friend" > > `, proto.MarshalTextString(gr)) }
func TestQuery(t *testing.T) { dir1, dir2, ps, clog, err := prepare() if err != nil { t.Error(err) return } defer closeAll(dir1, dir2, clog) // Parse GQL into internal query representation. gq, _, err := gql.Parse(q0) if err != nil { t.Error(err) return } g, err := query.ToSubGraph(gq, ps) if err != nil { t.Error(err) return } // Test internal query representation. if len(g.Children) != 3 { t.Errorf("Expected 3 children. Got: %v", len(g.Children)) } { child := g.Children[0] if child.Attr != "follows" { t.Errorf("Expected follows. Got: %q", child.Attr) } if len(child.Children) != 2 { t.Errorf("Expected 2 child. Got: %v", len(child.Children)) } gc := child.Children[0] if gc.Attr != "_xid_" { t.Errorf("Expected _xid_. Got: %q", gc.Attr) } gc = child.Children[1] if gc.Attr != "status" { t.Errorf("Expected status. Got: %q", gc.Attr) } } { child := g.Children[1] if child.Attr != "_xid_" { t.Errorf("Expected _xid_. Got: %q", child.Attr) } } { child := g.Children[2] if child.Attr != "status" { t.Errorf("Expected status. Got: %q", child.Attr) } } ch := make(chan error) go query.ProcessGraph(g, ch, ps) if err := <-ch; err != nil { t.Error(err) return } var l query.Latency js, err := g.ToJson(&l) if err != nil { t.Error(err) return } fmt.Println(string(js)) }
// Test sorting / ordering by dob. func TestToProtoOrderOffsetCount(t *testing.T) { dir, dir2, _ := populateGraph(t) defer os.RemoveAll(dir) defer os.RemoveAll(dir2) query := ` { me(_uid_:0x01) { name gender friend(order: dob, first: 2, offset: 1) { name } } } ` gq, _, err := gql.Parse(query) require.NoError(t, err) ctx := context.Background() sg, err := ToSubGraph(ctx, gq) require.NoError(t, err) ch := make(chan error) go ProcessGraph(ctx, sg, nil, ch) err = <-ch require.NoError(t, err) var l Latency pb, err := sg.ToProtocolBuffer(&l) require.NoError(t, err) expectedPb := `attribute: "_root_" children: < attribute: "me" properties: < prop: "name" value: < str_val: "Michonne" > > properties: < prop: "gender" value: < bytes_val: "female" > > children: < attribute: "friend" properties: < prop: "name" value: < str_val: "Daryl Dixon" > > > children: < attribute: "friend" properties: < prop: "name" value: < str_val: "Glenn Rhee" > > > > ` require.EqualValues(t, expectedPb, proto.MarshalTextString(pb)) }