Skip to content

Commit

Permalink
internal/civisibility: add support for the test.source.end tag (#2911)
Browse files Browse the repository at this point in the history
  • Loading branch information
tonyredondo authored Oct 4, 2024
1 parent d50070a commit 966abf2
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 3 deletions.
4 changes: 4 additions & 0 deletions internal/civisibility/constants/test_tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ const (
// This constant is used to tag traces with the line number in the source file where the test starts.
TestSourceStartLine = "test.source.start"

// TestSourceEndLine indicates the line of the source file where the test ends.
// This constant is used to tag traces with the line number in the source file where the test ends.
TestSourceEndLine = "test.source.end"

// TestCodeOwners indicates the test code owners.
// This constant is used to tag traces with the code owners responsible for the test.
TestCodeOwners = "test.codeowners"
Expand Down
56 changes: 53 additions & 3 deletions internal/civisibility/integrations/manual_api_ddtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ package integrations
import (
"context"
"fmt"
"go/ast"
"go/parser"
"go/token"
"runtime"
"strings"
"time"
Expand Down Expand Up @@ -134,11 +137,58 @@ func (t *tslvTest) SetTestFunc(fn *runtime.Func) {
return
}

file, line := fn.FileLine(fn.Entry())
file = utils.GetRelativePathFromCITagsSourceRoot(file)
// let's get the file path and the start line of the function
absolutePath, startLine := fn.FileLine(fn.Entry())
file := utils.GetRelativePathFromCITagsSourceRoot(absolutePath)
t.SetTag(constants.TestSourceFile, file)
t.SetTag(constants.TestSourceStartLine, line)
t.SetTag(constants.TestSourceStartLine, startLine)

// now, let's try to get the end line of the function using ast
// parse the entire file where the function is defined to create an abstract syntax tree (AST)
// if we can't parse the file (source code is not available) we silently bail out
fset := token.NewFileSet()
fileNode, err := parser.ParseFile(fset, absolutePath, nil, parser.AllErrors)
if err == nil {
// get the function name without the package name
fullName := fn.Name()
firstDot := strings.LastIndex(fullName, ".") + 1
name := fullName[firstDot:]

// variable to store the ending line of the function
var endLine int
// traverse the AST to find the function declaration for the target function
ast.Inspect(fileNode, func(n ast.Node) bool {
// check if the current node is a function declaration
if funcDecl, ok := n.(*ast.FuncDecl); ok {
// if the function name matches the target function name
if funcDecl.Name.Name == name {
// get the line number of the end of the function body
endLine = fset.Position(funcDecl.Body.End()).Line
// stop further inspection since we have found the target function
return false
}
}
// check if the current node is a function literal (FuncLit)
if funcLit, ok := n.(*ast.FuncLit); ok {
// get the line number of the start of the function literal
funcStartLine := fset.Position(funcLit.Body.Pos()).Line
// if the start line matches the known start line, record the end line
if funcStartLine == startLine {
endLine = fset.Position(funcLit.Body.End()).Line
return false // stop further inspection since we have found the function
}
}
// continue inspecting other nodes
return true
})

// if we found an endLine we check is greater than the calculated startLine
if endLine > startLine {
t.SetTag(constants.TestSourceEndLine, endLine)
}
}

// get the codeowner of the function
codeOwners := utils.GetCodeOwners()
if codeOwners != nil {
match, found := codeOwners.Match("/" + file)
Expand Down
39 changes: 39 additions & 0 deletions internal/civisibility/integrations/manual_api_mocktracer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,39 @@ func Test(t *testing.T) {
test.Close(ResultStatusSkip)
}

func TestWithInnerFunc(t *testing.T) {
mockTracer.Reset()
assert := assert.New(t)

now := time.Now()
session, module, suite, test := createDDTest(now)
defer func() {
session.Close(0)
module.Close()
suite.Close()
}()
test.SetError(errors.New("we keep the last error"))
test.SetErrorInfo("my-type", "my-message", "my-stack")
func() {
pc, _, _, _ := runtime.Caller(0)
test.SetTestFunc(runtime.FuncForPC(pc))
}()

assert.NotNil(test.Context())
assert.Equal("my-test", test.Name())
assert.Equal(now, test.StartTime())
assert.Equal(suite, test.Suite())

test.Close(ResultStatusPass)

finishedSpans := mockTracer.FinishedSpans()
assert.Equal(1, len(finishedSpans))
testAssertions(assert, now, finishedSpans[0])

//no-op call
test.Close(ResultStatusSkip)
}

func testAssertions(assert *assert.Assertions, now time.Time, testSpan mocktracer.Span) {
assert.Equal(now, testSpan.StartTime())
assert.Equal("my-module-framework.test", testSpan.OperationName())
Expand All @@ -272,6 +305,12 @@ func testAssertions(assert *assert.Assertions, now time.Time, testSpan mocktrace
assert.Contains(spanTags, constants.TestModuleIDTag)
assert.Contains(spanTags, constants.TestSuiteIDTag)
assert.Contains(spanTags, constants.TestSourceFile)

// make sure we have both start and end line
assert.Contains(spanTags, constants.TestSourceStartLine)
assert.Contains(spanTags, constants.TestSourceEndLine)
// make sure the startLine < endLine
assert.Less(spanTags[constants.TestSourceStartLine].(int), spanTags[constants.TestSourceEndLine].(int))

commonAssertions(assert, testSpan)
}

0 comments on commit 966abf2

Please sign in to comment.