Skip to content

Commit

Permalink
New build configuration option for coverage mode
Browse files Browse the repository at this point in the history
Small fixes on "func" mode instrumentation api
Formatting of the Go code
  • Loading branch information
muratekici committed Sep 24, 2020
1 parent 6631b3c commit f111817
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 81 deletions.
1 change: 1 addition & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ go_config(
debug = "//go/config:debug",
gotags = "//go/config:tags",
linkmode = "//go/config:linkmode",
covermode = "//go/config:covermode",
msan = "//go/config:msan",
pure = "//go/config:pure",
race = "//go/config:race",
Expand Down
6 changes: 6 additions & 0 deletions go/config/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ string_flag(
visibility = ["//visibility:public"],
)

string_flag(
name = "covermode",
build_setting_default = "",
visibility = ["//visibility:public"],
)

string_list_flag(
name = "tags",
build_setting_default = [],
Expand Down
4 changes: 2 additions & 2 deletions go/private/actions/compilepkg.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ def emit_compilepkg(
if cover and go.coverdata:
inputs.append(go.coverdata.data.export_file)
args.add("-arc", _archive(go.coverdata))
if go.coverage_mode != "":
args.add("-cover_mode", go.coverage_mode)
if go.mode.covermode != "":
args.add("-cover_mode", go.mode.covermode)
elif go.mode.race:
args.add("-cover_mode", "atomic")
else:
Expand Down
6 changes: 5 additions & 1 deletion go/private/context.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,6 @@ def go_context(ctx, attr = None):
nogo = nogo,
coverdata = coverdata,
coverage_enabled = ctx.configuration.coverage_enabled,
coverage_mode = ctx.configuration.coverage_mode,
coverage_instrumented = ctx.coverage_instrumented(),
env = env,
tags = tags,
Expand Down Expand Up @@ -765,6 +764,7 @@ def _go_config_impl(ctx):
strip = ctx.attr.strip[BuildSettingInfo].value,
debug = ctx.attr.debug[BuildSettingInfo].value,
linkmode = ctx.attr.linkmode[BuildSettingInfo].value,
covermode = ctx.attr.covermode[BuildSettingInfo].value,
tags = ctx.attr.gotags[BuildSettingInfo].value,
stamp = ctx.attr.stamp,

Expand Down Expand Up @@ -803,6 +803,10 @@ go_config = rule(
mandatory = True,
providers = [BuildSettingInfo],
),
"covermode": attr.label(
mandatory = True,
providers = [BuildSettingInfo],
),
"gotags": attr.label(
mandatory = True,
providers = [BuildSettingInfo],
Expand Down
2 changes: 2 additions & 0 deletions go/private/mode.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ def get_mode(ctx, go_toolchain, cgo_context_info, go_config_info):
stamp = go_config_info.stamp if go_config_info else False
debug = go_config_info.debug if go_config_info else False
linkmode = go_config_info.linkmode if go_config_info else LINKMODE_NORMAL
covermode = go_config_info.covermode if go_config_info else ""
goos = go_toolchain.default_goos
goarch = go_toolchain.default_goarch

Expand All @@ -110,6 +111,7 @@ def get_mode(ctx, go_toolchain, cgo_context_info, go_config_info):
msan = msan,
pure = pure,
link = linkmode,
covermode = covermode,
strip = strip,
stamp = stamp,
debug = debug,
Expand Down
5 changes: 2 additions & 3 deletions go/tools/builders/cover.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ func cover(args []string) error {
// a coverage-instrumented version of the file. It also registers the file
// with the coverdata package.
func instrumentForCoverage(goenv *env, srcPath, srcName, coverVar, mode, outPath string) error {

if mode == "func" {
err := instrumentForFunctionCoverage(srcPath, srcName, coverVar, outPath)
if err != nil {
Expand Down Expand Up @@ -128,11 +127,11 @@ func registerCoverage(coverSrc, varName, srcName, mode string) error {
return fmt.Errorf("registerCoverage: could not reformat coverage source %s: %v", coverSrc, err)
}

// Append an init function.
// Append an init function accordingly to the coverage mode.
if mode == "func" {
fmt.Fprintf(&buf, `
func init() {
%s.RegisterFileFuncCover(%s.SourcePath, %s.FuncNames, %s.FuncLines, %s.Flags)
%s.RegisterFileFunc(%s.SourcePath, %s.FuncNames, %s.FuncLines, %s.Executed)
}`, coverdataName, varName, varName, varName, varName)
} else {
fmt.Fprintf(&buf, `
Expand Down
82 changes: 42 additions & 40 deletions go/tools/builders/funccover_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,31 +25,33 @@ import (
"text/template"
)

// FuncCoverBlock contains tha name and line of a function
// Line contains the line of the definition in the source code
type FuncCoverBlock struct {
// Contains the function information.
// Line keeps the definition line of the function in the source code.
type funcCoverBlock struct {
Name string
Line int32
}

// SaveFuncs parses given source code and returns a FuncCover instance
func SaveFuncs(src string, content []byte) ([]FuncCoverBlock, error) {

// Parses given source code and returns a funcCoverBlock array.
func saveFuncs(src string, content []byte) ([]funcCoverBlock, error) {
fset := token.NewFileSet()

parsedFile, err := parser.ParseFile(fset, "", content, parser.ParseComments)
if err != nil {
return nil, err
}

var funcBlocks []FuncCoverBlock
var funcBlocks []funcCoverBlock

// Find function declerations to instrument and save them to funcCover
// Finds function declerations to instrument and saves them to funcCover
for _, decl := range parsedFile.Decls {
switch t := decl.(type) {
// Function Decleration
// Function decleration is found.
case *ast.FuncDecl:
funcBlocks = append(funcBlocks, FuncCoverBlock{
if t.Body == nil {
continue
}
funcBlocks = append(funcBlocks, funcCoverBlock{
Name: t.Name.Name,
Line: int32(fset.Position(t.Pos()).Line),
})
Expand All @@ -66,9 +68,9 @@ func astToByte(fset *token.FileSet, f *ast.File) []byte {
return buf.Bytes()
}

// InsertInstructions writes necessary set instrucions for instrumentation to function definitions
func InsertInstructions(w io.Writer, content []byte, coverVar string) (bool, error) {

// Inserts necessary set instrucions for instrumentation to function definitions.
// Writes the instrumented output using w.
func insertInstructions(w io.Writer, content []byte, coverVar string) (bool, error) {
fset := token.NewFileSet()
parsedFile, err := parser.ParseFile(fset, "", content, parser.ParseComments)
if err != nil {
Expand All @@ -79,56 +81,56 @@ func InsertInstructions(w io.Writer, content []byte, coverVar string) (bool, err
var events []int
var mainLbrace = -1

// Iterates over functions to find the positions to insert instructions
// Saves the positions to array events
// Finds the position of main function if exists
// This will be used to insert a defer call, so that coverage can be collected just before exit
// Positions are changed due to imports but saved information in funcCover will be the same with the
// initial source code
// Iterates over functions to find the positions to insert instructions.
// Saves the positions to array events.
// Finds the position of main function if exists.
// This will be used to insert a defer call to an exit hook for coverage report on exit.
for _, decl := range parsedFile.Decls {
switch t := decl.(type) {
// Function Decleration
// Function decleration is found.
case *ast.FuncDecl:
if t.Body == nil {
continue
}
events = append(events, int(t.Body.Lbrace))
if t.Name.Name == "main" {
mainLbrace = int(t.Body.Lbrace) - 1
}
}
}

// Writes the instrumented code using w io.Writer
// Insert set instructions to the functions
// Writes the instrumented code using w io.Writer.
// Insert set instructions to the functions.
// f() {
// coverVar.Counts[funcNumber] = 1;
// coverVar.Executed[funcNumber] = true;
// ...
// }
// Also inserts defer LastCallForFunccoverReport() to the beginning of main()
// Initially this is just an empty function but handler can override it
// Also inserts "defer (*FuncCoverExitHook)()" to the beginning of main().
// Initially hook just points to an empty function but the handler can override it.
// func main {
// defer LastCallForFunccoverReport()
// defer (*FuncCoverExitHook)()
// ...
// }
eventIndex := 0
for i := 0; i < contentLength; i++ {
if eventIndex < len(events) && i == events[eventIndex] {
fmt.Fprintf(w, "\n\t%s.Flags[%v] = true;", coverVar, eventIndex)
fmt.Fprintf(w, "\n\t%s.Executed[%v] = true;", coverVar, eventIndex)
eventIndex++
}
fmt.Fprintf(w, "%s", string(content[i]))
fmt.Fprintf(w, "%s", string(content[i:i+1]))
if i == mainLbrace {
fmt.Fprintf(w, "\n\tdefer (*LastCallForFunccoverReport)()\n")
fmt.Fprintf(w, "\n\tdefer (*FuncCoverExitHook)()\n")
}
}

return mainLbrace != -1, nil
}

// declCover writes the declaration of cover variable to the end of the given source file writer using go templates
// If source has a main function, defines LastCallForFunccoverReport function variable and assign an empty
// function to it
// Embedded report libraries can implement an init() function that assigns LastCallForFunccoverReport
// a different function referance
func declCover(w io.Writer, coverVar, source string, hasMain bool, funcCover []FuncCoverBlock) {
// Writes the declaration of cover variables using go templates.
// If source has a main function, defines FuncCoverExitHook function pointer.
// Initializes FuncCoverExitHook with an empty function.
// Embedded report libraries can implement an init() function to use FuncCoverExitHook.
func declCover(w io.Writer, coverVar, source string, hasMain bool, funcCover []funcCoverBlock) {

funcTemplate, err := template.New("cover variables").Parse(declTmpl)

Expand All @@ -140,7 +142,7 @@ func declCover(w io.Writer, coverVar, source string, hasMain bool, funcCover []F
CoverVar string
SourceName string
HasMain bool
FuncBlocks []FuncCoverBlock
FuncBlocks []funcCoverBlock
}{coverVar, source, hasMain, funcCover}

err = funcTemplate.Execute(w, declParams)
Expand All @@ -155,7 +157,7 @@ var {{.CoverVar}} = struct {
SourcePath string
FuncNames []string
FuncLines []int32
Flags []bool
Executed []bool
} {
SourcePath: "{{.SourceName}}",
FuncNames: []string{ {{range .FuncBlocks}}
Expand All @@ -164,11 +166,11 @@ var {{.CoverVar}} = struct {
FuncLines: []int32{ {{range .FuncBlocks}}
{{.Line}},{{end}}
},
Flags: []bool{ {{range .FuncBlocks}}
Executed: []bool{ {{range .FuncBlocks}}
false,{{end}}
},
}
{{ if eq .HasMain true }}
var EmptyVoidFunctionThisNameIsLongToAvoidCollusions082 = func() {}
var LastCallForFunccoverReport *func() = &EmptyVoidFunctionThisNameIsLongToAvoidCollusions082{{end}}`
var FuncCoverExitHookEmptyFunc = func() {}
var FuncCoverExitHook *func() = &FuncCoverExitHookEmptyFunc{{end}}`
36 changes: 16 additions & 20 deletions go/tools/builders/funccover_instrumenter.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,17 @@ import (
"io/ioutil"
)

// Instrumentation keeps the data necessary for instrumentation
type Instrumentation struct {
// funccoverInstrumenter struct have the data necessary for instrumentation.
type funccoverInstrumenter struct {
fset *token.FileSet
content []byte
coverVar string
outPath string
srcName string
}

// saveFile saves given file to instrumentation
func (h *Instrumentation) saveFile(src string) error {

// Saves given file's content to funccoverInstrumenter instance.
func (h *funccoverInstrumenter) saveFile(src string) error {
if h.fset == nil {
h.fset = token.NewFileSet()
}
Expand All @@ -48,21 +47,20 @@ func (h *Instrumentation) saveFile(src string) error {
return nil
}

// instrument function instruments the content saved in Instrumentation
func (h *Instrumentation) instrument() ([]byte, error) {

var funcCover = []FuncCoverBlock{}
// Instruments the source code content saved in h.
func (h *funccoverInstrumenter) instrument() ([]byte, error) {
var funcCover = []funcCoverBlock{}

// Saves the function data to funcCover
funcCover, err := SaveFuncs(h.srcName, h.content)
funcCover, err := saveFuncs(h.srcName, h.content)
if err != nil {
return nil, err
}

buf := new(bytes.Buffer)

// Inserts necessary instructions to the functions
hasMain, err := InsertInstructions(buf, h.content, h.coverVar)
hasMain, err := insertInstructions(buf, h.content, h.coverVar)

if err != nil {
return nil, err
Expand All @@ -74,35 +72,33 @@ func (h *Instrumentation) instrument() ([]byte, error) {
return buf.Bytes(), nil
}

// writeInstrumented writes the instrumented source to h.outPath
func (h *Instrumentation) writeInstrumented(instrumented []byte) error {
// Writes the instrumented content to h.outPath
func (h *funccoverInstrumenter) writeInstrumented(instrumented []byte) error {
if err := ioutil.WriteFile(h.outPath, instrumented, 0666); err != nil {
return fmt.Errorf("Instrumentation failed: %v", err)
}
return nil
}

// instrumentForFunctionCoverage instruments the file given and writes it to outPath
// Instruments the file given and writes it to outPath
func instrumentForFunctionCoverage(srcPath, srcName, coverVar, outPath string) error {

var instrumentation = Instrumentation{
var instrumenter = funccoverInstrumenter{
coverVar: coverVar,
outPath: outPath,
srcName: srcName,
}

err := instrumentation.saveFile(srcPath)
err := instrumenter.saveFile(srcPath)
if err != nil {
return err
}

instrumented, err := instrumentation.instrument()
instrumented, err := instrumenter.instrument()
if err != nil {
return err
}

err = instrumentation.writeInstrumented(instrumented)

err = instrumenter.writeInstrumented(instrumented)
if err != nil {
return err
}
Expand Down
30 changes: 15 additions & 15 deletions go/tools/coverdata/coverdata_func.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,24 @@

package coverdata

// FunctionCover contains the coverage data needed
type FunctionCover struct {
SourcePaths []string
FunctionNames []string
FunctionLines []int32
Flags []*bool
// FuncCover contains the coverage data needed
type FuncCover struct {
SourcePaths []string
FuncNames []string
FuncLines []int32
Executed []*bool
}

// FuncCover keeps the coverage data
// It is exported so that other packages can use it to report coverage
var FuncCover FunctionCover
// FuncCoverData keeps the coverage data in runtime
// Instrumented packages registers their coverage data to FuncCoverData
var FuncCoverData FuncCover

// RegisterFileFuncCover eegisters functions to exported variable FuncCover
func RegisterFileFuncCover(SourcePath string, FuncNames []string, FuncLines []int32, Flags []bool) {
// RegisterFileFunc eegisters functions to exported variable FuncCoverData
func RegisterFileFunc(SourcePath string, FuncNames []string, FuncLines []int32, Executed []bool) {
for i, funcName := range FuncNames {
FuncCover.SourcePaths = append(FuncCover.SourcePaths, SourcePath)
FuncCover.FunctionNames = append(FuncCover.FunctionNames, funcName)
FuncCover.FunctionLines = append(FuncCover.FunctionLines, FuncLines[i])
FuncCover.Flags = append(FuncCover.Flags, &(Flags[i]))
FuncCoverData.SourcePaths = append(FuncCoverData.SourcePaths, SourcePath)
FuncCoverData.FuncNames = append(FuncCoverData.FuncNames, funcName)
FuncCoverData.FuncLines = append(FuncCoverData.FuncLines, FuncLines[i])
FuncCoverData.Executed = append(FuncCoverData.Executed, &(Executed[i]))
}
}

0 comments on commit f111817

Please sign in to comment.