github.com/cornelk/go-cloud@v0.17.1/internal/testing/octest/diff.go (about) 1 // Copyright 2019 The Go Cloud Development Kit Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package octest 16 17 import ( 18 "fmt" 19 "strings" 20 21 "github.com/cornelk/go-cloud/gcerrors" 22 "go.opencensus.io/stats/view" 23 "go.opencensus.io/trace" 24 ) 25 26 // Call holds the expected contents of a measured call. 27 // It is used for both metric and trace comparison. 28 type Call struct { 29 Method string 30 Code gcerrors.ErrorCode 31 } 32 33 func formatSpanData(s *trace.SpanData) string { 34 if s == nil { 35 return "missing" 36 } 37 return fmt.Sprintf("<Name: %q, Code: %d>", s.Name, s.Code) 38 } 39 40 func formatCall(c *Call) string { 41 if c == nil { 42 return "nothing" 43 } 44 return fmt.Sprintf("<Name: %q, Code: %d>", c.Method, c.Code) 45 } 46 47 // Diff compares the list of spans and metric counts obtained from OpenCensus 48 // instrumentation (using the TestExporter in this package, or similar) with an 49 // expected list of calls. Only the name and code are compared. Order matters for 50 // traces (though not for metrics). 51 func Diff(gotSpans []*trace.SpanData, gotRows []*view.Row, namePrefix, provider string, want []Call) string { 52 ds := diffSpans(gotSpans, namePrefix, want) 53 dc := diffCounts(gotRows, namePrefix, provider, want) 54 if len(ds) > 0 { 55 ds = "trace: " + ds + "\n" 56 } 57 if len(dc) > 0 { 58 dc = "metrics: " + dc 59 } 60 return ds + dc 61 } 62 63 func diffSpans(got []*trace.SpanData, prefix string, want []Call) string { 64 var diffs []string 65 add := func(i int, g *trace.SpanData, w *Call) { 66 diffs = append(diffs, fmt.Sprintf("#%d: got %s, want %s", i, formatSpanData(g), formatCall(w))) 67 } 68 69 for i := 0; i < len(got) || i < len(want); i++ { 70 switch { 71 case i >= len(got): 72 add(i, nil, &want[i]) 73 case i >= len(want): 74 add(i, got[i], nil) 75 case got[i].Name != prefix+"."+want[i].Method || got[i].Code != int32(want[i].Code): 76 w := want[i] 77 w.Method = prefix + "." + w.Method 78 add(i, got[i], &w) 79 } 80 } 81 return strings.Join(diffs, "\n") 82 } 83 84 func diffCounts(got []*view.Row, prefix, provider string, wantCalls []Call) string { 85 // Because OpenCensus keeps global state, running tests with -count N can result 86 // in aggregate counts greater than 1. Also, other tests can contribute measurements. 87 // So all we can do is make sure that each call appears with count at least 1. 88 var diffs []string 89 gotTags := map[string]bool{} // map of canonicalized row tags 90 for _, row := range got { 91 if _, ok := row.Data.(*view.CountData); !ok { 92 diffs = append(diffs, fmt.Sprintf("row.Data is %T, want CountData", row.Data)) 93 continue 94 } 95 var tags []string 96 for _, t := range row.Tags { 97 tags = append(tags, t.Key.Name()+":"+t.Value) 98 } 99 gotTags[strings.Join(tags, ",")] = true 100 } 101 for _, wc := range wantCalls { 102 mapKey := fmt.Sprintf("gocdk_method:%s.%s,gocdk_provider:%s,gocdk_status:%s", 103 prefix, wc.Method, provider, fmt.Sprint(wc.Code)) 104 if !gotTags[mapKey] { 105 diffs = append(diffs, fmt.Sprintf("missing %q", mapKey)) 106 } 107 } 108 return strings.Join(diffs, "\n") 109 }