diff --git a/cmd/eval_test.go b/cmd/eval_test.go index b1e3cace64..495abc5f27 100755 --- a/cmd/eval_test.go +++ b/cmd/eval_test.go @@ -131,13 +131,19 @@ func TestEvalWithProfiler(t *testing.T) { files := map[string]string{ "x.rego": `package x -p = 1`, +p { + a := 1 + b := 2 + c := 3 + x = a + b * c +}`, } test.WithTempFS(files, func(path string) { params := newEvalCommandParams() params.profile = true + params.profileCriteria = newrepeatedStringFlag([]string{"line"}) params.dataPaths = newrepeatedStringFlag([]string{path}) var buf bytes.Buffer @@ -156,6 +162,29 @@ p = 1`, if len(output.Profile) == 0 { t.Fatal("Expected profile output to be non-empty") } + + expectedNumEval := []int{3, 1, 1, 1, 1} + expectedNumRedo := []int{3, 1, 1, 1, 1} + expectedRow := []int{7, 6, 5, 4, 1} + expectedNumGenExpr := []int{3, 1, 1, 1, 1} + + for idx, actualExprStat := range output.Profile { + if actualExprStat.NumEval != expectedNumEval[idx] { + t.Fatalf("Index %v: Expected number of evals %v but got %v", idx, expectedNumEval[idx], actualExprStat.NumEval) + } + + if actualExprStat.NumRedo != expectedNumRedo[idx] { + t.Fatalf("Index %v: Expected number of redos %v but got %v", idx, expectedNumRedo[idx], actualExprStat.NumRedo) + } + + if actualExprStat.Location.Row != expectedRow[idx] { + t.Fatalf("Index %v: Expected row %v but got %v", idx, expectedRow[idx], actualExprStat.Location.Row) + } + + if actualExprStat.NumGenExpr != expectedNumGenExpr[idx] { + t.Fatalf("Index %v: Expected number of generated expressions %v but got %v", idx, expectedNumGenExpr[idx], actualExprStat.NumGenExpr) + } + } }) } diff --git a/docs/content/policy-performance.md b/docs/content/policy-performance.md index 025132571a..127895215c 100644 --- a/docs/content/policy-performance.md +++ b/docs/content/policy-performance.md @@ -387,24 +387,65 @@ why policy evaluation is slow. The `opa eval` command provides the following profiler options: -| Option | Detail | Default | -| --- | --- | --- | -| `--profile` | Enables expression profiling and outputs profiler results. | off | -| `--profile-sort` | Criteria to sort the expression profiling results. This options implies `--profile`. | total_time_ns => num_eval => num_redo => file => line | -| `--profile-limit` | Desired number of profiling results sorted on the given criteria. This options implies `--profile`. | 10 | -| `--count` | Desired number of evaluations that profiling metrics are to be captured for. With `--format=pretty`, the output will contain min, max, mean and the 90th and 99th percentile. All collected percentiles can be found in the JSON output. | 1 | +| Option | Detail | Default | +| --- | --- |-----------------------------------------------------------------------| +| `--profile` | Enables expression profiling and outputs profiler results. | off | +| `--profile-sort` | Criteria to sort the expression profiling results. This options implies `--profile`. | total_time_ns => num_eval => num_redo => num_gen_expr => file => line | +| `--profile-limit` | Desired number of profiling results sorted on the given criteria. This options implies `--profile`. | 10 | +| `--count` | Desired number of evaluations that profiling metrics are to be captured for. With `--format=pretty`, the output will contain min, max, mean and the 90th and 99th percentile. All collected percentiles can be found in the JSON output. | 1 | #### Sort criteria for the profile results * `total_time_ns` - Results are displayed is decreasing order of *expression evaluation time* * `num_eval` - Results are displayed is decreasing order of *number of times an expression is evaluated* * `num_redo` - Results are displayed is decreasing order of *number of times an expression is re-evaluated(redo)* +* `num_gen_expr` - Results are displayed is decreasing order of *number of generated expressions* * `file` - Results are sorted in reverse alphabetical order based on the *rego source filename* * `line` - Results are displayed is decreasing order of *expression line number* in the source file When the sort criteria is not provided `total_time_ns` has the **highest** priority while `line` has the **lowest**. +The `num_gen_expr` represents the number of expressions generated for a given statement on a particular line. For example, +let's take the following policy: + +```rego +package test + +p { + a := 1 + b := 2 + c := 3 + x = a + b * c +} +``` + +If we profile the above policy we would get something like the following output: + +```ruby ++----------+----------+----------+--------------+-------------+ +| TIME | NUM EVAL | NUM REDO | NUM GEN EXPR | LOCATION | ++----------+----------+----------+--------------+-------------+ +| 20.291µs | 3 | 3 | 3 | test.rego:7 | +| 1µs | 1 | 1 | 1 | test.rego:6 | +| 2.333µs | 1 | 1 | 1 | test.rego:5 | +| 6.333µs | 1 | 1 | 1 | test.rego:4 | +| 84.75µs | 1 | 1 | 1 | data | ++----------+----------+----------+--------------+-------------+ +``` + +The first entry indicates that line `test.rego:7` has a `EVAL/REDO` count of `3`. If we look at the expression on line `test.rego:7` +ie `x = a + b * c` it's not immediately clear why this line has a `EVAL/REDO` count of `3`. But we also notice that there +are `3` generated expressions (ie. `NUM GEN EXPR`) at line `test.rego:7`. This is because the compiler rewrites the above policy to +something like below: + +`p = true { __local0__ = 1; __local1__ = 2; __local2__ = 3; mul(__local1__, __local2__, __local3__); plus(__local0__, __local3__, __local4__); x = __local4__ }` + +And that line `test.rego:7` is rewritten to `mul(__local1__, __local2__, __local3__); plus(__local0__, __local3__, __local4__); x = __local4__` which +results in a `NUM GEN EXPR` count of `3`. Hence the `NUM GEN EXPR` count can help to better understand the `EVAL/REDO` counts +for a given expression and also provide more clarity into the profile results and how policy evaluation works. + + #### Example Policy The different profiling examples shown later on this page use the below @@ -483,20 +524,20 @@ opa eval --data rbac.rego --profile --format=pretty 'data.rbac.allow' ```ruby false -+----------+----------+----------+-----------------+ -| TIME | NUM EVAL | NUM REDO | LOCATION | -+----------+----------+----------+-----------------+ -| 47.148µs | 1 | 1 | data.rbac.allow | -| 28.965µs | 1 | 1 | rbac.rego:11 | -| 24.384µs | 1 | 1 | rbac.rego:41 | -| 23.064µs | 2 | 1 | rbac.rego:47 | -| 15.525µs | 1 | 1 | rbac.rego:38 | -| 14.137µs | 1 | 2 | rbac.rego:46 | -| 13.927µs | 1 | 0 | rbac.rego:42 | -| 13.568µs | 1 | 1 | rbac.rego:55 | -| 12.982µs | 1 | 0 | rbac.rego:56 | -| 12.763µs | 1 | 2 | rbac.rego:52 | -+----------+----------+----------+-----------------+ ++----------+----------+----------+--------------+-----------------+ +| TIME | NUM EVAL | NUM REDO | NUM GEN EXPR | LOCATION | ++----------+----------+----------+--------------+-----------------+ +| 47.148µs | 1 | 1 | 1 | data.rbac.allow | +| 28.965µs | 1 | 1 | 1 | rbac.rego:11 | +| 24.384µs | 1 | 1 | 1 | rbac.rego:41 | +| 23.064µs | 2 | 1 | 1 | rbac.rego:47 | +| 15.525µs | 1 | 1 | 1 | rbac.rego:38 | +| 14.137µs | 1 | 2 | 1 | rbac.rego:46 | +| 13.927µs | 1 | 0 | 1 | rbac.rego:42 | +| 13.568µs | 1 | 1 | 1 | rbac.rego:55 | +| 12.982µs | 1 | 0 | 1 | rbac.rego:56 | +| 12.763µs | 1 | 2 | 1 | rbac.rego:52 | ++----------+----------+----------+--------------+-----------------+ +------------------------------+----------+ | METRIC | VALUE | @@ -532,20 +573,20 @@ false | timer_rego_query_eval_ns | 161812 | 1198092 | 637754 | 1.1846622e+06 | 1.198092e+06 | | timer_rego_query_parse_ns | 6078 | 6078 | 6078 | 6078 | 6078 | +------------------------------+---------+----------+---------------+----------------+---------------+ -+----------+-------------+-------------+-------------+-------------+----------+----------+-----------------+ -| MIN | MAX | MEAN | 90% | 99% | NUM EVAL | NUM REDO | LOCATION | -+----------+-------------+-------------+-------------+-------------+----------+----------+-----------------+ -| 43.875µs | 26.135469ms | 11.494512ms | 25.746215ms | 26.135469ms | 1 | 1 | data.rbac.allow | -| 21.478µs | 211.461µs | 98.102µs | 205.72µs | 211.461µs | 1 | 1 | rbac.rego:13 | -| 19.652µs | 123.537µs | 73.161µs | 122.75µs | 123.537µs | 1 | 1 | rbac.rego:40 | -| 12.303µs | 117.277µs | 61.59µs | 116.733µs | 117.277µs | 2 | 1 | rbac.rego:50 | -| 12.224µs | 93.214µs | 51.289µs | 92.217µs | 93.214µs | 1 | 1 | rbac.rego:44 | -| 5.561µs | 84.121µs | 43.002µs | 83.469µs | 84.121µs | 1 | 1 | rbac.rego:51 | -| 5.56µs | 71.712µs | 36.545µs | 71.158µs | 71.712µs | 1 | 0 | rbac.rego:45 | -| 4.958µs | 66.04µs | 33.161µs | 65.636µs | 66.04µs | 1 | 2 | rbac.rego:49 | -| 4.326µs | 65.836µs | 30.461µs | 65.083µs | 65.836µs | 1 | 1 | rbac.rego:6 | -| 3.948µs | 43.399µs | 24.167µs | 43.055µs | 43.399µs | 1 | 2 | rbac.rego:55 | -+----------+-------------+-------------+-------------+-------------+----------+----------+-----------------+ ++----------+-------------+-------------+-------------+-------------+----------+----------+--------------+------------------+ +| MIN | MAX | MEAN | 90% | 99% | NUM EVAL | NUM REDO | NUM GEN EXPR | LOCATION | ++----------+-------------+-------------+-------------+-------------+----------+----------+--------------+------------------+ +| 43.875µs | 26.135469ms | 11.494512ms | 25.746215ms | 26.135469ms | 1 | 1 | 1 | data.rbac.allow | +| 21.478µs | 211.461µs | 98.102µs | 205.72µs | 211.461µs | 1 | 1 | 1 | rbac.rego:13 | +| 19.652µs | 123.537µs | 73.161µs | 122.75µs | 123.537µs | 1 | 1 | 1 | rbac.rego:40 | +| 12.303µs | 117.277µs | 61.59µs | 116.733µs | 117.277µs | 2 | 1 | 1 | rbac.rego:50 | +| 12.224µs | 93.214µs | 51.289µs | 92.217µs | 93.214µs | 1 | 1 | 1 | rbac.rego:44 | +| 5.561µs | 84.121µs | 43.002µs | 83.469µs | 84.121µs | 1 | 1 | 1 | rbac.rego:51 | +| 5.56µs | 71.712µs | 36.545µs | 71.158µs | 71.712µs | 1 | 0 | 1 | rbac.rego:45 | +| 4.958µs | 66.04µs | 33.161µs | 65.636µs | 66.04µs | 1 | 2 | 1 | rbac.rego:49 | +| 4.326µs | 65.836µs | 30.461µs | 65.083µs | 65.836µs | 1 | 1 | 1 | rbac.rego:6 | +| 3.948µs | 43.399µs | 24.167µs | 43.055µs | 43.399µs | 1 | 2 | 1 | rbac.rego:55 | ++----------+-------------+-------------+-------------+-------------+----------+----------+--------------+------------------+ ``` ##### Example: Display top `5` profile results @@ -557,15 +598,15 @@ opa eval --data rbac.rego --profile-limit 5 --format=pretty 'data.rbac.allow' **Sample Output** ```ruby -+----------+----------+----------+-----------------+ -| TIME | NUM EVAL | NUM REDO | LOCATION | -+----------+----------+----------+-----------------+ -| 46.329µs | 1 | 1 | data.rbac.allow | -| 26.656µs | 1 | 1 | rbac.rego:11 | -| 24.206µs | 2 | 1 | rbac.rego:47 | -| 23.235µs | 1 | 1 | rbac.rego:41 | -| 18.242µs | 1 | 1 | rbac.rego:38 | -+----------+----------+----------+-----------------+ ++----------+----------+----------+--------------+-----------------+ +| TIME | NUM EVAL | NUM REDO | NUM GEN EXPR | LOCATION | ++----------+----------+----------+--------------+-----------------+ +| 46.329µs | 1 | 1 | 1 | data.rbac.allow | +| 26.656µs | 1 | 1 | 1 | rbac.rego:11 | +| 24.206µs | 2 | 1 | 1 | rbac.rego:47 | +| 23.235µs | 1 | 1 | 1 | rbac.rego:41 | +| 18.242µs | 1 | 1 | 1 | rbac.rego:38 | ++----------+----------+----------+--------------+-----------------+ ``` The profile results are sorted on the default sort criteria. @@ -580,15 +621,15 @@ opa eval --data rbac.rego --profile-limit 5 --profile-sort num_eval --format=pr **Sample Profile Output** ```ruby -+----------+----------+----------+-----------------+ -| TIME | NUM EVAL | NUM REDO | LOCATION | -+----------+----------+----------+-----------------+ -| 26.675µs | 2 | 1 | rbac.rego:47 | -| 9.274µs | 2 | 1 | rbac.rego:53 | -| 43.356µs | 1 | 1 | data.rbac.allow | -| 22.467µs | 1 | 1 | rbac.rego:41 | -| 22.425µs | 1 | 1 | rbac.rego:11 | -+----------+----------+----------+-----------------+ ++----------+----------+----------+--------------+-----------------+ +| TIME | NUM EVAL | NUM REDO | NUM GEN EXPR | LOCATION | ++----------+----------+----------+--------------+-----------------+ +| 26.675µs | 2 | 1 | 1 | rbac.rego:47 | +| 9.274µs | 2 | 1 | 1 | rbac.rego:53 | +| 43.356µs | 1 | 1 | 1 | data.rbac.allow | +| 22.467µs | 1 | 1 | 1 | rbac.rego:41 | +| 22.425µs | 1 | 1 | 1 | rbac.rego:11 | ++----------+----------+----------+--------------+-----------------+ ``` As seen from the above table, the results are arranged first in decreasing @@ -606,15 +647,15 @@ opa eval --data rbac.rego --profile-limit 5 --profile-sort num_eval,num_redo --f **Sample Profile Output** ```ruby -+----------+----------+----------+-----------------+ -| TIME | NUM EVAL | NUM REDO | LOCATION | -+----------+----------+----------+-----------------+ -| 22.892µs | 2 | 1 | rbac.rego:47 | -| 8.831µs | 2 | 1 | rbac.rego:53 | -| 13.767µs | 1 | 2 | rbac.rego:46 | -| 10.78µs | 1 | 2 | rbac.rego:52 | -| 42.338µs | 1 | 1 | data.rbac.allow | -+----------+----------+----------+-----------------+ ++----------+----------+----------+--------------+-----------------+ +| TIME | NUM EVAL | NUM REDO | NUM GEN EXPR | LOCATION | ++----------+----------+----------+--------------+-----------------+ +| 22.892µs | 2 | 1 | 1 | rbac.rego:47 | +| 8.831µs | 2 | 1 | 1 | rbac.rego:53 | +| 13.767µs | 1 | 2 | 1 | rbac.rego:46 | +| 10.78µs | 1 | 2 | 1 | rbac.rego:52 | +| 42.338µs | 1 | 1 | 1 | data.rbac.allow | ++----------+----------+----------+--------------+-----------------+ ``` As seen from the above table, result are first arranged based on *number of evaluations*, diff --git a/internal/presentation/presentation.go b/internal/presentation/presentation.go index 18c0e102a5..7a024d87f2 100644 --- a/internal/presentation/presentation.go +++ b/internal/presentation/presentation.go @@ -496,14 +496,16 @@ func prettyAggregatedMetrics(w io.Writer, ms map[string]interface{}, limit int) func prettyProfile(w io.Writer, profile []profiler.ExprStats) error { tableProfile := generateTableProfile(w) + for _, rs := range profile { line := []string{} timeNs := time.Duration(rs.ExprTimeNs) * time.Nanosecond timeNsStr := timeNs.String() numEval := strconv.FormatInt(int64(rs.NumEval), 10) numRedo := strconv.FormatInt(int64(rs.NumRedo), 10) + numGenExpr := strconv.FormatInt(int64(rs.NumGenExpr), 10) loc := rs.Location.String() - line = append(line, timeNsStr, numEval, numRedo, loc) + line = append(line, timeNsStr, numEval, numRedo, numGenExpr, loc) tableProfile.Append(line) } if tableProfile.NumLines() > 0 { @@ -513,7 +515,7 @@ func prettyProfile(w io.Writer, profile []profiler.ExprStats) error { } func prettyAggregatedProfile(w io.Writer, profile []profiler.ExprStatsAggregated) error { - tableProfile := generateTableWithKeys(w, append(statKeys, "num eval", "num redo", "location")...) + tableProfile := generateTableWithKeys(w, append(statKeys, "num eval", "num redo", "num gen expr", "location")...) for _, rs := range profile { line := []string{} for _, k := range statKeys { @@ -526,8 +528,9 @@ func prettyAggregatedProfile(w io.Writer, profile []profiler.ExprStatsAggregated } numEval := strconv.FormatInt(int64(rs.NumEval), 10) numRedo := strconv.FormatInt(int64(rs.NumRedo), 10) + numGenExpr := strconv.FormatInt(int64(rs.NumGenExpr), 10) loc := rs.Location.String() - line = append(line, numEval, numRedo, loc) + line = append(line, numEval, numRedo, numGenExpr, loc) tableProfile.Append(line) } if tableProfile.NumLines() > 0 { @@ -611,7 +614,7 @@ func generateTableWithKeys(writer io.Writer, keys ...string) *tablewriter.Table } func generateTableProfile(writer io.Writer) *tablewriter.Table { - return generateTableWithKeys(writer, "Time", "Num Eval", "Num Redo", "Location") + return generateTableWithKeys(writer, "Time", "Num Eval", "Num Redo", "Num Gen Expr", "Location") } func populateTableMetrics(m metrics.Metrics, table *tablewriter.Table, prettyLimit int) { diff --git a/profiler/profiler.go b/profiler/profiler.go index fc6bc21b0f..62dfd2d38d 100644 --- a/profiler/profiler.go +++ b/profiler/profiler.go @@ -16,13 +16,15 @@ import ( // Profiler computes and reports on the time spent on expressions. type Profiler struct { - hits map[string]map[int]ExprStats - activeTimer time.Time - prevExpr exprInfo + hits map[string]map[int]ExprStats + hitsByExprIndex map[string]map[int]map[int]ExprStats + activeTimer time.Time + prevExpr exprInfo } // exprInfo stores information about an expression. type exprInfo struct { + index int location *ast.Location op topdown.Op } @@ -30,7 +32,8 @@ type exprInfo struct { // New returns a new Profiler object. func New() *Profiler { return &Profiler{ - hits: map[string]map[int]ExprStats{}, + hits: map[string]map[int]ExprStats{}, + hitsByExprIndex: map[string]map[int]map[int]ExprStats{}, } } @@ -52,9 +55,13 @@ func (p *Profiler) ReportByFile() Report { p.processLastExpr() report := Report{Files: map[string]*FileReport{}} + for file, hits := range p.hits { stats := []ExprStats{} - for _, stat := range hits { + for row, stat := range hits { + if entry, ok := p.hitsByExprIndex[file][row]; ok { + stat.NumGenExpr = len(entry) + } stats = append(stats, stat) } @@ -66,6 +73,7 @@ func (p *Profiler) ReportByFile() Report { } fr.Result = stats } + return report } @@ -73,10 +81,14 @@ func (p *Profiler) ReportByFile() Report { // criteria. If N <= 0, all the results based on the criteria are returned. func (p *Profiler) ReportTopNResults(numResults int, criteria []string) []ExprStats { p.processLastExpr() + stats := []ExprStats{} - for _, hits := range p.hits { - for _, stat := range hits { + for file, hits := range p.hits { + for row, stat := range hits { + if entry, ok := p.hitsByExprIndex[file][row]; ok { + stat.NumGenExpr = len(entry) + } stats = append(stats, stat) } } @@ -92,6 +104,9 @@ func (p *Profiler) ReportTopNResults(numResults int, criteria []string) []ExprSt allowedCriteria["num_redo"] = func(stat1, stat2 *ExprStats) bool { return stat1.NumRedo > stat2.NumRedo } + allowedCriteria["num_gen_expr"] = func(stat1, stat2 *ExprStats) bool { + return stat1.NumGenExpr > stat2.NumGenExpr + } allowedCriteria["file"] = func(stat1, stat2 *ExprStats) bool { return stat1.Location.File > stat2.Location.File } @@ -156,11 +171,14 @@ func (p *Profiler) processExpr(expr *ast.Expr, eventType topdown.Op) { p.prevExpr = exprInfo{ op: eventType, location: expr.Location, + index: expr.Index, } return } // record the profiler results for the previous expression + p.calculateHitsByExprIndex() + file := p.prevExpr.location.File hits, ok := p.hits[file] if !ok { @@ -190,16 +208,53 @@ func (p *Profiler) processExpr(expr *ast.Expr, eventType topdown.Op) { p.prevExpr = exprInfo{ op: eventType, location: expr.Location, + index: expr.Index, } } func (p *Profiler) processLastExpr() { expr := ast.Expr{ Location: p.prevExpr.location, + Index: p.prevExpr.index, } p.processExpr(&expr, p.prevExpr.op) } +func (p *Profiler) calculateHitsByExprIndex() { + file := p.prevExpr.location.File + hitsUnique, ok := p.hitsByExprIndex[file] + + if !ok { + hitsUnique = map[int]map[int]ExprStats{} + hitsUnique[p.prevExpr.location.Row] = map[int]ExprStats{p.prevExpr.index: getProfilerStats(p.prevExpr, p.activeTimer)} + p.hitsByExprIndex[file] = hitsUnique + } else { + row := p.prevExpr.location.Row + idx := p.prevExpr.index + + pStats, ok := hitsUnique[row] + if !ok { + hitsUnique[row] = map[int]ExprStats{idx: getProfilerStats(p.prevExpr, p.activeTimer)} + } else { + pStatsIdx, ok := pStats[idx] + if !ok { + hitsUnique[row][idx] = getProfilerStats(p.prevExpr, p.activeTimer) + } else { + pStatsIdx.ExprTimeNs += time.Since(p.activeTimer).Nanoseconds() + + switch p.prevExpr.op { + case topdown.EvalOp: + pStatsIdx.NumEval++ + case topdown.RedoOp: + pStatsIdx.NumRedo++ + } + + hitsUnique[row][idx] = pStatsIdx + } + } + } +} + func getProfilerStats(expr exprInfo, timer time.Time) ExprStats { profilerStats := ExprStats{} profilerStats.ExprTimeNs = time.Since(timer).Nanoseconds() @@ -219,6 +274,7 @@ type ExprStats struct { ExprTimeNs int64 `json:"total_time_ns"` NumEval int `json:"num_eval"` NumRedo int `json:"num_redo"` + NumGenExpr int `json:"num_gen_expr"` Location *ast.Location `json:"location"` } @@ -228,6 +284,7 @@ type ExprStatsAggregated struct { ExprTimeNsStats interface{} `json:"total_time_ns_stats"` NumEval int `json:"num_eval"` NumRedo int `json:"num_redo"` + NumGenExpr int `json:"num_gen_expr"` Location *ast.Location `json:"location"` } @@ -236,9 +293,10 @@ func aggregate(stats ...ExprStats) ExprStatsAggregated { return ExprStatsAggregated{} } res := ExprStatsAggregated{ - NumEval: stats[0].NumEval, - NumRedo: stats[0].NumRedo, - Location: stats[0].Location, + NumEval: stats[0].NumEval, + NumRedo: stats[0].NumRedo, + NumGenExpr: stats[0].NumGenExpr, + Location: stats[0].Location, } timeNs := make([]int64, 0, len(stats)) for _, s := range stats { diff --git a/profiler/profiler_test.go b/profiler/profiler_test.go index a7a54e110e..a706415960 100644 --- a/profiler/profiler_test.go +++ b/profiler/profiler_test.go @@ -23,6 +23,7 @@ func TestProfilerLargeArray(t *testing.T) { module := `package test foo { + p bar not baz bee @@ -44,7 +45,15 @@ baz { true false true -}` +} + +p { + a := 1 + b := 2 + c := 3 + x = a + b * c +} +` _, err := ast.ParseModule("test.rego", module) if err != nil { @@ -71,13 +80,14 @@ baz { t.Fatal("Expected file report for test.rego") } - if len(fr.Result) != 11 { - t.Fatalf("Expected file report length to be 11 instead got %v", len(fr.Result)) + if len(fr.Result) != 16 { + t.Fatalf("Expected file report length to be 16 instead got %v", len(fr.Result)) } - expectedNumEval := []int{1, 2, 1, 1, 1, 1633, 1, 1, 1, 1, 1} - expectedNumRedo := []int{1, 0, 0, 1, 1633, 0, 1, 1, 1, 1, 0} - expectedRow := []int{4, 5, 6, 10, 11, 12, 16, 17, 18, 22, 23} + expectedNumEval := []int{1, 1, 2, 1, 1, 1, 1633, 1, 1, 1, 1, 1, 1, 1, 1, 3} + expectedNumRedo := []int{1, 1, 0, 0, 1, 1633, 0, 1, 1, 1, 1, 0, 1, 1, 1, 3} + expectedRow := []int{4, 5, 6, 7, 11, 12, 13, 17, 18, 19, 23, 24, 29, 30, 31, 32} + expectedNumGenExpr := []int{1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3} for idx, actualExprStat := range fr.Result { if actualExprStat.NumEval != expectedNumEval[idx] { @@ -92,6 +102,9 @@ baz { t.Fatalf("Index %v: Expected row %v but got %v", idx, expectedRow[idx], actualExprStat.Location.Row) } + if actualExprStat.NumGenExpr != expectedNumGenExpr[idx] { + t.Fatalf("Index %v: Expected number of generated expressions %v but got %v", idx, expectedNumGenExpr[idx], actualExprStat.NumGenExpr) + } } } @@ -464,6 +477,7 @@ allowed_operations = [ expectedNumEval := []int{2, 1} expectedNumRedo := []int{2, 1} + expectedNumGenExpr := []int{1, 1} expectedLocation := []string{"???", "data.partial.__result__"} for idx, actualExprStat := range fr.Result { @@ -475,10 +489,13 @@ allowed_operations = [ t.Fatalf("Index %v: Expected number of redos %v but got %v", idx, expectedNumRedo[idx], actualExprStat.NumRedo) } + if actualExprStat.NumGenExpr != expectedNumGenExpr[idx] { + t.Fatalf("Index %v: Expected number of generated expressions %v but got %v", idx, expectedNumGenExpr[idx], actualExprStat.NumGenExpr) + } + if string(actualExprStat.Location.Text) != expectedLocation[idx] { t.Fatalf("Index %v: Expected location %v but got %v", idx, expectedLocation[idx], string(actualExprStat.Location.Text)) } - } }