Skip to content
This repository was archived by the owner on Aug 17, 2020. It is now read-only.

Commit 4648a27

Browse files
authored
Support for projects without go.mod (#153)
* better support for project without go.mod * fix * benchmark fix * Refactor * Refactor * comments * comments * remove source root from init.go * Tests for util file * go fmt * avoid empty test.code in benchmark tests
1 parent 647a637 commit 4648a27

File tree

5 files changed

+205
-72
lines changed

5 files changed

+205
-72
lines changed

instrumentation/testing/benchmark.go

Lines changed: 16 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package testing
22

33
import (
44
"context"
5-
"fmt"
65
"math"
76
"regexp"
87
"runtime"
@@ -13,7 +12,6 @@ import (
1312

1413
"github.com/opentracing/opentracing-go"
1514

16-
"go.undefinedlabs.com/scopeagent/ast"
1715
"go.undefinedlabs.com/scopeagent/instrumentation"
1816
"go.undefinedlabs.com/scopeagent/reflection"
1917
"go.undefinedlabs.com/scopeagent/runner"
@@ -88,10 +86,10 @@ func startBenchmark(b *testing.B, pc uintptr, benchFunc func(b *testing.B)) {
8886
if bChild == nil {
8987
return
9088
}
91-
if getBenchmarkHasSub(bChild) > 0 {
89+
if reflection.GetBenchmarkHasSub(bChild) > 0 {
9290
return
9391
}
94-
results, err := extractBenchmarkResult(bChild)
92+
results, err := reflection.GetBenchmarkResult(bChild)
9593
if err != nil {
9694
instrumentation.Logger().Printf("Error while extracting the benchmark result object: %v\n", err)
9795
return
@@ -102,7 +100,7 @@ func startBenchmark(b *testing.B, pc uintptr, benchFunc func(b *testing.B)) {
102100
fullTestName := runner.GetOriginalTestName(b.Name())
103101

104102
// We detect if the parent benchmark is instrumented, and if so we remove the "*" SubBenchmark from the previous instrumentation
105-
parentBenchmark := getParentBenchmark(b)
103+
parentBenchmark := reflection.GetParentBenchmark(b)
106104
if parentBenchmark != nil && hasBenchmark(parentBenchmark) {
107105
var nameSegments []string
108106
for _, match := range benchmarkNameRegex.FindAllStringSubmatch(fullTestName, -1) {
@@ -112,30 +110,27 @@ func startBenchmark(b *testing.B, pc uintptr, benchFunc func(b *testing.B)) {
112110
}
113111
fullTestName = strings.Join(nameSegments, "/")
114112
}
115-
116-
testNameSlash := strings.IndexByte(fullTestName, '/')
117-
funcName := fullTestName
118-
if testNameSlash >= 0 {
119-
funcName = fullTestName[:testNameSlash]
120-
}
121-
packageName := getBenchmarkSuiteName(b)
122-
123-
sourceBounds, _ := ast.GetFuncSourceForName(pc, funcName)
124-
var testCode string
125-
if sourceBounds != nil {
126-
testCode = fmt.Sprintf("%s:%d:%d", sourceBounds.File, sourceBounds.Start.Line, sourceBounds.End.Line)
113+
packageName := reflection.GetBenchmarkSuiteName(b)
114+
if packageName == "" {
115+
packageName = getPackageName(pc, fullTestName)
127116
}
128117

129-
var startOptions []opentracing.StartSpanOption
130-
startOptions = append(startOptions, opentracing.Tags{
118+
oTags := opentracing.Tags{
131119
"span.kind": "test",
132120
"test.name": fullTestName,
133121
"test.suite": packageName,
134-
"test.code": testCode,
135122
"test.framework": "testing",
136123
"test.language": "go",
137124
"test.type": "benchmark",
138-
}, opentracing.StartTime(startTime))
125+
}
126+
127+
testCode := getTestCodeBoundaries(pc, fullTestName)
128+
if testCode != "" {
129+
oTags["test.code"] = testCode
130+
}
131+
132+
var startOptions []opentracing.StartSpanOption
133+
startOptions = append(startOptions, oTags, opentracing.StartTime(startTime))
139134

140135
span, _ := opentracing.StartSpanFromContextWithTracer(context.Background(), instrumentation.Tracer(), fullTestName, startOptions...)
141136
span.SetBaggageItem("trace.kind", "test")
@@ -153,33 +148,3 @@ func startBenchmark(b *testing.B, pc uintptr, benchFunc func(b *testing.B)) {
153148
FinishTime: startTime.Add(results.T),
154149
})
155150
}
156-
157-
func getParentBenchmark(b *testing.B) *testing.B {
158-
if ptr, err := reflection.GetFieldPointerOf(b, "parent"); err == nil {
159-
return *(**testing.B)(ptr)
160-
}
161-
return nil
162-
}
163-
164-
func getBenchmarkSuiteName(b *testing.B) string {
165-
if ptr, err := reflection.GetFieldPointerOf(b, "importPath"); err == nil {
166-
return *(*string)(ptr)
167-
}
168-
return ""
169-
}
170-
171-
func getBenchmarkHasSub(b *testing.B) int32 {
172-
if ptr, err := reflection.GetFieldPointerOf(b, "hasSub"); err == nil {
173-
return *(*int32)(ptr)
174-
}
175-
return 0
176-
}
177-
178-
//Extract benchmark result from the private result field in testing.B
179-
func extractBenchmarkResult(b *testing.B) (*testing.BenchmarkResult, error) {
180-
if ptr, err := reflection.GetFieldPointerOf(b, "result"); err == nil {
181-
return (*testing.BenchmarkResult)(ptr), nil
182-
} else {
183-
return nil, err
184-
}
185-
}

instrumentation/testing/testing.go

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package testing
22

33
import (
44
"context"
5-
"fmt"
65
"reflect"
76
"regexp"
87
"runtime"
@@ -14,7 +13,6 @@ import (
1413

1514
"github.com/opentracing/opentracing-go"
1615

17-
"go.undefinedlabs.com/scopeagent/ast"
1816
"go.undefinedlabs.com/scopeagent/errors"
1917
"go.undefinedlabs.com/scopeagent/instrumentation"
2018
"go.undefinedlabs.com/scopeagent/instrumentation/logging"
@@ -71,21 +69,10 @@ func StartTestFromCaller(t *testing.T, pc uintptr, opts ...Option) *Test {
7169
opt(test)
7270
}
7371

74-
// Extracting the benchmark func name (by removing any possible sub-benchmark suffix `{bench_func}/{sub_benchmark}`)
72+
// Extracting the testing func name (by removing any possible sub-test suffix `{test_func}/{sub_test}`)
7573
// to search the func source code bounds and to calculate the package name.
7674
fullTestName := runner.GetOriginalTestName(t.Name())
77-
testNameSlash := strings.IndexByte(fullTestName, '/')
78-
funcName := fullTestName
79-
if testNameSlash >= 0 {
80-
funcName = fullTestName[:testNameSlash]
81-
}
82-
83-
funcFullName := runtime.FuncForPC(pc).Name()
84-
funcNameIndex := strings.LastIndex(funcFullName, funcName)
85-
if funcNameIndex < 1 {
86-
funcNameIndex = len(funcFullName)
87-
}
88-
packageName := funcFullName[:funcNameIndex-1]
75+
packageName := getPackageName(pc, fullTestName)
8976

9077
testTags := opentracing.Tags{
9178
"span.kind": "test",
@@ -94,12 +81,10 @@ func StartTestFromCaller(t *testing.T, pc uintptr, opts ...Option) *Test {
9481
"test.framework": "testing",
9582
"test.language": "go",
9683
}
97-
sourceBounds, sErr := ast.GetFuncSourceForName(pc, funcName)
98-
if sErr != nil {
99-
instrumentation.Logger().Printf("error calculating the source boundaries for '%s [%s]': %v", funcName, funcFullName, sErr)
100-
}
101-
if sourceBounds != nil {
102-
testTags["test.code"] = fmt.Sprintf("%s:%d:%d", sourceBounds.File, sourceBounds.Start.Line, sourceBounds.End.Line)
84+
85+
testCode := getTestCodeBoundaries(pc, fullTestName)
86+
if testCode != "" {
87+
testTags["test.code"] = testCode
10388
}
10489

10590
if test.ctx == nil {

instrumentation/testing/util.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package testing
2+
3+
import (
4+
"fmt"
5+
"path"
6+
"runtime"
7+
"strings"
8+
9+
"go.undefinedlabs.com/scopeagent/ast"
10+
"go.undefinedlabs.com/scopeagent/instrumentation"
11+
)
12+
13+
// Gets the Test/Benchmark parent func name without sub-benchmark and sub-test segments
14+
func getFuncName(fullName string) string {
15+
testNameSlash := strings.IndexByte(fullName, '/')
16+
funcName := fullName
17+
if testNameSlash >= 0 {
18+
funcName = fullName[:testNameSlash]
19+
}
20+
return funcName
21+
}
22+
23+
// Gets the Package name
24+
func getPackageName(pc uintptr, fullName string) string {
25+
// Parent test/benchmark name
26+
funcName := getFuncName(fullName)
27+
// Full func name (format ex: {packageName}.{test/benchmark name}.{inner function of sub benchmark/test}
28+
funcFullName := runtime.FuncForPC(pc).Name()
29+
30+
// We select the packageName as substring from start to the index of the test/benchmark name minus 1
31+
funcNameIndex := strings.LastIndex(funcFullName, funcName)
32+
if funcNameIndex < 1 {
33+
funcNameIndex = len(funcFullName)
34+
}
35+
packageName := funcFullName[:funcNameIndex-1]
36+
37+
// If the package has the format: _/{path...}
38+
// We convert the path from absolute to relative to the source root
39+
sourceRoot := instrumentation.GetSourceRoot()
40+
if len(packageName) > 0 && packageName[0] == '_' && strings.Index(packageName, sourceRoot) != -1 {
41+
packageName = strings.Replace(packageName, path.Dir(sourceRoot)+"/", "", -1)[1:]
42+
}
43+
44+
return packageName
45+
}
46+
47+
// Gets the source code boundaries of a test or benchmark in the format: {file}:{startLine}:{endLine}
48+
func getTestCodeBoundaries(pc uintptr, fullName string) string {
49+
funcName := getFuncName(fullName)
50+
sourceBounds, err := ast.GetFuncSourceForName(pc, funcName)
51+
if err != nil {
52+
instrumentation.Logger().Printf("error calculating the source boundaries for '%s [%s]': %v", funcName, fullName, err)
53+
}
54+
if sourceBounds != nil {
55+
return fmt.Sprintf("%s:%d:%d", sourceBounds.File, sourceBounds.Start.Line, sourceBounds.End.Line)
56+
}
57+
return ""
58+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package testing
2+
3+
import (
4+
"fmt"
5+
"runtime"
6+
"testing"
7+
8+
"go.undefinedlabs.com/scopeagent/ast"
9+
)
10+
11+
func TestGetFuncName(t *testing.T) {
12+
cases := map[string]string{
13+
"TestBase": "TestBase",
14+
"TestBase/Sub01": "TestBase",
15+
"TestBase/Sub 02": "TestBase",
16+
"TestBase/Sub/Sub02": "TestBase",
17+
"TestBase/Sub/Sub02/Sub03": "TestBase",
18+
"TestBase/Sub/Sub02/Sub03/S u b 0 4": "TestBase",
19+
}
20+
21+
for key, expected := range cases {
22+
t.Run(key, func(t *testing.T) {
23+
value := getFuncName(key)
24+
if value != expected {
25+
t.Fatalf("value '%s', expected: '%s'", value, expected)
26+
}
27+
})
28+
}
29+
}
30+
31+
func TestGetPackageName(t *testing.T) {
32+
var pc uintptr
33+
func() { pc, _, _, _ = runtime.Caller(1) }()
34+
testName := t.Name()
35+
packName := getPackageName(pc, testName)
36+
37+
subTestName := ""
38+
t.Run("sub-test", func(t *testing.T) {
39+
subTestName = t.Name()
40+
})
41+
packName02 := getPackageName(pc, subTestName)
42+
43+
if testName != "TestGetPackageName" {
44+
t.Fatalf("value '%s' not expected", testName)
45+
}
46+
if subTestName != "TestGetPackageName/sub-test" {
47+
t.Fatalf("value '%s' not expected", testName)
48+
}
49+
if packName != "go.undefinedlabs.com/scopeagent/instrumentation/testing" {
50+
t.Fatalf("value '%s' not expected", packName)
51+
}
52+
if packName != packName02 {
53+
t.Fatalf("value '%s' not expected", packName02)
54+
}
55+
}
56+
57+
func TestGetTestCodeBoundaries(t *testing.T) {
58+
var pc uintptr
59+
func() { pc, _, _, _ = runtime.Caller(1) }()
60+
testName := t.Name()
61+
62+
actualBoundary := getTestCodeBoundaries(pc, testName)
63+
boundaryExpected, _ := ast.GetFuncSourceForName(pc, testName)
64+
calcExpected := fmt.Sprintf("%s:%d:%d", boundaryExpected.File, boundaryExpected.Start.Line, boundaryExpected.End.Line)
65+
if actualBoundary != calcExpected {
66+
t.Fatalf("value '%s' not expected", actualBoundary)
67+
}
68+
}

reflection/reflect.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,60 @@ func GetIsParallel(t *testing.T) bool {
4646
}
4747
return false
4848
}
49+
50+
func GetBenchmarkMutex(b *testing.B) *sync.RWMutex {
51+
if ptr, err := GetFieldPointerOf(b, "mu"); err == nil {
52+
return (*sync.RWMutex)(ptr)
53+
}
54+
return nil
55+
}
56+
57+
func GetParentBenchmark(b *testing.B) *testing.B {
58+
mu := GetBenchmarkMutex(b)
59+
if mu != nil {
60+
mu.Lock()
61+
defer mu.Unlock()
62+
}
63+
if ptr, err := GetFieldPointerOf(b, "parent"); err == nil {
64+
return *(**testing.B)(ptr)
65+
}
66+
return nil
67+
}
68+
69+
func GetBenchmarkSuiteName(b *testing.B) string {
70+
mu := GetBenchmarkMutex(b)
71+
if mu != nil {
72+
mu.Lock()
73+
defer mu.Unlock()
74+
}
75+
if ptr, err := GetFieldPointerOf(b, "importPath"); err == nil {
76+
return *(*string)(ptr)
77+
}
78+
return ""
79+
}
80+
81+
func GetBenchmarkHasSub(b *testing.B) int32 {
82+
mu := GetBenchmarkMutex(b)
83+
if mu != nil {
84+
mu.Lock()
85+
defer mu.Unlock()
86+
}
87+
if ptr, err := GetFieldPointerOf(b, "hasSub"); err == nil {
88+
return *(*int32)(ptr)
89+
}
90+
return 0
91+
}
92+
93+
//Get benchmark result from the private result field in testing.B
94+
func GetBenchmarkResult(b *testing.B) (*testing.BenchmarkResult, error) {
95+
mu := GetBenchmarkMutex(b)
96+
if mu != nil {
97+
mu.Lock()
98+
defer mu.Unlock()
99+
}
100+
if ptr, err := GetFieldPointerOf(b, "result"); err == nil {
101+
return (*testing.BenchmarkResult)(ptr), nil
102+
} else {
103+
return nil, err
104+
}
105+
}

0 commit comments

Comments
 (0)