From 7bcfd232fa5b87a9c04d3d20fd1033bfde7e56fc Mon Sep 17 00:00:00 2001 From: Volodymyr Gubarkov Date: Sat, 22 Oct 2022 00:36:56 +0300 Subject: [PATCH] Code coverage support (#154) This PR adds code coverage support to the goawk command with the -cover* options. See details in cover.md. --- cover.md | 59 ++++ cover.png | Bin 0 -> 94367 bytes goawk.go | 102 ++++++- goawk_test.go | 159 ++++++++++- internal/ast/ast.go | 109 ++++++-- internal/cover/cover.go | 253 ++++++++++++++++++ internal/cover/cover_test.go | 69 +++++ interp/newexecute.go | 24 ++ interp/newexecute_test.go | 28 ++ lexer/lexer.go | 6 + parser/parser.go | 37 +-- parser/parser_test.go | 102 +++++++ testdata/cover/a1.awk | 11 + testdata/cover/a1_a2_covermode_count.awk | 33 +++ testdata/cover/a1_annotation_data.txt | 3 + testdata/cover/a1_covermode_set.awk | 18 ++ testdata/cover/a2.awk | 8 + testdata/cover/a2_annotation_data.txt | 4 + testdata/cover/a2_covermode_count.awk | 14 + testdata/cover/a3.awk | 22 ++ testdata/cover/a3_annotation_data.txt | 10 + testdata/cover/a3_covermode_set.awk | 34 +++ testdata/cover/test_1file2runs_set.cov | 7 + .../cover/test_1file2runs_set_truncated.cov | 4 + testdata/cover/test_2file2runs_count.cov | 15 ++ .../cover/test_2file2runs_count_truncated.cov | 8 + testdata/cover/test_a2a1_count.cov | 8 + testdata/cover/test_a2a1_set.cov | 8 + testdata/cover/test_count.cov | 4 + testdata/cover/test_set.cov | 4 + 30 files changed, 1113 insertions(+), 50 deletions(-) create mode 100644 cover.md create mode 100644 cover.png create mode 100644 internal/cover/cover.go create mode 100644 internal/cover/cover_test.go create mode 100644 testdata/cover/a1.awk create mode 100644 testdata/cover/a1_a2_covermode_count.awk create mode 100644 testdata/cover/a1_annotation_data.txt create mode 100644 testdata/cover/a1_covermode_set.awk create mode 100644 testdata/cover/a2.awk create mode 100644 testdata/cover/a2_annotation_data.txt create mode 100644 testdata/cover/a2_covermode_count.awk create mode 100644 testdata/cover/a3.awk create mode 100644 testdata/cover/a3_annotation_data.txt create mode 100644 testdata/cover/a3_covermode_set.awk create mode 100644 testdata/cover/test_1file2runs_set.cov create mode 100644 testdata/cover/test_1file2runs_set_truncated.cov create mode 100644 testdata/cover/test_2file2runs_count.cov create mode 100644 testdata/cover/test_2file2runs_count_truncated.cov create mode 100644 testdata/cover/test_a2a1_count.cov create mode 100644 testdata/cover/test_a2a1_set.cov create mode 100644 testdata/cover/test_count.cov create mode 100644 testdata/cover/test_set.cov diff --git a/cover.md b/cover.md new file mode 100644 index 00000000..021dbb77 --- /dev/null +++ b/cover.md @@ -0,0 +1,59 @@ +# Code coverage for GoAWK + +GoAWK implements code coverage functionality, similar to the one built in the Golang itself (https://go.dev/blog/cover). + +![screenshot](cover.png) + +## How to use + +To generate coverage report: +``` +goawk -f a.awk -coverprofile c.out -covermode set +``` +This generates `c.out` file with coverage profile data for the `a.awk` program execution. + +Now it's time to visualize the coverage report `c.out` by rendering it to HTML. + +Please note. The code coverage functionality of GoAWK relies on Golang toolset. +So to generate the HTML coverage report (like on the screenshot above) use the command below. This opens a web browser displaying annotated source code: +``` +go tool cover -html=c.out +``` + +Write out an HTML file instead of launching a web browser: +``` +go tool cover -html=c.out -o coverage.html +``` + +Please note. At the moment it's not possible to generate coverage report for AWK functions, similar to what is possible for Go: +``` +go tool cover -func=c.out # this will not work :( +``` +This won't work because the `go tool cover` functionality for `-func` is only capable of processing the Go code. Luckily, the `-html` report option is generic enough to process any source code! + +If you don't provide the `coverprofile` argument for `goawk` but you do provide `-covermode`, it just prints to stdout the coverage-annotated source code (this might be useful for debug purposes): +``` +goawk -f a.awk -covermode set +``` + + +## CLI options for coverage + +- `-coverprofile ` + - sets the cover report filename to put collected coverage profile data. +If this option is omitted, but the `-covermode` is set - outputs the annotated awk source to stdout. +- `-covermode ` + - `mode` can be one of: + - `set` - did each statement run? + - `count` - how many times did each statement run? (Produces heat maps report). +- `-coverappend` + - if this option is provided the profile data will append to existing, otherwise the profile file will be first truncated. + +## Future work + +- Add a way to support per-function coverage report similar to `go tool cover -func=c.out` +- More complete handling for coverage cases for `if/else`. That is not only we want to check if `if` body was visited but also we want to make sure the case where `if` body was *not* visited is also covered. In other words we want to make sure that both variants of `if` condition is covered. + +## Feedback + +Please [open an issue](https://github.com/benhoyt/goawk/issues) if you have bug reports or feature requests for GoAWK's code coverage support. diff --git a/cover.png b/cover.png new file mode 100644 index 0000000000000000000000000000000000000000..98f2b475f578fefed271a7307fc4d62e65ff821e GIT binary patch literal 94367 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 len(os.Args) { + errorExitf("flag needs an argument: -covermode") + } + i++ + coverMode = coverModeFromString(os.Args[i]) + case "-coverprofile": + if i+1 >= len(os.Args) { + errorExitf("flag needs an argument: -coverprofile") + } + i++ + coverProfile = os.Args[i] + case "-coverappend": + coverAppend = true case "-E": if i+1 >= len(os.Args) { errorExitf("flag needs an argument: -E") @@ -134,7 +157,7 @@ argsLoop: errorExitf("flag needs an argument: -cpuprofile") } i++ - cpuprofile = os.Args[i] + cpuProfile = os.Args[i] case "-d": debug = true case "-da": @@ -157,7 +180,7 @@ argsLoop: errorExitf("flag needs an argument: -memprofile") } i++ - memprofile = os.Args[i] + memProfile = os.Args[i] case "-o": if i+1 >= len(os.Args) { errorExitf("flag needs an argument: -o") @@ -185,14 +208,21 @@ argsLoop: case strings.HasPrefix(arg, "-v"): vars = append(vars, arg[2:]) case strings.HasPrefix(arg, "-cpuprofile="): - cpuprofile = arg[12:] + cpuProfile = arg[len("-cpuprofile="):] case strings.HasPrefix(arg, "-memprofile="): - memprofile = arg[12:] + memProfile = arg[len("-memprofile="):] + case strings.HasPrefix(arg, "-covermode="): + coverMode = coverModeFromString(arg[len("-covermode="):]) + case strings.HasPrefix(arg, "-coverprofile="): + coverProfile = arg[len("-coverprofile="):] default: errorExitf("flag provided but not defined: %s", arg) } } } + if coverProfile != "" && coverMode == cover.ModeUnspecified { + coverMode = cover.ModeSet + } // Any remaining args are program and input files args := os.Args[i:] @@ -248,6 +278,29 @@ argsLoop: errorExitf("%s", err) } + coverage := cover.New(coverMode, coverAppend, fileReader) + + if coverMode != cover.ModeUnspecified { + astProgram := &prog.ResolvedProgram.Program + coverage.Annotate(astProgram) + + // re-resolve annotated program + prog.ResolvedProgram = *resolver.Resolve(astProgram, &resolver.Config{ + DebugTypes: parserConfig.DebugTypes, + DebugWriter: parserConfig.DebugWriter}) + + // re-compile it + prog.Compiled, err = compiler.Compile(&prog.ResolvedProgram) + if err != nil { + errorExitf("%s", err) + } + + if coverProfile == "" { + fmt.Fprintln(os.Stdout, prog) + os.Exit(0) + } + } + if debug { fmt.Fprintln(os.Stdout, prog) } @@ -303,8 +356,8 @@ argsLoop: config.Vars = append(config.Vars, name, value) } - if cpuprofile != "" { - f, err := os.Create(cpuprofile) + if cpuProfile != "" { + f, err := os.Create(cpuProfile) if err != nil { errorExitf("could not create CPU profile: %v", err) } @@ -314,16 +367,25 @@ argsLoop: } // Run the program! - status, err := interp.ExecProgram(prog, config) + interpreter, err := interp.New(prog) + status, err := interpreter.Execute(config) + if err != nil { errorExit(err) } - if cpuprofile != "" { + if coverProfile != "" { + err := coverage.WriteProfile(coverProfile, interpreter.Array(cover.ArrayName)) + if err != nil { + errorExitf("unable to write coverage profile: %v", err) + } + } + + if cpuProfile != "" { pprof.StopCPUProfile() } - if memprofile != "" { - f, err := os.Create(memprofile) + if memProfile != "" { + f, err := os.Create(memProfile) if err != nil { errorExitf("could not create memory profile: %v", err) } @@ -337,6 +399,18 @@ argsLoop: os.Exit(status) } +func coverModeFromString(mode string) cover.Mode { + switch mode { + case "set": + return cover.ModeSet + case "count": + return cover.ModeCount + default: + errorExitf("-covermode can only be one of: set, count") + return cover.ModeUnspecified + } +} + // Show source line and position of error, for example: // // BEGIN { x*; } diff --git a/goawk_test.go b/goawk_test.go index a3d78b4a..75079ad4 100644 --- a/goawk_test.go +++ b/goawk_test.go @@ -448,7 +448,7 @@ func TestCommandLine(t *testing.T) { func TestDevStdout(t *testing.T) { if runtime.GOOS == "windows" { - t.Skip("/dev/stdout not presnt on Windows") + t.Skip("/dev/stdout not present on Windows") } runAWKs(t, []string{`BEGIN { print "1"; print "2">"/dev/stdout" }`}, "", "1\n2\n", "") } @@ -758,3 +758,160 @@ func TestMandelbrot(t *testing.T) { t.Fatalf("expected:\n%s\ngot:\n%s", expected, stdout) } } + +func TestCoverPrintAnnotatedSource(t *testing.T) { + tests := []struct { + sourceFiles []string + mode string + expectedResultFile string + }{ + {[]string{"a1.awk"}, "set", "a1_covermode_set.awk"}, + {[]string{"a2.awk"}, "count", "a2_covermode_count.awk"}, + {[]string{"a1.awk", "a2.awk"}, "count", "a1_a2_covermode_count.awk"}, + {[]string{"a3.awk"}, "set", "a3_covermode_set.awk"}, + } + + for _, test := range tests { + t.Run(test.expectedResultFile, func(t *testing.T) { + args := []string{"-covermode", test.mode} + for _, file := range test.sourceFiles { + args = append(args, "-f", "testdata/cover/"+file) + } + stdout, stderr, err := runGoAWK(args, "") + if err != nil { + t.Fatalf("expected no error, got %v (%q)", err, stderr) + } + expected, err := ioutil.ReadFile("testdata/cover/" + test.expectedResultFile) + if err != nil { + t.Fatal(err) + } + if normalizeAwkSource(stdout) != normalizeAwkSource(string(expected)) { + t.Fatalf("output differs, got:\n%s\nexpected:\n%s", stdout, expected) + } + }) + } +} + +func normalizeAwkSource(source string) string { + p, err := parser.ParseProgram([]byte(source), nil) + if err != nil { + panic(err) + } + return p.String() +} + +func TestCoverInvalidArgs(t *testing.T) { + tests := []struct { + args []string + expectedError string + }{ + {[]string{"-coverprofile"}, "flag needs an argument: -coverprofile"}, + {[]string{"-covermode"}, "flag needs an argument: -covermode"}, + {[]string{"-covermode", "wrong"}, "-covermode can only be one of: set, count"}, + {[]string{"-covermode=wrong"}, "-covermode can only be one of: set, count"}, + } + + for _, test := range tests { + t.Run(test.expectedError, func(t *testing.T) { + _, stderr, err := runGoAWK(test.args, "") + if err == nil { + t.Fatalf("expected error") + } + stderr = strings.TrimSpace(stderr) + if stderr != test.expectedError { + t.Fatalf("error differs, got:\n%s\nexpected:\n%s", stderr, test.expectedError) + } + }) + } +} + +func TestCover(t *testing.T) { + tests := []struct { + mode string + coverAppend bool + runs [][]string + expectedCoverReport string + }{ + {"set", true, [][]string{{"a1.awk"}}, "test_set.cov"}, + {"count", true, [][]string{{"a1.awk"}}, "test_count.cov"}, + {"set", true, [][]string{{"a2.awk", "a1.awk"}}, "test_a2a1_set.cov"}, + {"count", true, [][]string{{"a2.awk", "a1.awk"}}, "test_a2a1_count.cov"}, + {"set", true, [][]string{{"a1.awk"}, {"a1.awk"}}, "test_1file2runs_set.cov"}, + {"count", true, [][]string{{"a2.awk", "a1.awk"}, {"a2.awk", "a1.awk"}}, "test_2file2runs_count.cov"}, + {"set", false, [][]string{{"a1.awk"}, {"a1.awk"}}, "test_1file2runs_set_truncated.cov"}, + {"count", false, [][]string{{"a2.awk", "a1.awk"}, {"a2.awk", "a1.awk"}}, "test_2file2runs_count_truncated.cov"}, + } + + for _, test := range tests { + t.Run(test.expectedCoverReport, func(t *testing.T) { + tempFile, err := ioutil.TempFile("", "testCov*.txt") + if err != nil { + t.Fatalf("%v", err) + } + err = tempFile.Close() + if err != nil { + t.Fatalf("%v", err) + } + coverProfile := tempFile.Name() + defer os.Remove(coverProfile) + + // make sure file doesn't exist - delete the temp file first, but use its name + err = os.Remove(coverProfile) + if err != nil { + t.Fatalf("%v", err) + } + + for _, run := range test.runs { + var args []string + for _, file := range run { + args = append(args, "-f", "testdata/cover/"+file) + } + args = append(args, "-coverprofile", coverProfile) + args = append(args, "-covermode", test.mode) + if test.coverAppend { + args = append(args, "-coverappend") + } + _, _, err := runGoAWK(args, "") + if err != nil { + t.Fatalf("%v", err) + } + } + + result, err := ioutil.ReadFile(coverProfile) + if err != nil { + t.Fatalf("%v", err) + } + resultStr := string(normalizeNewlines(result)) + resultStr = strings.TrimSpace(convertPathsToFilenames(t, resultStr)) + expected, err := ioutil.ReadFile("testdata/cover/" + test.expectedCoverReport) + if err != nil { + t.Fatalf("%v", err) + } + expectedStr := strings.TrimSpace(string(normalizeNewlines(expected))) + if resultStr != expectedStr { + t.Fatalf("wrong coverage report, expected:\n\n%s\n\nactual:\n\n%s", expectedStr, resultStr) + } + }) + } +} + +func convertPathsToFilenames(t *testing.T, str string) string { + lines := strings.Split(str, "\n") + for i, line := range lines { + if i == 0 { + continue // skip mode line + } + if line == "" { + continue // skip empty line + } + parts := strings.Split(line, ":") // on win line can be `D:\a\goawk\goawk\testdata\cover\a1.awk:2.3,6.30 5 1` + path := strings.Join(parts[:len(parts)-1], ":") + data := parts[len(parts)-1] + if !filepath.IsAbs(path) { + t.Fatalf("must be absolute path in coverage report: %s", line) + } + // leave only the part with name + lines[i] = filepath.Base(path) + ":" + data + } + return strings.Join(lines, "\n") +} diff --git a/internal/ast/ast.go b/internal/ast/ast.go index de19ce32..0924febb 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -434,6 +434,8 @@ func IsLValue(expr Expr) bool { // Stmt is the abstract syntax tree for any AWK statement. type Stmt interface { Node + StartPos() Position // position of first character belonging to the node + EndPos() Position // position of first character immediately after the node stmt() String() string } @@ -455,11 +457,45 @@ func (s *DeleteStmt) stmt() {} func (s *ReturnStmt) stmt() {} func (s *BlockStmt) stmt() {} +func (s *PrintStmt) StartPos() Position { return s.Start } +func (s *PrintfStmt) StartPos() Position { return s.Start } +func (s *ExprStmt) StartPos() Position { return s.Start } +func (s *IfStmt) StartPos() Position { return s.Start } +func (s *ForStmt) StartPos() Position { return s.Start } +func (s *ForInStmt) StartPos() Position { return s.Start } +func (s *WhileStmt) StartPos() Position { return s.Start } +func (s *DoWhileStmt) StartPos() Position { return s.Start } +func (s *BreakStmt) StartPos() Position { return s.Start } +func (s *ContinueStmt) StartPos() Position { return s.Start } +func (s *NextStmt) StartPos() Position { return s.Start } +func (s *ExitStmt) StartPos() Position { return s.Start } +func (s *DeleteStmt) StartPos() Position { return s.Start } +func (s *ReturnStmt) StartPos() Position { return s.Start } +func (s *BlockStmt) StartPos() Position { return s.Start } + +func (s *PrintStmt) EndPos() Position { return s.End } +func (s *PrintfStmt) EndPos() Position { return s.End } +func (s *ExprStmt) EndPos() Position { return s.End } +func (s *IfStmt) EndPos() Position { return s.End } +func (s *ForStmt) EndPos() Position { return s.End } +func (s *ForInStmt) EndPos() Position { return s.End } +func (s *WhileStmt) EndPos() Position { return s.End } +func (s *DoWhileStmt) EndPos() Position { return s.End } +func (s *BreakStmt) EndPos() Position { return s.End } +func (s *ContinueStmt) EndPos() Position { return s.End } +func (s *NextStmt) EndPos() Position { return s.End } +func (s *ExitStmt) EndPos() Position { return s.End } +func (s *DeleteStmt) EndPos() Position { return s.End } +func (s *ReturnStmt) EndPos() Position { return s.End } +func (s *BlockStmt) EndPos() Position { return s.End } + // PrintStmt is a statement like print $1, $3. type PrintStmt struct { Args []Expr Redirect Token Dest Expr + Start Position + End Position } func (s *PrintStmt) String() string { @@ -483,6 +519,8 @@ type PrintfStmt struct { Args []Expr Redirect Token Dest Expr + Start Position + End Position } func (s *PrintfStmt) String() string { @@ -491,7 +529,9 @@ func (s *PrintfStmt) String() string { // ExprStmt is statement like a bare function call: my_func(x). type ExprStmt struct { - Expr Expr + Expr Expr + Start Position + End Position } func (s *ExprStmt) String() string { @@ -500,9 +540,12 @@ func (s *ExprStmt) String() string { // IfStmt is an if or if-else statement. type IfStmt struct { - Cond Expr - Body Stmts - Else Stmts + Cond Expr + BodyStart Position + Body Stmts + Else Stmts + Start Position + End Position } func (s *IfStmt) String() string { @@ -515,10 +558,13 @@ func (s *IfStmt) String() string { // ForStmt is a C-like for loop: for (i=0; i<10; i++) print i. type ForStmt struct { - Pre Stmt - Cond Expr - Post Stmt - Body Stmts + Pre Stmt + Cond Expr + Post Stmt + BodyStart Position + Body Stmts + Start Position + End Position } func (s *ForStmt) String() string { @@ -539,9 +585,12 @@ func (s *ForStmt) String() string { // ForInStmt is a for loop like for (k in a) print k, a[k]. type ForInStmt struct { - Var *VarExpr - Array *ArrayExpr - Body Stmts + Var *VarExpr + Array *ArrayExpr + BodyStart Position + Body Stmts + Start Position + End Position } func (s *ForInStmt) String() string { @@ -550,8 +599,11 @@ func (s *ForInStmt) String() string { // WhileStmt is a while loop. type WhileStmt struct { - Cond Expr - Body Stmts + Cond Expr + BodyStart Position + Body Stmts + Start Position + End Position } func (s *WhileStmt) String() string { @@ -560,8 +612,10 @@ func (s *WhileStmt) String() string { // DoWhileStmt is a do-while loop. type DoWhileStmt struct { - Body Stmts - Cond Expr + Body Stmts + Cond Expr + Start Position + End Position } func (s *DoWhileStmt) String() string { @@ -569,21 +623,30 @@ func (s *DoWhileStmt) String() string { } // BreakStmt is a break statement. -type BreakStmt struct{} +type BreakStmt struct { + Start Position + End Position +} func (s *BreakStmt) String() string { return "break" } // ContinueStmt is a continue statement. -type ContinueStmt struct{} +type ContinueStmt struct { + Start Position + End Position +} func (s *ContinueStmt) String() string { return "continue" } // NextStmt is a next statement. -type NextStmt struct{} +type NextStmt struct { + Start Position + End Position +} func (s *NextStmt) String() string { return "next" @@ -592,6 +655,8 @@ func (s *NextStmt) String() string { // ExitStmt is an exit statement. type ExitStmt struct { Status Expr + Start Position + End Position } func (s *ExitStmt) String() string { @@ -606,6 +671,8 @@ func (s *ExitStmt) String() string { type DeleteStmt struct { Array *ArrayExpr Index []Expr + Start Position + End Position } func (s *DeleteStmt) String() string { @@ -619,6 +686,8 @@ func (s *DeleteStmt) String() string { // ReturnStmt is a return statement. type ReturnStmt struct { Value Expr + Start Position + End Position } func (s *ReturnStmt) String() string { @@ -631,7 +700,9 @@ func (s *ReturnStmt) String() string { // BlockStmt is a stand-alone block like { print "x" }. type BlockStmt struct { - Body Stmts + Body Stmts + Start Position + End Position } func (s *BlockStmt) String() string { diff --git a/internal/cover/cover.go b/internal/cover/cover.go new file mode 100644 index 00000000..fe99de77 --- /dev/null +++ b/internal/cover/cover.go @@ -0,0 +1,253 @@ +// Package cover implements AWK code coverage and reporting. +package cover + +import ( + "fmt" + "os" + "path/filepath" + "strconv" + + "github.com/benhoyt/goawk/internal/ast" + "github.com/benhoyt/goawk/internal/parseutil" + "github.com/benhoyt/goawk/lexer" +) + +const ArrayName = "__COVER" + +type Mode int + +const ( + ModeUnspecified Mode = iota + ModeSet + ModeCount +) + +func (m Mode) String() string { + switch m { + case ModeSet: + return "set" + case ModeCount: + return "count" + default: + return fmt.Sprintf("", m) + } +} + +type Cover struct { + mode Mode + append bool + fileReader *parseutil.FileReader + trackedBlocks []trackedBlock +} + +type trackedBlock struct { + start lexer.Position + end lexer.Position + path string + numStmts int +} + +func New(mode Mode, append bool, fileReader *parseutil.FileReader) *Cover { + return &Cover{ + mode: mode, + append: append, + fileReader: fileReader, + } +} + +// Annotate annotates the program with coverage tracking code. +func (cover *Cover) Annotate(prog *ast.Program) { + prog.Begin = cover.annotateStmtsList(prog.Begin) + prog.Actions = cover.annotateActions(prog.Actions) + prog.End = cover.annotateStmtsList(prog.End) + prog.Functions = cover.annotateFunctions(prog.Functions) +} + +// WriteProfile writes coverage data to a file at the given path. +func (cover *Cover) WriteProfile(path string, data map[string]interface{}) error { + // 1a. If file doesn't exist - create and write cover mode line + // 1b. If file exists and coverappend=true - open it for writing in append mode + // 1c. If file exists and coverappend=false - truncate it and follow 1a. + // 2. Write all cover data lines + + dataInts, err := dataToInts(data) + if err != nil { + return err + } + isNewFile := true + + var f *os.File + if _, err := os.Stat(path); os.IsNotExist(err) { + f, err = os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + return err + } + } else if err == nil { + // file exists + fileOpt := os.O_TRUNC + if cover.append { + isNewFile = false + fileOpt = os.O_APPEND + } + f, err = os.OpenFile(path, os.O_WRONLY|fileOpt, 0644) + if err != nil { + return err + } + } else { + return err + } + + if isNewFile { + _, err := fmt.Fprintf(f, "mode: %s\n", cover.mode) + if err != nil { + return err + } + } + + for i, block := range cover.trackedBlocks { + _, err := fmt.Fprintf(f, "%s:%d.%d,%d.%d %d %d\n", + toAbsolutePath(block.path), + block.start.Line, block.start.Column, + block.end.Line, block.end.Column, + block.numStmts, dataInts[i+1], + ) + if err != nil { + return err + } + } + return nil +} + +func dataToInts(data map[string]interface{}) (map[int]int, error) { + res := make(map[int]int) + for k, v := range data { + ki, err := strconv.Atoi(k) + if err != nil { + return nil, fmt.Errorf("non-int index in data: %s", k) + } + vf, ok := v.(float64) + if !ok { + return nil, fmt.Errorf("non-float64 value in data: %v", v) + } + res[ki] = int(vf) + } + return res, nil +} + +func (cover *Cover) annotateActions(actions []*ast.Action) []*ast.Action { + res := make([]*ast.Action, 0, len(actions)) + for _, action := range actions { + action.Stmts = cover.annotateStmts(action.Stmts) + res = append(res, action) + } + return res +} + +func (cover *Cover) annotateFunctions(functions []*ast.Function) []*ast.Function { + res := make([]*ast.Function, 0, len(functions)) + for _, function := range functions { + function.Body = cover.annotateStmts(function.Body) + res = append(res, function) + } + return res +} + +func (cover *Cover) annotateStmtsList(stmtsList []ast.Stmts) []ast.Stmts { + res := make([]ast.Stmts, 0, len(stmtsList)) + for _, stmts := range stmtsList { + res = append(res, cover.annotateStmts(stmts)) + } + return res +} + +// annotateStmts takes a list of statements and adds counters to the beginning of +// each basic block at the top level of that list. For instance, given +// +// S1 +// if cond { +// S2 +// } +// S3 +// +// counters will be added before S1,S2,S3. +func (cover *Cover) annotateStmts(stmts ast.Stmts) ast.Stmts { + var res ast.Stmts + var trackedBlockStmts []ast.Stmt + for _, stmt := range stmts { + blockEnds := true + switch s := stmt.(type) { + case *ast.IfStmt: + s.Body = cover.annotateStmts(s.Body) + s.Else = cover.annotateStmts(s.Else) + case *ast.ForStmt: + s.Body = cover.annotateStmts(s.Body) + case *ast.ForInStmt: + s.Body = cover.annotateStmts(s.Body) + case *ast.WhileStmt: + s.Body = cover.annotateStmts(s.Body) + case *ast.DoWhileStmt: + s.Body = cover.annotateStmts(s.Body) + case *ast.BlockStmt: + s.Body = cover.annotateStmts(s.Body) + default: + blockEnds = false + } + trackedBlockStmts = append(trackedBlockStmts, stmt) + if blockEnds { + res = append(res, cover.trackStatement(trackedBlockStmts)) + res = append(res, trackedBlockStmts...) + trackedBlockStmts = []ast.Stmt{} + } + } + if len(trackedBlockStmts) > 0 { + res = append(res, cover.trackStatement(trackedBlockStmts)) + res = append(res, trackedBlockStmts...) + } + return res +} + +func endPos(stmt ast.Stmt) lexer.Position { + switch s := stmt.(type) { + case *ast.IfStmt: + return s.BodyStart + case *ast.ForStmt: + return s.BodyStart + case *ast.ForInStmt: + return s.BodyStart + case *ast.WhileStmt: + return s.BodyStart + default: + return s.EndPos() + } +} + +func (cover *Cover) trackStatement(stmts []ast.Stmt) ast.Stmt { + start1 := stmts[0].StartPos() + end2 := endPos(stmts[len(stmts)-1]) + path, startLine := cover.fileReader.FileLine(start1.Line) + _, endLine := cover.fileReader.FileLine(end2.Line) + cover.trackedBlocks = append(cover.trackedBlocks, trackedBlock{ + start: lexer.Position{startLine, start1.Column}, + end: lexer.Position{endLine, end2.Column}, + path: path, + numStmts: len(stmts), + }) + left := &ast.IndexExpr{ + Array: ast.ArrayRef(ArrayName, lexer.Position{}), + Index: []ast.Expr{&ast.StrExpr{Value: strconv.Itoa(len(cover.trackedBlocks))}}, + } + if cover.mode == ModeCount { + // AST for __COVER[index]++ + return &ast.ExprStmt{Expr: &ast.IncrExpr{Expr: left, Op: lexer.INCR}} + } + // AST for __COVER[index] = 1 + return &ast.ExprStmt{Expr: &ast.AssignExpr{Left: left, Right: &ast.NumExpr{Value: 1}}} +} + +func toAbsolutePath(path string) string { + absPath, err := filepath.Abs(path) + if err != nil { + return path + } + return absPath +} diff --git a/internal/cover/cover_test.go b/internal/cover/cover_test.go new file mode 100644 index 00000000..51aa7c30 --- /dev/null +++ b/internal/cover/cover_test.go @@ -0,0 +1,69 @@ +package cover + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "strings" + "testing" + + "github.com/benhoyt/goawk/internal/parseutil" + "github.com/benhoyt/goawk/parser" +) + +func TestAnnotatingLogicCorrectness(t *testing.T) { + tests := []struct { + awkPath string + checkPath string + }{ + {"a1.awk", "a1_annotation_data.txt"}, + {"a2.awk", "a2_annotation_data.txt"}, + {"a3.awk", "a3_annotation_data.txt"}, + } + + for _, test := range tests { + t.Run(test.awkPath, func(t *testing.T) { + awkPath := "../../testdata/cover/" + test.awkPath + checkPath := "../../testdata/cover/" + test.checkPath + f, err := os.Open(awkPath) + if err != nil { + panic(err) + } + fileReader := &parseutil.FileReader{} + err = fileReader.AddFile(test.awkPath, f) + if err != nil { + panic(err) + } + coverage := New(ModeSet, false, fileReader) + prog, err := parser.ParseProgram(fileReader.Source(), nil) + if err != nil { + panic(err) + } + coverage.Annotate(&prog.ResolvedProgram.Program) + + var actualAnnotationData strings.Builder + + for i, block := range coverage.trackedBlocks { + actualAnnotationData.WriteString(fmt.Sprintf("%d %s %s-%s %d\n", i+1, + block.path, block.start, block.end, + block.numStmts)) + } + + result := strings.TrimSpace(actualAnnotationData.String()) + + expected, err := ioutil.ReadFile(checkPath) + if err != nil { + panic(err) + } + + if strings.TrimSpace(string(normalizeNewlines(expected))) != result { + t.Errorf("Annotation data is wrong:\n\nactual:\n\n%s\n\nexpected:\n\n%s", result, expected) + } + }) + } +} + +func normalizeNewlines(b []byte) []byte { + return bytes.Replace(b, []byte("\r\n"), []byte{'\n'}, -1) +} diff --git a/interp/newexecute.go b/interp/newexecute.go index 438fe6dc..ba3caed8 100644 --- a/interp/newexecute.go +++ b/interp/newexecute.go @@ -6,6 +6,7 @@ import ( "context" "math" + "github.com/benhoyt/goawk/internal/ast" "github.com/benhoyt/goawk/parser" ) @@ -60,6 +61,29 @@ func (p *Interpreter) Execute(config *Config) (int, error) { return p.interp.executeAll() } +// Array returns a map representing the items in the named AWK array. AWK +// numbers are included as type float64, strings (including "numeric strings") +// are included as type string. If the named array does not exist, return nil. +func (p *Interpreter) Array(name string) map[string]interface{} { + index, exists := p.interp.program.Arrays[name] + if !exists { + return nil + } + array := p.interp.array(ast.ScopeGlobal, index) + result := make(map[string]interface{}, len(array)) + for k, v := range array { + switch v.typ { + case typeNum: + result[k] = v.n + case typeStr, typeNumStr: + result[k] = v.s + default: + result[k] = "" + } + } + return result +} + func (p *interp) resetCore() { p.scanner = nil for k := range p.scanners { diff --git a/interp/newexecute_test.go b/interp/newexecute_test.go index 32d9f0c0..dd6088e2 100644 --- a/interp/newexecute_test.go +++ b/interp/newexecute_test.go @@ -6,6 +6,7 @@ import ( "bytes" "context" "errors" + "math" "strings" "testing" "time" @@ -110,6 +111,33 @@ func TestResetRand(t *testing.T) { } } +func TestGetArrayValue(t *testing.T) { + interpreter := newInterp(t, ` +BEGIN { Arr["key"]; f(); g(Arr) } +function f() { Arr["hello"]="world" } +function g(arr) { arr["a"]=1.23 }`) + _, err := interpreter.Execute(nil) + if err != nil { + t.Fatalf("error executing: %v", err) + } + arr := interpreter.Array("Arr") + if len(arr) != 3 { + t.Errorf("expected length 3, got %d", len(arr)) + } + if arr["key"] != "" { + t.Errorf("expected value \"\", got %q", arr["key"]) + } + if arr["hello"] != "world" { + t.Errorf("expected value \"world\", got %q", arr["hello"]) + } + if math.Abs(arr["a"].(float64)-1.23) > 1e-9 { + t.Errorf("expected value 1.23, got %f", arr["a"]) + } + if interpreter.Array("NonExistent") != nil { + t.Errorf("non existent name must resolve to nil") + } +} + func TestExecuteContextNoError(t *testing.T) { interpreter := newInterp(t, `BEGIN {}`) _, err := interpreter.ExecuteContext(context.Background(), nil) diff --git a/lexer/lexer.go b/lexer/lexer.go index dc3a48dd..e5408de7 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -9,6 +9,7 @@ package lexer import ( "errors" + "fmt" ) // Lexer tokenizes a byte string of AWK source code. Use NewLexer to @@ -32,6 +33,11 @@ type Position struct { Column int } +// String returns the position in "line:col" format. +func (p Position) String() string { + return fmt.Sprintf("%d:%d", p.Line, p.Column) +} + // NewLexer creates a new lexer that will tokenize the given source // code. See the module-level example for a working example. func NewLexer(src []byte) *Lexer { diff --git a/parser/parser.go b/parser/parser.go index c3b13350..e8fd6d33 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -90,7 +90,6 @@ func ParseProgram(src []byte, config *ParserConfig) (prog *Program, err error) { // Compile to virtual machine code prog.Compiled, err = compiler.Compile(&prog.ResolvedProgram) - return prog, err } @@ -230,6 +229,7 @@ func (p *parser) stmtsBrace() ast.Stmts { // Parse a "simple" statement (eg: allowed in a for loop init clause). func (p *parser) simpleStmt() ast.Stmt { + startPos := p.pos switch p.tok { case PRINT, PRINTF: op := p.tok @@ -250,12 +250,12 @@ func (p *parser) simpleStmt() ast.Stmt { dest = p.expr() } if op == PRINT { - return &ast.PrintStmt{args, redirect, dest} + return &ast.PrintStmt{args, redirect, dest, startPos, p.pos} } else { if len(args) == 0 { panic(p.errorf("expected printf args, got none")) } - return &ast.PrintfStmt{args, redirect, dest} + return &ast.PrintfStmt{args, redirect, dest, startPos, p.pos} } case DELETE: p.next() @@ -270,11 +270,11 @@ func (p *parser) simpleStmt() ast.Stmt { } p.expect(RBRACKET) } - return &ast.DeleteStmt{ref, index} + return &ast.DeleteStmt{ref, index, startPos, p.pos} case IF, FOR, WHILE, DO, BREAK, CONTINUE, NEXT, EXIT, RETURN: panic(p.errorf("expected print/printf, delete, or expression")) default: - return &ast.ExprStmt{p.expr()} + return &ast.ExprStmt{p.expr(), startPos, p.pos} } } @@ -284,6 +284,7 @@ func (p *parser) stmt() ast.Stmt { p.next() } var s ast.Stmt + startPos := p.pos switch p.tok { case IF: p.next() @@ -291,6 +292,7 @@ func (p *parser) stmt() ast.Stmt { cond := p.expr() p.expect(RPAREN) p.optionalNewlines() + bodyStart := p.pos body := p.stmts() p.optionalNewlines() var elseBody ast.Stmts @@ -299,7 +301,7 @@ func (p *parser) stmt() ast.Stmt { p.optionalNewlines() elseBody = p.stmts() } - s = &ast.IfStmt{cond, body, elseBody} + s = &ast.IfStmt{cond, bodyStart, body, elseBody, startPos, p.pos} case FOR: // Parse for statement, either "for in" or C-like for loop. // @@ -333,8 +335,9 @@ func (p *parser) stmt() ast.Stmt { if !ok { panic(p.errorf("expected 'for (var in array) ...'")) } + bodyStart := p.pos body := p.loopStmts() - s = &ast.ForInStmt{varExpr, inExpr.Array, body} + s = &ast.ForInStmt{varExpr, inExpr.Array, bodyStart, body, startPos, p.pos} } else { // Match: for ([pre]; [cond]; [post]) body p.expect(SEMICOLON) @@ -351,8 +354,9 @@ func (p *parser) stmt() ast.Stmt { } p.expect(RPAREN) p.optionalNewlines() + bodyStart := p.pos body := p.loopStmts() - s = &ast.ForStmt{pre, cond, post, body} + s = &ast.ForStmt{pre, cond, post, bodyStart, body, startPos, p.pos} } case WHILE: p.next() @@ -360,8 +364,9 @@ func (p *parser) stmt() ast.Stmt { cond := p.expr() p.expect(RPAREN) p.optionalNewlines() + bodyStart := p.pos body := p.loopStmts() - s = &ast.WhileStmt{cond, body} + s = &ast.WhileStmt{cond, bodyStart, body, startPos, p.pos} case DO: p.next() p.optionalNewlines() @@ -370,32 +375,32 @@ func (p *parser) stmt() ast.Stmt { p.expect(LPAREN) cond := p.expr() p.expect(RPAREN) - s = &ast.DoWhileStmt{body, cond} + s = &ast.DoWhileStmt{body, cond, startPos, p.pos} case BREAK: if p.loopDepth == 0 { panic(p.errorf("break must be inside a loop body")) } p.next() - s = &ast.BreakStmt{} + s = &ast.BreakStmt{startPos, p.pos} case CONTINUE: if p.loopDepth == 0 { panic(p.errorf("continue must be inside a loop body")) } p.next() - s = &ast.ContinueStmt{} + s = &ast.ContinueStmt{startPos, p.pos} case NEXT: if !p.inAction && p.funcName == "" { panic(p.errorf("next can't be inside BEGIN or END")) } p.next() - s = &ast.NextStmt{} + s = &ast.NextStmt{startPos, p.pos} case EXIT: p.next() var status ast.Expr if !p.matches(NEWLINE, SEMICOLON, RBRACE) { status = p.expr() } - s = &ast.ExitStmt{status} + s = &ast.ExitStmt{status, startPos, p.pos} case RETURN: if p.funcName == "" { panic(p.errorf("return must be inside a function")) @@ -405,10 +410,10 @@ func (p *parser) stmt() ast.Stmt { if !p.matches(NEWLINE, SEMICOLON, RBRACE) { value = p.expr() } - s = &ast.ReturnStmt{value} + s = &ast.ReturnStmt{value, startPos, p.pos} case LBRACE: body := p.stmtsBrace() - s = &ast.BlockStmt{body} + s = &ast.BlockStmt{body, startPos, p.pos} default: s = p.simpleStmt() } diff --git a/parser/parser_test.go b/parser/parser_test.go index ee442d45..bf0b5abd 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -8,6 +8,7 @@ import ( "strings" "testing" + "github.com/benhoyt/goawk/internal/ast" "github.com/benhoyt/goawk/parser" ) @@ -217,6 +218,107 @@ func TestResolveLargeCallGraph(t *testing.T) { } } +func TestPositions(t *testing.T) { + source := strings.TrimSpace(` +function AddNums(n, sum,i) { + sum = 0 + if (n%2 == 0) { + for (i = 0; i < n; i++) { + sum += i * 2 + } + } else if (n%3 == 0) { + print "Divides by 3" + } else { + sum = -1 + } + while(1) { do { print 123 } while(1) } + return sum +}`) + expectedAst := strings.TrimSpace(` +*ast.Program + *ast.Function + *ast.ExprStmt [2:3-2:10) + *ast.AssignExpr + *ast.VarExpr + *ast.NumExpr + *ast.IfStmt [3:3-12:3) + *ast.BinaryExpr + *ast.BinaryExpr + *ast.VarExpr + *ast.NumExpr + *ast.NumExpr + *ast.ForStmt [4:5-6:6) + *ast.ExprStmt [4:10-4:15) + *ast.AssignExpr + *ast.VarExpr + *ast.NumExpr + *ast.BinaryExpr + *ast.VarExpr + *ast.VarExpr + *ast.ExprStmt [4:24-4:27) + *ast.IncrExpr + *ast.VarExpr + *ast.ExprStmt [5:7-5:19) + *ast.AugAssignExpr + *ast.VarExpr + *ast.BinaryExpr + *ast.VarExpr + *ast.NumExpr + *ast.IfStmt [7:10-11:4) + *ast.BinaryExpr + *ast.BinaryExpr + *ast.VarExpr + *ast.NumExpr + *ast.NumExpr + *ast.PrintStmt [8:5-8:25) + *ast.StrExpr + *ast.ExprStmt [10:5-10:13) + *ast.AssignExpr + *ast.VarExpr + *ast.UnaryExpr + *ast.NumExpr + *ast.WhileStmt [12:3-12:41) + *ast.NumExpr + *ast.DoWhileStmt [12:14-12:40) + *ast.PrintStmt [12:19-12:29) + *ast.NumExpr + *ast.NumExpr + *ast.ReturnStmt [13:3-13:13) + *ast.VarExpr +`) + prog, err := parser.ParseProgram([]byte(source), nil) + if err != nil { + t.Fatalf("error parsing program: %v", err) + } + code := &code{} + ast.Walk(code, &prog.ResolvedProgram.Program) + result := strings.TrimSpace(code.buf.String()) + if expectedAst != result { + t.Fatalf("Wrong AST and/or positions:\n%s", result) + } +} + +type code struct { + indent int + buf strings.Builder +} + +func (c *code) Visit(node ast.Node) ast.Visitor { + indent := strings.Repeat(" ", c.indent) + if node != nil { + c.buf.WriteString(indent) + c.buf.WriteString(fmt.Sprintf("%T", node)) + if stmt, ok := node.(ast.Stmt); ok { + c.buf.WriteString(fmt.Sprintf(" [%s-%s)", stmt.StartPos(), stmt.EndPos())) + } + c.buf.WriteString("\n") + c.indent++ + } else { + c.indent-- + } + return c +} + func Example_valid() { prog, err := parser.ParseProgram([]byte("$0 { print $1 }"), nil) if err != nil { diff --git a/testdata/cover/a1.awk b/testdata/cover/a1.awk new file mode 100644 index 00000000..65315ca1 --- /dev/null +++ b/testdata/cover/a1.awk @@ -0,0 +1,11 @@ +BEGIN { + print "hello" + callF() + callF() + callF() + exit 0 # this will call END +} +function callF(){ + print "world" +} +END{ print "END" } diff --git a/testdata/cover/a1_a2_covermode_count.awk b/testdata/cover/a1_a2_covermode_count.awk new file mode 100644 index 00000000..67b187e9 --- /dev/null +++ b/testdata/cover/a1_a2_covermode_count.awk @@ -0,0 +1,33 @@ +BEGIN { + __COVER["1"]++ + print "hello" + callF() + callF() + callF() + exit 0 +} + +BEGIN { + __COVER["3"]++ + if (1) { + __COVER["2"]++ + print "hello" + print "world" + } + __COVER["5"]++ + print "end" + for (i = 0; i < 7; i++) { + __COVER["4"]++ + print i + } +} + +END { + __COVER["6"]++ + print "END" +} + +function callF() { + __COVER["7"]++ + print "world" +} \ No newline at end of file diff --git a/testdata/cover/a1_annotation_data.txt b/testdata/cover/a1_annotation_data.txt new file mode 100644 index 00000000..df0229bb --- /dev/null +++ b/testdata/cover/a1_annotation_data.txt @@ -0,0 +1,3 @@ +1 a1.awk 2:3-6:30 5 +2 a1.awk 11:6-11:18 1 +3 a1.awk 9:3-9:16 1 \ No newline at end of file diff --git a/testdata/cover/a1_covermode_set.awk b/testdata/cover/a1_covermode_set.awk new file mode 100644 index 00000000..09e052a7 --- /dev/null +++ b/testdata/cover/a1_covermode_set.awk @@ -0,0 +1,18 @@ +BEGIN { + __COVER["1"] = 1 + print "hello" + callF() + callF() + callF() + exit 0 +} + +END { + __COVER["2"] = 1 + print "END" +} + +function callF() { + __COVER["3"] = 1 + print "world" +} \ No newline at end of file diff --git a/testdata/cover/a2.awk b/testdata/cover/a2.awk new file mode 100644 index 00000000..13348931 --- /dev/null +++ b/testdata/cover/a2.awk @@ -0,0 +1,8 @@ +BEGIN { + if (1) { + print "hello" + print "world" + } + print "end" + for (i=0; i<7; i++) print i +} \ No newline at end of file diff --git a/testdata/cover/a2_annotation_data.txt b/testdata/cover/a2_annotation_data.txt new file mode 100644 index 00000000..db6043ad --- /dev/null +++ b/testdata/cover/a2_annotation_data.txt @@ -0,0 +1,4 @@ +1 a2.awk 3:5-4:18 2 +2 a2.awk 2:3-2:10 1 +3 a2.awk 7:23-7:30 1 +4 a2.awk 6:3-7:23 2 \ No newline at end of file diff --git a/testdata/cover/a2_covermode_count.awk b/testdata/cover/a2_covermode_count.awk new file mode 100644 index 00000000..26c3ce8d --- /dev/null +++ b/testdata/cover/a2_covermode_count.awk @@ -0,0 +1,14 @@ +BEGIN { + __COVER["2"]++ + if (1) { + __COVER["1"]++ + print "hello" + print "world" + } + __COVER["4"]++ + print "end" + for (i = 0; i < 7; i++) { + __COVER["3"]++ + print i + } +} \ No newline at end of file diff --git a/testdata/cover/a3.awk b/testdata/cover/a3.awk new file mode 100644 index 00000000..350c1b84 --- /dev/null +++ b/testdata/cover/a3.awk @@ -0,0 +1,22 @@ +BEGIN { + if (1) { + for (i=0; i<10; i++) { + print i + while (1) { + do { + for (j in A) { + print j + if (2) { + print 2 + { + if (3) print 3 + } + } else { + continue + } + } + } while(1) + } + } + } +} \ No newline at end of file diff --git a/testdata/cover/a3_annotation_data.txt b/testdata/cover/a3_annotation_data.txt new file mode 100644 index 00000000..9cf58720 --- /dev/null +++ b/testdata/cover/a3_annotation_data.txt @@ -0,0 +1,10 @@ +1 a3.awk 12:24-12:31 1 +2 a3.awk 12:17-12:24 1 +3 a3.awk 10:15-13:16 2 +4 a3.awk 15:15-15:23 1 +5 a3.awk 8:13-9:20 2 +6 a3.awk 7:11-7:24 1 +7 a3.awk 6:9-18:19 1 +8 a3.awk 4:7-5:17 2 +9 a3.awk 3:5-3:26 1 +10 a3.awk 2:3-2:10 1 \ No newline at end of file diff --git a/testdata/cover/a3_covermode_set.awk b/testdata/cover/a3_covermode_set.awk new file mode 100644 index 00000000..f5da5c8f --- /dev/null +++ b/testdata/cover/a3_covermode_set.awk @@ -0,0 +1,34 @@ +BEGIN { + __COVER["10"] = 1 + if (1) { + __COVER["9"] = 1 + for (i = 0; i < 10; i++) { + __COVER["8"] = 1 + print i + while (1) { + __COVER["7"] = 1 + do { + __COVER["6"] = 1 + for (j in A) { + __COVER["5"] = 1 + print j + if (2) { + __COVER["3"] = 1 + print 2 + { + __COVER["2"] = 1 + if (3) { + __COVER["1"] = 1 + print 3 + } + } + } else { + __COVER["4"] = 1 + continue + } + } + } while (1) + } + } + } +} \ No newline at end of file diff --git a/testdata/cover/test_1file2runs_set.cov b/testdata/cover/test_1file2runs_set.cov new file mode 100644 index 00000000..0a9c772c --- /dev/null +++ b/testdata/cover/test_1file2runs_set.cov @@ -0,0 +1,7 @@ +mode: set +a1.awk:2.3,6.30 5 1 +a1.awk:11.6,11.18 1 1 +a1.awk:9.3,9.16 1 1 +a1.awk:2.3,6.30 5 1 +a1.awk:11.6,11.18 1 1 +a1.awk:9.3,9.16 1 1 diff --git a/testdata/cover/test_1file2runs_set_truncated.cov b/testdata/cover/test_1file2runs_set_truncated.cov new file mode 100644 index 00000000..cdd17b48 --- /dev/null +++ b/testdata/cover/test_1file2runs_set_truncated.cov @@ -0,0 +1,4 @@ +mode: set +a1.awk:2.3,6.30 5 1 +a1.awk:11.6,11.18 1 1 +a1.awk:9.3,9.16 1 1 diff --git a/testdata/cover/test_2file2runs_count.cov b/testdata/cover/test_2file2runs_count.cov new file mode 100644 index 00000000..7a6e5a95 --- /dev/null +++ b/testdata/cover/test_2file2runs_count.cov @@ -0,0 +1,15 @@ +mode: count +a2.awk:3.5,4.18 2 1 +a2.awk:2.3,2.10 1 1 +a2.awk:7.23,7.30 1 7 +a2.awk:6.3,7.23 2 1 +a1.awk:2.3,6.30 5 1 +a1.awk:11.6,11.18 1 1 +a1.awk:9.3,9.16 1 3 +a2.awk:3.5,4.18 2 1 +a2.awk:2.3,2.10 1 1 +a2.awk:7.23,7.30 1 7 +a2.awk:6.3,7.23 2 1 +a1.awk:2.3,6.30 5 1 +a1.awk:11.6,11.18 1 1 +a1.awk:9.3,9.16 1 3 \ No newline at end of file diff --git a/testdata/cover/test_2file2runs_count_truncated.cov b/testdata/cover/test_2file2runs_count_truncated.cov new file mode 100644 index 00000000..dbce8d09 --- /dev/null +++ b/testdata/cover/test_2file2runs_count_truncated.cov @@ -0,0 +1,8 @@ +mode: count +a2.awk:3.5,4.18 2 1 +a2.awk:2.3,2.10 1 1 +a2.awk:7.23,7.30 1 7 +a2.awk:6.3,7.23 2 1 +a1.awk:2.3,6.30 5 1 +a1.awk:11.6,11.18 1 1 +a1.awk:9.3,9.16 1 3 \ No newline at end of file diff --git a/testdata/cover/test_a2a1_count.cov b/testdata/cover/test_a2a1_count.cov new file mode 100644 index 00000000..dbce8d09 --- /dev/null +++ b/testdata/cover/test_a2a1_count.cov @@ -0,0 +1,8 @@ +mode: count +a2.awk:3.5,4.18 2 1 +a2.awk:2.3,2.10 1 1 +a2.awk:7.23,7.30 1 7 +a2.awk:6.3,7.23 2 1 +a1.awk:2.3,6.30 5 1 +a1.awk:11.6,11.18 1 1 +a1.awk:9.3,9.16 1 3 \ No newline at end of file diff --git a/testdata/cover/test_a2a1_set.cov b/testdata/cover/test_a2a1_set.cov new file mode 100644 index 00000000..74ff79b3 --- /dev/null +++ b/testdata/cover/test_a2a1_set.cov @@ -0,0 +1,8 @@ +mode: set +a2.awk:3.5,4.18 2 1 +a2.awk:2.3,2.10 1 1 +a2.awk:7.23,7.30 1 1 +a2.awk:6.3,7.23 2 1 +a1.awk:2.3,6.30 5 1 +a1.awk:11.6,11.18 1 1 +a1.awk:9.3,9.16 1 1 \ No newline at end of file diff --git a/testdata/cover/test_count.cov b/testdata/cover/test_count.cov new file mode 100644 index 00000000..1df21ae0 --- /dev/null +++ b/testdata/cover/test_count.cov @@ -0,0 +1,4 @@ +mode: count +a1.awk:2.3,6.30 5 1 +a1.awk:11.6,11.18 1 1 +a1.awk:9.3,9.16 1 3 diff --git a/testdata/cover/test_set.cov b/testdata/cover/test_set.cov new file mode 100644 index 00000000..cdd17b48 --- /dev/null +++ b/testdata/cover/test_set.cov @@ -0,0 +1,4 @@ +mode: set +a1.awk:2.3,6.30 5 1 +a1.awk:11.6,11.18 1 1 +a1.awk:9.3,9.16 1 1