blob: 61f46c6e6ea5afac49ff4b7484793101d675febf [file] [log] [blame]
package vtrace_test
import (
"bytes"
"strings"
"testing"
"v.io/core/veyron2"
"v.io/core/veyron2/context"
"v.io/core/veyron2/ipc"
"v.io/core/veyron2/ipc/stream"
"v.io/core/veyron2/naming"
"v.io/core/veyron2/security"
"v.io/core/veyron2/vlog"
"v.io/core/veyron2/vtrace"
_ "v.io/core/veyron/profiles"
iipc "v.io/core/veyron/runtimes/google/ipc"
"v.io/core/veyron/runtimes/google/ipc/stream/manager"
tnaming "v.io/core/veyron/runtimes/google/testing/mocks/naming"
)
func TestNewFromContext(t *testing.T) {
c0, shutdown := veyron2.Init()
defer shutdown()
c1, s1 := vtrace.SetNewSpan(c0, "s1")
c2, s2 := vtrace.SetNewSpan(c1, "s2")
c3, s3 := vtrace.SetNewSpan(c2, "s3")
expected := map[*context.T]vtrace.Span{
c1: s1,
c2: s2,
c3: s3,
}
for ctx, expectedSpan := range expected {
if s := vtrace.GetSpan(ctx); s != expectedSpan {
t.Errorf("Wrong span for ctx %v. Got %v, want %v", c0, s, expectedSpan)
}
}
}
type fakeAuthorizer int
func (fakeAuthorizer) Authorize(security.Context) error {
return nil
}
type testServer struct {
sm stream.Manager
ns naming.Namespace
name string
child string
stop func() error
forceCollect bool
}
func (c *testServer) Run(ctx ipc.ServerContext) error {
if c.forceCollect {
vtrace.ForceCollect(ctx.Context())
}
client, err := iipc.InternalNewClient(c.sm, c.ns)
if err != nil {
vlog.Error(err)
return err
}
vtrace.GetSpan(ctx.Context()).Annotate(c.name + "-begin")
if c.child != "" {
var call ipc.Call
if call, err = client.StartCall(ctx.Context(), c.child, "Run", []interface{}{}); err != nil {
vlog.Error(err)
return err
}
var outerr error
if err = call.Finish(&outerr); err != nil {
vlog.Error(err)
return err
}
if outerr != nil {
vlog.Error(outerr)
return outerr
}
}
vtrace.GetSpan(ctx.Context()).Annotate(c.name + "-end")
return nil
}
func makeTestServer(ctx *context.T, ns naming.Namespace, name, child string, forceCollect bool) (*testServer, error) {
sm := manager.InternalNew(naming.FixedRoutingID(0x111111111))
s, err := iipc.InternalNewServer(ctx, sm, ns, nil)
if err != nil {
return nil, err
}
if _, err := s.Listen(veyron2.GetListenSpec(ctx)); err != nil {
return nil, err
}
c := &testServer{
sm: sm,
ns: ns,
name: name,
child: child,
stop: s.Stop,
forceCollect: forceCollect,
}
if err := s.Serve(name, c, fakeAuthorizer(0)); err != nil {
return nil, err
}
return c, nil
}
func summary(span *vtrace.SpanRecord) string {
summary := span.Name
if len(span.Annotations) > 0 {
msgs := []string{}
for _, annotation := range span.Annotations {
msgs = append(msgs, annotation.Message)
}
summary += ": " + strings.Join(msgs, ", ")
}
return summary
}
func traceString(trace *vtrace.TraceRecord) string {
var b bytes.Buffer
vtrace.FormatTrace(&b, trace, nil)
return b.String()
}
func expectSequence(t *testing.T, trace vtrace.TraceRecord, expectedSpans []string) {
// It's okay to have additional spans - someone may have inserted
// additional spans for more debugging.
if got, want := len(trace.Spans), len(expectedSpans); got < want {
t.Errorf("Found %d spans, want %d", got, want)
}
spans := map[string]*vtrace.SpanRecord{}
summaries := []string{}
for i := range trace.Spans {
span := &trace.Spans[i]
// All spans should have a start.
if span.Start == 0 {
t.Errorf("span missing start: %x, %s", span.ID[12:], traceString(&trace))
}
// All spans except the root should have a valid end.
// TODO(mattr): For now I'm also skipping connectFlow and
// vc.HandshakeDialedVC spans because the ws endpoints are
// currently non-deterministic in terms of whether they fail
// before the test ends or not. In the future it will be
// configurable whether we listen on ws or not and then we should
// adjust the test to not listen and remove this check.
if span.Name != "" &&
span.Name != "<client>connectFlow" &&
span.Name != "vc.HandshakeDialedVC" {
if span.End == 0 {
t.Errorf("span missing end: %x, %s", span.ID[12:], traceString(&trace))
} else if span.Start >= span.End {
t.Errorf("span end should be after start: %x, %s", span.ID[12:], traceString(&trace))
}
}
summary := summary(span)
summaries = append(summaries, summary)
spans[summary] = span
}
for i := range expectedSpans {
child, ok := spans[expectedSpans[i]]
if !ok {
t.Errorf("expected span %s not found in %#v", expectedSpans[i], summaries)
continue
}
if i == 0 {
continue
}
parent, ok := spans[expectedSpans[i-1]]
if !ok {
t.Errorf("expected span %s not found in %#v", expectedSpans[i-1], summaries)
continue
}
if child.Parent != parent.ID {
t.Errorf("%v should be a child of %v, but it's not.", child, parent)
}
}
}
func runCallChain(t *testing.T, ctx *context.T, force1, force2 bool) {
sm := manager.InternalNew(naming.FixedRoutingID(0x555555555))
ns := tnaming.NewSimpleNamespace()
client, err := iipc.InternalNewClient(sm, ns)
if err != nil {
t.Error(err)
}
ctx1, _ := vtrace.SetNewTrace(ctx)
c1, err := makeTestServer(ctx1, ns, "c1", "c2", force1)
if err != nil {
t.Fatal("Can't start server:", err)
}
defer c1.stop()
ctx2, _ := vtrace.SetNewTrace(ctx)
c2, err := makeTestServer(ctx2, ns, "c2", "", force2)
if err != nil {
t.Fatal("Can't start server:", err)
}
defer c2.stop()
call, err := client.StartCall(ctx, "c1", "Run", []interface{}{})
if err != nil {
t.Fatal("can't call: ", err)
}
var outerr error
if err = call.Finish(&outerr); err != nil {
t.Error(err)
}
if outerr != nil {
t.Error(outerr)
}
}
// TestCancellationPropagation tests that cancellation propogates along an
// RPC call chain without user intervention.
func TestTraceAcrossRPCs(t *testing.T) {
ctx, shutdown := veyron2.Init()
defer shutdown()
ctx, span := vtrace.SetNewSpan(ctx, "")
vtrace.ForceCollect(ctx)
span.Annotate("c0-begin")
runCallChain(t, ctx, false, false)
span.Annotate("c0-end")
expectedSpans := []string{
": c0-begin, c0-end",
"<client>\"c1\".Run",
"\"\".Run: c1-begin, c1-end",
"<client>\"c2\".Run",
"\"\".Run: c2-begin, c2-end",
}
record := vtrace.GetStore(ctx).TraceRecord(span.Trace())
expectSequence(t, *record, expectedSpans)
}
// TestCancellationPropagationLateForce tests that cancellation propogates along an
// RPC call chain when tracing is initiated by someone deep in the call chain.
func TestTraceAcrossRPCsLateForce(t *testing.T) {
ctx, shutdown := veyron2.Init()
defer shutdown()
ctx, span := vtrace.SetNewSpan(ctx, "")
span.Annotate("c0-begin")
runCallChain(t, ctx, false, true)
span.Annotate("c0-end")
expectedSpans := []string{
": c0-end",
"<client>\"c1\".Run",
"\"\".Run: c1-end",
"<client>\"c2\".Run",
"\"\".Run: c2-begin, c2-end",
}
record := vtrace.GetStore(ctx).TraceRecord(span.Trace())
expectSequence(t, *record, expectedSpans)
}