github.com/rajeev159/opa@v0.45.0/topdown/trace_test.go (about)

     1  // Copyright 2016 The OPA Authors.  All rights reserved.
     2  // Use of this source code is governed by an Apache2
     3  // license that can be found in the LICENSE file.
     4  
     5  package topdown
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"fmt"
    11  	"reflect"
    12  	"strings"
    13  	"testing"
    14  
    15  	"github.com/open-policy-agent/opa/ast"
    16  	"github.com/open-policy-agent/opa/storage"
    17  	"github.com/open-policy-agent/opa/storage/inmem"
    18  	"github.com/open-policy-agent/opa/util"
    19  )
    20  
    21  func TestEventEqual(t *testing.T) {
    22  
    23  	a := ast.NewValueMap()
    24  	a.Put(ast.String("foo"), ast.Number("1"))
    25  	b := ast.NewValueMap()
    26  	b.Put(ast.String("foo"), ast.Number("2"))
    27  
    28  	tests := []struct {
    29  		a     *Event
    30  		b     *Event
    31  		equal bool
    32  	}{
    33  		{&Event{}, &Event{}, true},
    34  		{&Event{Op: EvalOp}, &Event{Op: EnterOp}, false},
    35  		{&Event{QueryID: 1}, &Event{QueryID: 2}, false},
    36  		{&Event{ParentID: 1}, &Event{ParentID: 2}, false},
    37  		{&Event{Node: ast.MustParseBody("true")}, &Event{Node: ast.MustParseBody("false")}, false},
    38  		{&Event{Node: ast.MustParseBody("true")[0]}, &Event{Node: ast.MustParseBody("false")[0]}, false},
    39  		{&Event{Node: ast.MustParseRule(`p = true { true }`)}, &Event{Node: ast.MustParseRule(`p = true { false }`)}, false},
    40  	}
    41  
    42  	for _, tc := range tests {
    43  		if tc.a.Equal(tc.b) != tc.equal {
    44  			var s string
    45  			if tc.equal {
    46  				s = "=="
    47  			} else {
    48  				s = "!="
    49  			}
    50  			t.Errorf("Expected %v %v %v", tc.a, s, tc.b)
    51  		}
    52  	}
    53  
    54  }
    55  
    56  func TestPrettyTrace(t *testing.T) {
    57  	module := `package test
    58  
    59  	p { q[x]; plus(x, 1, n) }
    60  	q[x] { x = data.a[_] }`
    61  
    62  	ctx := context.Background()
    63  	compiler := compileModules([]string{module})
    64  	data := loadSmallTestData()
    65  	store := inmem.NewFromObject(data)
    66  	txn := storage.NewTransactionOrDie(ctx, store)
    67  	defer store.Abort(ctx, txn)
    68  
    69  	tracer := NewBufferTracer()
    70  	query := NewQuery(ast.MustParseBody("data.test.p = _")).
    71  		WithCompiler(compiler).
    72  		WithStore(store).
    73  		WithTransaction(txn).
    74  		WithTracer(tracer)
    75  
    76  	_, err := query.Run(ctx)
    77  	if err != nil {
    78  		panic(err)
    79  	}
    80  
    81  	expected := `Enter data.test.p = _
    82  | Eval data.test.p = _
    83  | Index data.test.p (matched 1 rule, early exit)
    84  | Enter data.test.p
    85  | | Eval data.test.q[x]
    86  | | Index data.test.q (matched 1 rule)
    87  | | Enter data.test.q
    88  | | | Eval x = data.a[_]
    89  | | | Exit data.test.q
    90  | | Redo data.test.q
    91  | | | Redo x = data.a[_]
    92  | | | Exit data.test.q
    93  | | Redo data.test.q
    94  | | | Redo x = data.a[_]
    95  | | | Exit data.test.q
    96  | | Redo data.test.q
    97  | | | Redo x = data.a[_]
    98  | | | Exit data.test.q
    99  | | Redo data.test.q
   100  | | | Redo x = data.a[_]
   101  | | Eval plus(x, 1, n)
   102  | | Exit data.test.p early
   103  | Exit data.test.p = _
   104  Redo data.test.p = _
   105  | Redo data.test.p = _
   106  | Redo data.test.p
   107  | | Redo plus(x, 1, n)
   108  | | Redo data.test.q[x]
   109  `
   110  
   111  	var buf bytes.Buffer
   112  	PrettyTrace(&buf, *tracer)
   113  	compareBuffers(t, expected, buf.String())
   114  }
   115  
   116  func TestPrettyTraceWithLocation(t *testing.T) {
   117  	module := `package test
   118  
   119  	p { q[x]; plus(x, 1, n) }
   120  	q[x] { x = data.a[_] }`
   121  
   122  	ctx := context.Background()
   123  	compiler := compileModules([]string{module})
   124  	data := loadSmallTestData()
   125  	store := inmem.NewFromObject(data)
   126  	txn := storage.NewTransactionOrDie(ctx, store)
   127  	defer store.Abort(ctx, txn)
   128  
   129  	tracer := NewBufferTracer()
   130  	query := NewQuery(ast.MustParseBody("data.test.p = _")).
   131  		WithCompiler(compiler).
   132  		WithStore(store).
   133  		WithTransaction(txn).
   134  		WithTracer(tracer)
   135  
   136  	_, err := query.Run(ctx)
   137  	if err != nil {
   138  		panic(err)
   139  	}
   140  
   141  	expected := `query:1     Enter data.test.p = _
   142  query:1     | Eval data.test.p = _
   143  query:1     | Index data.test.p (matched 1 rule, early exit)
   144  query:3     | Enter data.test.p
   145  query:3     | | Eval data.test.q[x]
   146  query:3     | | Index data.test.q (matched 1 rule)
   147  query:4     | | Enter data.test.q
   148  query:4     | | | Eval x = data.a[_]
   149  query:4     | | | Exit data.test.q
   150  query:4     | | Redo data.test.q
   151  query:4     | | | Redo x = data.a[_]
   152  query:4     | | | Exit data.test.q
   153  query:4     | | Redo data.test.q
   154  query:4     | | | Redo x = data.a[_]
   155  query:4     | | | Exit data.test.q
   156  query:4     | | Redo data.test.q
   157  query:4     | | | Redo x = data.a[_]
   158  query:4     | | | Exit data.test.q
   159  query:4     | | Redo data.test.q
   160  query:4     | | | Redo x = data.a[_]
   161  query:3     | | Eval plus(x, 1, n)
   162  query:3     | | Exit data.test.p early
   163  query:1     | Exit data.test.p = _
   164  query:1     Redo data.test.p = _
   165  query:1     | Redo data.test.p = _
   166  query:3     | Redo data.test.p
   167  query:3     | | Redo plus(x, 1, n)
   168  query:3     | | Redo data.test.q[x]
   169  `
   170  
   171  	var buf bytes.Buffer
   172  	PrettyTraceWithLocation(&buf, *tracer)
   173  	compareBuffers(t, expected, buf.String())
   174  }
   175  
   176  func TestPrettyTraceWithLocationTruncatedPaths(t *testing.T) {
   177  	ctx := context.Background()
   178  
   179  	compiler := ast.MustCompileModules(map[string]string{
   180  		"authz_bundle/com/foo/bar/baz/qux/acme/corp/internal/authz/policies/abac/v1/beta/policy.rego": `package test
   181  
   182  		import data.utils.q
   183  
   184  		p = true { q[x]; plus(x, 1, n) }
   185  		`,
   186  		"authz_bundle/com/foo/bar/baz/qux/acme/corp/internal/authz/policies/utils/utils.rego": `package utils
   187  
   188  		q[x] { x = data.a[_] }
   189  		`,
   190  	})
   191  	data := loadSmallTestData()
   192  	store := inmem.NewFromObject(data)
   193  	txn := storage.NewTransactionOrDie(ctx, store)
   194  	defer store.Abort(ctx, txn)
   195  
   196  	tracer := NewBufferTracer()
   197  	query := NewQuery(ast.MustParseBody("data.test.p = _")).
   198  		WithCompiler(compiler).
   199  		WithStore(store).
   200  		WithTransaction(txn).
   201  		WithTracer(tracer)
   202  
   203  	_, err := query.Run(ctx)
   204  	if err != nil {
   205  		panic(err)
   206  	}
   207  
   208  	expected := `query:1                                                              Enter data.test.p = _
   209  query:1                                                              | Eval data.test.p = _
   210  query:1                                                              | Index data.test.p (matched 1 rule, early exit)
   211  authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:5     | Enter data.test.p
   212  authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:5     | | Eval data.utils.q[x]
   213  authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:5     | | Index data.utils.q (matched 1 rule)
   214  authz_bundle/...ternal/authz/policies/utils/utils.rego:3             | | Enter data.utils.q
   215  authz_bundle/...ternal/authz/policies/utils/utils.rego:3             | | | Eval x = data.a[_]
   216  authz_bundle/...ternal/authz/policies/utils/utils.rego:3             | | | Exit data.utils.q
   217  authz_bundle/...ternal/authz/policies/utils/utils.rego:3             | | Redo data.utils.q
   218  authz_bundle/...ternal/authz/policies/utils/utils.rego:3             | | | Redo x = data.a[_]
   219  authz_bundle/...ternal/authz/policies/utils/utils.rego:3             | | | Exit data.utils.q
   220  authz_bundle/...ternal/authz/policies/utils/utils.rego:3             | | Redo data.utils.q
   221  authz_bundle/...ternal/authz/policies/utils/utils.rego:3             | | | Redo x = data.a[_]
   222  authz_bundle/...ternal/authz/policies/utils/utils.rego:3             | | | Exit data.utils.q
   223  authz_bundle/...ternal/authz/policies/utils/utils.rego:3             | | Redo data.utils.q
   224  authz_bundle/...ternal/authz/policies/utils/utils.rego:3             | | | Redo x = data.a[_]
   225  authz_bundle/...ternal/authz/policies/utils/utils.rego:3             | | | Exit data.utils.q
   226  authz_bundle/...ternal/authz/policies/utils/utils.rego:3             | | Redo data.utils.q
   227  authz_bundle/...ternal/authz/policies/utils/utils.rego:3             | | | Redo x = data.a[_]
   228  authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:5     | | Eval plus(x, 1, n)
   229  authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:5     | | Exit data.test.p early
   230  query:1                                                              | Exit data.test.p = _
   231  query:1                                                              Redo data.test.p = _
   232  query:1                                                              | Redo data.test.p = _
   233  authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:5     | Redo data.test.p
   234  authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:5     | | Redo plus(x, 1, n)
   235  authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:5     | | Redo data.utils.q[x]
   236  `
   237  
   238  	var buf bytes.Buffer
   239  	PrettyTraceWithLocation(&buf, *tracer)
   240  	compareBuffers(t, expected, buf.String())
   241  }
   242  
   243  func TestPrettyTracePartialWithLocationTruncatedPaths(t *testing.T) {
   244  	ctx := context.Background()
   245  
   246  	compiler := ast.MustCompileModules(map[string]string{
   247  		"authz_bundle/com/foo/bar/baz/qux/acme/corp/internal/authz/policies/rbac/v1/beta/policy.rego": `
   248  		package example_rbac
   249  
   250  		default allow = false
   251  
   252  		allow {
   253  		    data.utils.user_has_role[role_name]
   254  
   255  		    data.utils.role_has_permission[role_name]
   256  		}
   257  
   258  		`,
   259  		"authz_bundle/com/foo/bar/baz/qux/acme/corp/internal/authz/policies/utils/user.rego": `
   260  		package utils
   261  
   262  		user_has_role[role_name] {
   263  		    role_binding = data.bindings[_]
   264  		    role_binding.role = role_name
   265  		    role_binding.user = input.subject.user
   266  		}
   267  
   268  		role_has_permission[role_name] {
   269  		    role = data.roles[_]
   270  		    role.name = role_name
   271  		    role.operation = input.action.operation
   272  		    role.resource = input.action.resource
   273  		}
   274  		`,
   275  	})
   276  
   277  	var data map[string]interface{}
   278  	err := util.UnmarshalJSON([]byte(`{
   279      "roles": [
   280          {
   281              "operation": "read",
   282              "resource": "widgets",
   283              "name": "widget-reader"
   284          },
   285          {
   286              "operation": "write",
   287              "resource": "widgets",
   288              "name": "widget-writer"
   289          }
   290      ],
   291      "bindings": [
   292          {
   293              "user": "inspector-alice",
   294              "role": "widget-reader"
   295          },
   296          {
   297              "user": "maker-bob",
   298              "role": "widget-writer"
   299          }
   300      ]
   301  	}`), &data)
   302  	if err != nil {
   303  		t.Fatalf("Unexpected error: %s", err)
   304  	}
   305  
   306  	store := inmem.NewFromObject(data)
   307  	txn := storage.NewTransactionOrDie(ctx, store)
   308  	defer store.Abort(ctx, txn)
   309  
   310  	tracer := NewBufferTracer()
   311  	query := NewQuery(ast.MustParseBody("data.example_rbac.allow")).
   312  		WithCompiler(compiler).
   313  		WithStore(store).
   314  		WithTransaction(txn).
   315  		WithUnknowns([]*ast.Term{ast.MustParseTerm("input")}).
   316  		WithTracer(tracer)
   317  
   318  	_, _, err = query.PartialRun(ctx)
   319  	if err != nil {
   320  		panic(err)
   321  	}
   322  
   323  	expected := `query:1                                                              Enter data.example_rbac.allow
   324  query:1                                                              | Eval data.example_rbac.allow
   325  query:1                                                              | Index data.example_rbac.allow (matched 1 rule, early exit)
   326  authz_bundle/...ternal/authz/policies/rbac/v1/beta/policy.rego:6     | Enter data.example_rbac.allow
   327  authz_bundle/...ternal/authz/policies/rbac/v1/beta/policy.rego:7     | | Eval data.utils.user_has_role[role_name]
   328  authz_bundle/...ternal/authz/policies/rbac/v1/beta/policy.rego:7     | | Index data.utils.user_has_role (matched 1 rule)
   329  authz_bundle/...ternal/authz/policies/utils/user.rego:4              | | Enter data.utils.user_has_role
   330  authz_bundle/...ternal/authz/policies/utils/user.rego:5              | | | Eval role_binding = data.bindings[_]
   331  authz_bundle/...ternal/authz/policies/utils/user.rego:6              | | | Eval role_binding.role = role_name
   332  authz_bundle/...ternal/authz/policies/utils/user.rego:7              | | | Eval role_binding.user = input.subject.user
   333  authz_bundle/...ternal/authz/policies/utils/user.rego:7              | | | Save "inspector-alice" = input.subject.user
   334  authz_bundle/...ternal/authz/policies/utils/user.rego:4              | | | Exit data.utils.user_has_role
   335  authz_bundle/...ternal/authz/policies/rbac/v1/beta/policy.rego:9     | | Eval data.utils.role_has_permission[role_name]
   336  authz_bundle/...ternal/authz/policies/rbac/v1/beta/policy.rego:9     | | Index data.utils.role_has_permission (matched 1 rule)
   337  authz_bundle/...ternal/authz/policies/utils/user.rego:10             | | Enter data.utils.role_has_permission
   338  authz_bundle/...ternal/authz/policies/utils/user.rego:11             | | | Eval role = data.roles[_]
   339  authz_bundle/...ternal/authz/policies/utils/user.rego:12             | | | Eval role.name = role_name
   340  authz_bundle/...ternal/authz/policies/utils/user.rego:13             | | | Eval role.operation = input.action.operation
   341  authz_bundle/...ternal/authz/policies/utils/user.rego:13             | | | Save "read" = input.action.operation
   342  authz_bundle/...ternal/authz/policies/utils/user.rego:14             | | | Eval role.resource = input.action.resource
   343  authz_bundle/...ternal/authz/policies/utils/user.rego:14             | | | Save "widgets" = input.action.resource
   344  authz_bundle/...ternal/authz/policies/utils/user.rego:10             | | | Exit data.utils.role_has_permission
   345  authz_bundle/...ternal/authz/policies/rbac/v1/beta/policy.rego:6     | | Exit data.example_rbac.allow
   346  authz_bundle/...ternal/authz/policies/rbac/v1/beta/policy.rego:6     | Redo data.example_rbac.allow
   347  authz_bundle/...ternal/authz/policies/rbac/v1/beta/policy.rego:9     | | Redo data.utils.role_has_permission[role_name]
   348  authz_bundle/...ternal/authz/policies/utils/user.rego:10             | | Redo data.utils.role_has_permission
   349  authz_bundle/...ternal/authz/policies/utils/user.rego:14             | | | Redo role.resource = input.action.resource
   350  authz_bundle/...ternal/authz/policies/utils/user.rego:13             | | | Redo role.operation = input.action.operation
   351  authz_bundle/...ternal/authz/policies/utils/user.rego:12             | | | Redo role.name = role_name
   352  authz_bundle/...ternal/authz/policies/utils/user.rego:11             | | | Redo role = data.roles[_]
   353  authz_bundle/...ternal/authz/policies/utils/user.rego:12             | | | Eval role.name = role_name
   354  authz_bundle/...ternal/authz/policies/utils/user.rego:12             | | | Fail role.name = role_name
   355  authz_bundle/...ternal/authz/policies/utils/user.rego:11             | | | Redo role = data.roles[_]
   356  authz_bundle/...ternal/authz/policies/rbac/v1/beta/policy.rego:7     | | Redo data.utils.user_has_role[role_name]
   357  authz_bundle/...ternal/authz/policies/utils/user.rego:4              | | Redo data.utils.user_has_role
   358  authz_bundle/...ternal/authz/policies/utils/user.rego:7              | | | Redo role_binding.user = input.subject.user
   359  authz_bundle/...ternal/authz/policies/utils/user.rego:6              | | | Redo role_binding.role = role_name
   360  authz_bundle/...ternal/authz/policies/utils/user.rego:5              | | | Redo role_binding = data.bindings[_]
   361  authz_bundle/...ternal/authz/policies/utils/user.rego:6              | | | Eval role_binding.role = role_name
   362  authz_bundle/...ternal/authz/policies/utils/user.rego:7              | | | Eval role_binding.user = input.subject.user
   363  authz_bundle/...ternal/authz/policies/utils/user.rego:7              | | | Save "maker-bob" = input.subject.user
   364  authz_bundle/...ternal/authz/policies/utils/user.rego:4              | | | Exit data.utils.user_has_role
   365  authz_bundle/...ternal/authz/policies/rbac/v1/beta/policy.rego:9     | | Eval data.utils.role_has_permission[role_name]
   366  authz_bundle/...ternal/authz/policies/rbac/v1/beta/policy.rego:9     | | Index data.utils.role_has_permission (matched 1 rule)
   367  authz_bundle/...ternal/authz/policies/utils/user.rego:10             | | Enter data.utils.role_has_permission
   368  authz_bundle/...ternal/authz/policies/utils/user.rego:11             | | | Eval role = data.roles[_]
   369  authz_bundle/...ternal/authz/policies/utils/user.rego:12             | | | Eval role.name = role_name
   370  authz_bundle/...ternal/authz/policies/utils/user.rego:12             | | | Fail role.name = role_name
   371  authz_bundle/...ternal/authz/policies/utils/user.rego:11             | | | Redo role = data.roles[_]
   372  authz_bundle/...ternal/authz/policies/utils/user.rego:12             | | | Eval role.name = role_name
   373  authz_bundle/...ternal/authz/policies/utils/user.rego:13             | | | Eval role.operation = input.action.operation
   374  authz_bundle/...ternal/authz/policies/utils/user.rego:13             | | | Save "write" = input.action.operation
   375  authz_bundle/...ternal/authz/policies/utils/user.rego:14             | | | Eval role.resource = input.action.resource
   376  authz_bundle/...ternal/authz/policies/utils/user.rego:14             | | | Save "widgets" = input.action.resource
   377  authz_bundle/...ternal/authz/policies/utils/user.rego:10             | | | Exit data.utils.role_has_permission
   378  authz_bundle/...ternal/authz/policies/rbac/v1/beta/policy.rego:6     | | Exit data.example_rbac.allow
   379  authz_bundle/...ternal/authz/policies/rbac/v1/beta/policy.rego:6     | Redo data.example_rbac.allow
   380  authz_bundle/...ternal/authz/policies/rbac/v1/beta/policy.rego:9     | | Redo data.utils.role_has_permission[role_name]
   381  authz_bundle/...ternal/authz/policies/utils/user.rego:10             | | Redo data.utils.role_has_permission
   382  authz_bundle/...ternal/authz/policies/utils/user.rego:14             | | | Redo role.resource = input.action.resource
   383  authz_bundle/...ternal/authz/policies/utils/user.rego:13             | | | Redo role.operation = input.action.operation
   384  authz_bundle/...ternal/authz/policies/utils/user.rego:12             | | | Redo role.name = role_name
   385  authz_bundle/...ternal/authz/policies/utils/user.rego:11             | | | Redo role = data.roles[_]
   386  authz_bundle/...ternal/authz/policies/rbac/v1/beta/policy.rego:7     | | Redo data.utils.user_has_role[role_name]
   387  authz_bundle/...ternal/authz/policies/utils/user.rego:4              | | Redo data.utils.user_has_role
   388  authz_bundle/...ternal/authz/policies/utils/user.rego:7              | | | Redo role_binding.user = input.subject.user
   389  authz_bundle/...ternal/authz/policies/utils/user.rego:6              | | | Redo role_binding.role = role_name
   390  authz_bundle/...ternal/authz/policies/utils/user.rego:5              | | | Redo role_binding = data.bindings[_]
   391  authz_bundle/...ternal/authz/policies/rbac/v1/beta/policy.rego:4     | Enter data.example_rbac.allow
   392  authz_bundle/...ternal/authz/policies/rbac/v1/beta/policy.rego:4     | | Eval true
   393  authz_bundle/...ternal/authz/policies/rbac/v1/beta/policy.rego:4     | | Exit data.example_rbac.allow
   394  authz_bundle/...ternal/authz/policies/rbac/v1/beta/policy.rego:4     | Redo data.example_rbac.allow
   395  authz_bundle/...ternal/authz/policies/rbac/v1/beta/policy.rego:4     | | Redo true
   396  query:1                                                              | Save data.partial.example_rbac.allow = _
   397  query:1                                                              | Save _
   398  query:1                                                              | Exit data.example_rbac.allow
   399  query:1                                                              Redo data.example_rbac.allow
   400  query:1                                                              | Fail data.example_rbac.allow
   401  `
   402  
   403  	var buf bytes.Buffer
   404  	PrettyTraceWithLocation(&buf, *tracer)
   405  	compareBuffers(t, expected, buf.String())
   406  }
   407  
   408  func TestTraceDuplicate(t *testing.T) {
   409  	// NOTE(sr): We're explicitly bypassing a caching optimization here:
   410  	// When the first query for a partial is `p[x]`, and `x` is not ground,
   411  	// we'll have the evaluation eval the full extent of the partial and
   412  	// cache that. Thus the second `p[1]` here will not trigger a duplicate
   413  	// event, because the query eval uses a different code path.
   414  	// Having `p[1]` queried first will side-step the caching optimization.
   415  	module := `package test
   416  
   417  	p[1]
   418  	p[2]
   419  	p[1]
   420  	`
   421  
   422  	ctx := context.Background()
   423  	compiler := compileModules([]string{module})
   424  	data := loadSmallTestData()
   425  	store := inmem.NewFromObject(data)
   426  	txn := storage.NewTransactionOrDie(ctx, store)
   427  	defer store.Abort(ctx, txn)
   428  
   429  	tracer := NewBufferTracer()
   430  	query := NewQuery(ast.MustParseBody("data.test.p[1]; data.test.p[x] = _")).
   431  		WithCompiler(compiler).
   432  		WithStore(store).
   433  		WithTransaction(txn).
   434  		WithTracer(tracer)
   435  
   436  	_, err := query.Run(ctx)
   437  	if err != nil {
   438  		panic(err)
   439  	}
   440  
   441  	n := 0
   442  	for _, event := range *tracer {
   443  		if event.Op == DuplicateOp {
   444  			n++
   445  		}
   446  	}
   447  
   448  	if n != 1 {
   449  		t.Fatalf("Expected one duplicate event but got %v", n)
   450  	}
   451  }
   452  
   453  func TestTraceNote(t *testing.T) {
   454  	module := `package test
   455  
   456  	p { q[x]; plus(x, 1, n); trace(sprintf("n=%v", [n])) }
   457  	q[x] { x = data.a[_] }`
   458  
   459  	ctx := context.Background()
   460  	compiler := compileModules([]string{module})
   461  	data := loadSmallTestData()
   462  	store := inmem.NewFromObject(data)
   463  	txn := storage.NewTransactionOrDie(ctx, store)
   464  	defer store.Abort(ctx, txn)
   465  
   466  	tracer := NewBufferTracer()
   467  	query := NewQuery(ast.MustParseBody("data.test.p = _")).
   468  		WithCompiler(compiler).
   469  		WithStore(store).
   470  		WithTransaction(txn).
   471  		WithTracer(tracer)
   472  
   473  	_, err := query.Run(ctx)
   474  	if err != nil {
   475  		panic(err)
   476  	}
   477  
   478  	expected := `Enter data.test.p = _
   479  | Eval data.test.p = _
   480  | Index data.test.p (matched 1 rule, early exit)
   481  | Enter data.test.p
   482  | | Eval data.test.q[x]
   483  | | Index data.test.q (matched 1 rule)
   484  | | Enter data.test.q
   485  | | | Eval x = data.a[_]
   486  | | | Exit data.test.q
   487  | | Redo data.test.q
   488  | | | Redo x = data.a[_]
   489  | | | Exit data.test.q
   490  | | Redo data.test.q
   491  | | | Redo x = data.a[_]
   492  | | | Exit data.test.q
   493  | | Redo data.test.q
   494  | | | Redo x = data.a[_]
   495  | | | Exit data.test.q
   496  | | Redo data.test.q
   497  | | | Redo x = data.a[_]
   498  | | Eval plus(x, 1, n)
   499  | | Eval sprintf("n=%v", [n], __local0__)
   500  | | Eval trace(__local0__)
   501  | | Note "n=2"
   502  | | Exit data.test.p early
   503  | Exit data.test.p = _
   504  Redo data.test.p = _
   505  | Redo data.test.p = _
   506  | Redo data.test.p
   507  | | Redo trace(__local0__)
   508  | | Redo sprintf("n=%v", [n], __local0__)
   509  | | Redo plus(x, 1, n)
   510  | | Redo data.test.q[x]
   511  `
   512  
   513  	var buf bytes.Buffer
   514  	PrettyTrace(&buf, *tracer)
   515  	compareBuffers(t, expected, buf.String())
   516  }
   517  
   518  func TestTraceNoteWithLocation(t *testing.T) {
   519  	module := `package test
   520  
   521  	p { q[x]; plus(x, 1, n); trace(sprintf("n=%v", [n])) }
   522  	q[x] { x = data.a[_] }`
   523  
   524  	ctx := context.Background()
   525  	compiler := compileModules([]string{module})
   526  	data := loadSmallTestData()
   527  	store := inmem.NewFromObject(data)
   528  	txn := storage.NewTransactionOrDie(ctx, store)
   529  	defer store.Abort(ctx, txn)
   530  
   531  	tracer := NewBufferTracer()
   532  	query := NewQuery(ast.MustParseBody("data.test.p = _")).
   533  		WithCompiler(compiler).
   534  		WithStore(store).
   535  		WithTransaction(txn).
   536  		WithTracer(tracer)
   537  
   538  	_, err := query.Run(ctx)
   539  	if err != nil {
   540  		panic(err)
   541  	}
   542  
   543  	expected := `query:1     Enter data.test.p = _
   544  query:1     | Eval data.test.p = _
   545  query:1     | Index data.test.p (matched 1 rule, early exit)
   546  query:3     | Enter data.test.p
   547  query:3     | | Eval data.test.q[x]
   548  query:3     | | Index data.test.q (matched 1 rule)
   549  query:4     | | Enter data.test.q
   550  query:4     | | | Eval x = data.a[_]
   551  query:4     | | | Exit data.test.q
   552  query:4     | | Redo data.test.q
   553  query:4     | | | Redo x = data.a[_]
   554  query:4     | | | Exit data.test.q
   555  query:4     | | Redo data.test.q
   556  query:4     | | | Redo x = data.a[_]
   557  query:4     | | | Exit data.test.q
   558  query:4     | | Redo data.test.q
   559  query:4     | | | Redo x = data.a[_]
   560  query:4     | | | Exit data.test.q
   561  query:4     | | Redo data.test.q
   562  query:4     | | | Redo x = data.a[_]
   563  query:3     | | Eval plus(x, 1, n)
   564  query:3     | | Eval sprintf("n=%v", [n], __local0__)
   565  query:3     | | Eval trace(__local0__)
   566  query:3     | | Note "n=2"
   567  query:3     | | Exit data.test.p early
   568  query:1     | Exit data.test.p = _
   569  query:1     Redo data.test.p = _
   570  query:1     | Redo data.test.p = _
   571  query:3     | Redo data.test.p
   572  query:3     | | Redo trace(__local0__)
   573  query:3     | | Redo sprintf("n=%v", [n], __local0__)
   574  query:3     | | Redo plus(x, 1, n)
   575  query:3     | | Redo data.test.q[x]
   576  `
   577  
   578  	var buf bytes.Buffer
   579  	PrettyTraceWithLocation(&buf, *tracer)
   580  	compareBuffers(t, expected, buf.String())
   581  }
   582  
   583  func TestMultipleTracers(t *testing.T) {
   584  
   585  	ctx := context.Background()
   586  
   587  	buf1 := NewBufferTracer()
   588  	buf2 := NewBufferTracer()
   589  	q := NewQuery(ast.MustParseBody("a = 1")).
   590  		WithTracer(buf1).
   591  		WithTracer(buf2)
   592  
   593  	_, err := q.Run(ctx)
   594  	if err != nil {
   595  		t.Fatal(err)
   596  	}
   597  
   598  	if len(*buf1) != len(*buf2) {
   599  		t.Fatalf("Expected buffer lengths to be equal but got: %d and %d", len(*buf1), len(*buf2))
   600  	}
   601  
   602  	for i := range *buf1 {
   603  		if !(*buf1)[i].Equal((*buf2)[i]) {
   604  			t.Fatalf("Expected all events to be equal but at index %d got %v and %v", i, (*buf1)[i], (*buf2)[i])
   605  		}
   606  	}
   607  
   608  }
   609  
   610  func TestTraceRewrittenQueryVars(t *testing.T) {
   611  	module := `package test
   612  
   613  	y = [1, 2, 3]`
   614  
   615  	ctx := context.Background()
   616  	compiler := compileModules([]string{module})
   617  	queryCompiler := compiler.QueryCompiler()
   618  	data := loadSmallTestData()
   619  	store := inmem.NewFromObject(data)
   620  	txn := storage.NewTransactionOrDie(ctx, store)
   621  	defer store.Abort(ctx, txn)
   622  
   623  	compiledQuery, err := queryCompiler.Compile(ast.MustParseBody("z := {a | a := data.y[_]}"))
   624  	if err != nil {
   625  		t.Fatalf("unexpected error: %s", err)
   626  	}
   627  
   628  	tracer := NewBufferTracer()
   629  	query := NewQuery(compiledQuery).
   630  		WithQueryCompiler(queryCompiler).
   631  		WithCompiler(compiler).
   632  		WithStore(store).
   633  		WithTransaction(txn).
   634  		WithTracer(tracer)
   635  
   636  	_, err = query.Run(ctx)
   637  	if err != nil {
   638  		panic(err)
   639  	}
   640  
   641  	foundQueryVar := false
   642  
   643  	for _, event := range *tracer {
   644  		if event.LocalMetadata != nil {
   645  			name, ok := event.LocalMetadata["__localq1__"]
   646  			if ok && name.Name == "z" {
   647  				foundQueryVar = true
   648  				break
   649  			}
   650  		}
   651  	}
   652  
   653  	if !foundQueryVar {
   654  		t.Error("Expected to find trace with rewritten var 'z' -> '__localq__")
   655  	}
   656  
   657  	// Rewrite the vars in the first event (which is a query) and verify that
   658  	// that vars have been mapped to user-provided names.
   659  	cpy := rewrite((*tracer)[0])
   660  	node := cpy.Node.(ast.Body)
   661  	exp := ast.MustParseBody("z = {a | a = data.y[_]}")
   662  
   663  	if !node.Equal(exp) {
   664  		t.Errorf("Expected %v but got %v", exp, node)
   665  	}
   666  }
   667  
   668  func TestTraceRewrittenVars(t *testing.T) {
   669  
   670  	mustParse := func(s string) *ast.Expr {
   671  		return ast.MustParseBodyWithOpts(s, ast.ParserOptions{FutureKeywords: []string{"every"}})[0]
   672  	}
   673  	everyCheck := func(stmt string) func(*testing.T, *Event, *Event) {
   674  		return func(t *testing.T, _ *Event, output *Event) {
   675  			exp := mustParse(stmt)
   676  			if !exp.Equal(output.Node.(*ast.Expr)) {
   677  				t.Errorf("expected %v to equal %v", output, exp)
   678  			}
   679  		}
   680  	}
   681  
   682  	tests := []struct {
   683  		note string
   684  		evt  *Event
   685  		exp  func(*testing.T, *Event, *Event)
   686  	}{
   687  		{
   688  			note: "issue 2022",
   689  			evt: &Event{
   690  				Node: ast.NewExpr(ast.VarTerm("foo")),
   691  				LocalMetadata: map[ast.Var]VarMetadata{
   692  					ast.Var("foo"): {Name: ast.Var("bar")},
   693  				},
   694  			},
   695  			exp: func(t *testing.T, input *Event, output *Event) {
   696  				if input.Node == output.Node {
   697  					t.Fatal("expected node to have been copied")
   698  				} else if !output.Node.(*ast.Expr).Equal(ast.NewExpr(ast.VarTerm("bar"))) {
   699  					t.Fatal("expected copy to contain rewritten var")
   700  				}
   701  			},
   702  		},
   703  		{
   704  			note: "every: key/val rewritten",
   705  			evt: &Event{
   706  				Node: mustParse(`every __local0__, __local1__ in __local2__ { __local1__ == __local0__ }`),
   707  				LocalMetadata: map[ast.Var]VarMetadata{
   708  					ast.Var("__local0__"): {Name: ast.Var("k")},
   709  					ast.Var("__local1__"): {Name: ast.Var("v")},
   710  				},
   711  			},
   712  			exp: everyCheck(`every k, v in __local2__ { v == k }`),
   713  		},
   714  		{
   715  			note: "every: key hidden if not rewritten",
   716  			evt: &Event{
   717  				Node: mustParse(`every __local0__, __local1__ in __local2__ { __local1__ == 1 }`),
   718  				LocalMetadata: map[ast.Var]VarMetadata{
   719  					ast.Var("__local1__"): {Name: ast.Var("v")},
   720  				},
   721  			},
   722  			exp: everyCheck(`every v in __local2__ { v == 1 }`),
   723  		},
   724  		{
   725  			note: "every: key hidden if rewritten to generated key", // NOTE(sr): this would happen for traceRedo
   726  			evt: &Event{
   727  				Node: mustParse(`every __local0__, __local1__ in __local2__ { __local1__ == 1 }`),
   728  				LocalMetadata: map[ast.Var]VarMetadata{
   729  					ast.Var("__local1__"): {Name: ast.Var("v")},
   730  					ast.Var("__local0__"): {Name: ast.Var("__local0__")},
   731  				},
   732  			},
   733  			exp: everyCheck(`every v in __local2__ { v == 1 }`),
   734  		},
   735  	}
   736  
   737  	for _, tc := range tests {
   738  		t.Run(tc.note, func(t *testing.T) {
   739  			output := rewrite(tc.evt)
   740  			tc.exp(t, tc.evt, output)
   741  		})
   742  	}
   743  }
   744  
   745  func TestTraceEveryEvaluation(t *testing.T) {
   746  	ctx := context.Background()
   747  
   748  	events := func(es ...string) []string {
   749  		return es
   750  	}
   751  
   752  	// NOTE(sr): String() on an *Event isn't stable, because iterating the underlying ast.ValueMap isn't.
   753  	// So we're stubbing out all captured events' value maps to be able to compare these as strings.
   754  	tests := []struct {
   755  		note   string
   756  		query  string
   757  		module string
   758  		exp    []string // these need to be found, extra events captured are ignored
   759  	}{
   760  		{
   761  			note:  "empty domain",
   762  			query: "data.test.p = x",
   763  			module: `package test
   764  			p { every k, v in [] { k != v } }`,
   765  			exp: events(
   766  				`Enter every __local0__, __local1__ in __local2__ { neq(__local0__, __local1__) } {} (qid=2, pqid=1)`,
   767  				`Exit every __local0__, __local1__ in __local2__ { neq(__local0__, __local1__) } {} (qid=2, pqid=1)`,
   768  			),
   769  		},
   770  		{
   771  			note:  "successful eval",
   772  			query: "data.test.p = x",
   773  			module: `package test
   774  			p { every k, v in [1] { k != v } }`,
   775  			exp: events(
   776  				`Enter every __local0__, __local1__ in __local2__ { neq(__local0__, __local1__) } {} (qid=2, pqid=1)`,
   777  				`Enter neq(__local0__, __local1__) {} (qid=3, pqid=2)`,
   778  				`Exit neq(__local0__, __local1__) {} (qid=3, pqid=2)`,
   779  				`Redo every __local0__, __local1__ in __local2__ { neq(__local0__, __local1__) } {} (qid=2, pqid=1)`,
   780  				`Exit every __local0__, __local1__ in __local2__ { neq(__local0__, __local1__) } {} (qid=2, pqid=1)`,
   781  			),
   782  		},
   783  		{
   784  			note:  "failure in first body query",
   785  			query: "data.test.p = x",
   786  			module: `package test
   787  			p { every v in [1, 2] { 1 != v } }`,
   788  			exp: events(
   789  				`Enter every __local0__, __local1__ in __local2__ { neq(1, __local1__) } {} (qid=2, pqid=1)`,
   790  				`Enter neq(1, __local1__) {} (qid=3, pqid=2)`,
   791  				`Fail neq(1, __local1__) {} (qid=3, pqid=2)`,
   792  				`Fail every __local0__, __local1__ in __local2__ { neq(1, __local1__) } {} (qid=2, pqid=1)`,
   793  				`Redo every __local0__, __local1__ in __local2__ { neq(1, __local1__) } {} (qid=2, pqid=1)`,
   794  			),
   795  		},
   796  		{
   797  			note:  "failure in last body query",
   798  			query: "data.test.p = x",
   799  			module: `package test
   800  			p { every v in [0, 1] { 1 != v } }`,
   801  			exp: events(
   802  				`Enter every __local0__, __local1__ in __local2__ { neq(1, __local1__) } {} (qid=2, pqid=1)`,
   803  				`Enter neq(1, __local1__) {} (qid=3, pqid=2)`,
   804  				`Exit neq(1, __local1__) {} (qid=3, pqid=2)`,
   805  				`Enter neq(1, __local1__) {} (qid=4, pqid=2)`,
   806  				`Fail neq(1, __local1__) {} (qid=4, pqid=2)`,
   807  				`Fail every __local0__, __local1__ in __local2__ { neq(1, __local1__) } {} (qid=2, pqid=1)`,
   808  				`Redo every __local0__, __local1__ in __local2__ { neq(1, __local1__) } {} (qid=2, pqid=1)`,
   809  			),
   810  		},
   811  	}
   812  
   813  	for _, tc := range tests {
   814  
   815  		opts := ast.CompileOpts{ParserOptions: ast.ParserOptions{FutureKeywords: []string{"every"}}}
   816  		compiler, err := ast.CompileModulesWithOpt(map[string]string{"test.rego": tc.module}, opts)
   817  		if err != nil {
   818  			t.Fatal(err)
   819  		}
   820  		queryCompiler := compiler.QueryCompiler()
   821  
   822  		compiledQuery, err := queryCompiler.Compile(ast.MustParseBody(tc.query))
   823  		if err != nil {
   824  			t.Fatalf("unexpected error: %s", err)
   825  		}
   826  
   827  		buf := NewBufferTracer()
   828  		query := NewQuery(compiledQuery).
   829  			WithQueryCompiler(queryCompiler).
   830  			WithCompiler(compiler).
   831  			WithStore(inmem.New()).
   832  			WithQueryTracer(buf)
   833  
   834  		if _, err := query.Run(ctx); err != nil {
   835  			t.Fatalf("unexpected error: %s", err)
   836  		}
   837  
   838  		for _, exp := range tc.exp {
   839  			found := false
   840  			for _, act := range *buf {
   841  				act.Locals = nil
   842  				if act.String() == exp {
   843  					found = true
   844  				}
   845  			}
   846  			if !found {
   847  				t.Errorf("expected event %v, found none", exp)
   848  			}
   849  		}
   850  		if t.Failed() {
   851  			t.Log("captured events:")
   852  			for _, ev := range *buf {
   853  				t.Log(ev.String())
   854  			}
   855  		}
   856  	}
   857  }
   858  
   859  func TestShortTraceFileNames(t *testing.T) {
   860  	longFilePath1 := "/really/long/file/path/longer/than/most/would/really/ever/be/policy.rego"
   861  	longFilePath1Similar := "/really/long/file/path/longer/than/most/policy.rego"
   862  	longFilePath2 := "GfjEjnMA6coNiPoMoRMVk7KeorGeRmjRkIYUsWtr564SQ7yDo4Yss2SoN8PMoe0TOfVaNFd1HQbC9NhK.rego"
   863  	longFilePath3 := "RqS50uWAOxqqHmzdKVM3OCVsZDb12FJikUYHhz9pNqMWx3wjeQBKY3UYXsJXzYGOzuYZbidag5SfKVdk.rego"
   864  
   865  	cases := []struct {
   866  		note            string
   867  		trace           []*Event
   868  		expectedNames   map[string]string
   869  		expectedLongest int
   870  	}{
   871  		{
   872  			note:            "empty trace",
   873  			trace:           nil,
   874  			expectedNames:   map[string]string{},
   875  			expectedLongest: 0,
   876  		},
   877  		{
   878  			note: "no locations",
   879  			trace: []*Event{
   880  				{Op: EnterOp, Node: ast.MustParseBody("true")},
   881  				{Op: EvalOp, Node: ast.MustParseBody("true")},
   882  			},
   883  			expectedNames:   map[string]string{},
   884  			expectedLongest: 0,
   885  		},
   886  		{
   887  			note: "no file names",
   888  			trace: []*Event{
   889  				{Location: ast.NewLocation([]byte("foo1"), "", 1, 1)},
   890  				{Location: ast.NewLocation([]byte("foo2"), "", 2, 1)},
   891  				{Location: ast.NewLocation([]byte("foo100"), "", 100, 1)},
   892  				{Location: ast.NewLocation([]byte("foo3"), "", 3, 1)},
   893  				{Location: ast.NewLocation([]byte("foo4"), "", 4, 1)},
   894  			},
   895  			expectedNames:   map[string]string{},
   896  			expectedLongest: minLocationWidth + len(":100"),
   897  		},
   898  		{
   899  			note: "single file name not shortened",
   900  			trace: []*Event{
   901  				{Location: ast.NewLocation([]byte("foo1"), "policy.rego", 1, 1)},
   902  			},
   903  			expectedNames: map[string]string{
   904  				"policy.rego": "policy.rego",
   905  			},
   906  			expectedLongest: len("policy.rego:1"),
   907  		},
   908  		{
   909  			note: "single file name not shortened different rows",
   910  			trace: []*Event{
   911  				{Location: ast.NewLocation([]byte("foo1"), "policy.rego", 1, 1)},
   912  				{Location: ast.NewLocation([]byte("foo1234"), "policy.rego", 1234, 1)},
   913  				{Location: ast.NewLocation([]byte("foo12"), "policy.rego", 12, 1)},
   914  				{Location: ast.NewLocation([]byte("foo123"), "policy.rego", 123, 1)},
   915  			},
   916  			expectedNames: map[string]string{
   917  				"policy.rego": "policy.rego",
   918  			},
   919  			expectedLongest: len("policy.rego:1234"),
   920  		},
   921  		{
   922  			note: "multiple files name not shortened",
   923  			trace: []*Event{
   924  				{Location: ast.NewLocation([]byte("a1"), "a.rego", 1, 1)},
   925  				{Location: ast.NewLocation([]byte("a1234"), "a.rego", 1234, 1)},
   926  				{Location: ast.NewLocation([]byte("x1"), "x.rego", 12, 1)},
   927  				{Location: ast.NewLocation([]byte("foo123"), "policy.rego", 123, 1)},
   928  			},
   929  			expectedNames: map[string]string{
   930  				"a.rego":      "a.rego",
   931  				"x.rego":      "x.rego",
   932  				"policy.rego": "policy.rego",
   933  			},
   934  			expectedLongest: len("policy.rego:123"),
   935  		},
   936  		{
   937  			note: "single file name shortened",
   938  			trace: []*Event{
   939  				{Location: ast.NewLocation([]byte("foo1"), longFilePath1, 1, 1)},
   940  			},
   941  			expectedNames: map[string]string{
   942  				longFilePath1: "/really/...h/longer/than/most/would/really/ever/be/policy.rego",
   943  			},
   944  			expectedLongest: maxIdealLocationWidth,
   945  		},
   946  		{
   947  			note: "single file name shortened different rows",
   948  			trace: []*Event{
   949  				{Location: ast.NewLocation([]byte("foo1"), longFilePath1, 1, 1)},
   950  				{Location: ast.NewLocation([]byte("foo1234"), longFilePath1, 1234, 1)},
   951  				{Location: ast.NewLocation([]byte("foo123"), longFilePath1, 123, 1)},
   952  				{Location: ast.NewLocation([]byte("foo12"), longFilePath1, 12, 1)},
   953  			},
   954  			expectedNames: map[string]string{
   955  				longFilePath1: "/really/...onger/than/most/would/really/ever/be/policy.rego",
   956  			},
   957  			expectedLongest: maxIdealLocationWidth,
   958  		},
   959  		{
   960  			note: "multiple files name shortened different rows",
   961  			trace: []*Event{
   962  				{Location: ast.NewLocation([]byte("similar1"), longFilePath1Similar, 1, 1)},
   963  				{Location: ast.NewLocation([]byte("foo1234"), longFilePath1, 1234, 1)},
   964  				{Location: ast.NewLocation([]byte("similar12"), longFilePath1Similar, 12, 1)},
   965  				{Location: ast.NewLocation([]byte("foo123"), longFilePath1, 123, 1)},
   966  			},
   967  			expectedNames: map[string]string{
   968  				longFilePath1:        "/really/...onger/than/most/would/really/ever/be/policy.rego",
   969  				longFilePath1Similar: "/really/...onger/than/most/policy.rego",
   970  			},
   971  			expectedLongest: maxIdealLocationWidth,
   972  		},
   973  		{
   974  			note: "multiple files name cannot be shortened",
   975  			trace: []*Event{
   976  				{Location: ast.NewLocation([]byte("foo1"), longFilePath2, 1, 1)},
   977  				{Location: ast.NewLocation([]byte("foo1234"), longFilePath3, 1234, 1)},
   978  			},
   979  			expectedNames: map[string]string{
   980  				longFilePath2: longFilePath2,
   981  				longFilePath3: longFilePath3,
   982  			},
   983  			expectedLongest: len(longFilePath3 + ":1234"),
   984  		},
   985  		{
   986  			note: "single file name shortened no leading slash",
   987  			trace: []*Event{
   988  				{Location: ast.NewLocation([]byte("foo1"), longFilePath1[1:], 1, 1)},
   989  			},
   990  			expectedNames: map[string]string{
   991  				longFilePath1[1:]: "really/...th/longer/than/most/would/really/ever/be/policy.rego",
   992  			},
   993  			expectedLongest: maxIdealLocationWidth,
   994  		},
   995  	}
   996  
   997  	for _, tc := range cases {
   998  		t.Run(tc.note, func(t *testing.T) {
   999  			actualNames, actualLongest := getShortenedFileNames(tc.trace)
  1000  			if actualLongest != tc.expectedLongest {
  1001  				t.Errorf("Expected longest location to be %d, got %d", tc.expectedLongest, actualLongest)
  1002  			}
  1003  
  1004  			if !reflect.DeepEqual(actualNames, tc.expectedNames) {
  1005  				t.Errorf("Expected %+v got %+v", tc.expectedNames, actualNames)
  1006  			}
  1007  		})
  1008  	}
  1009  }
  1010  
  1011  func TestBufferTracerTraceConfig(t *testing.T) {
  1012  	ct := QueryTracer(NewBufferTracer())
  1013  	conf := ct.Config()
  1014  
  1015  	expected := TraceConfig{
  1016  		PlugLocalVars: true,
  1017  	}
  1018  
  1019  	if !reflect.DeepEqual(expected, conf) {
  1020  		t.Fatalf("Expected config: %+v, got %+v", expected, conf)
  1021  	}
  1022  }
  1023  
  1024  func TestTraceInput(t *testing.T) {
  1025  	ctx := context.Background()
  1026  	module := `
  1027  		package test
  1028  
  1029  		rule = x {
  1030  			x = input.v
  1031  		}
  1032  	`
  1033  
  1034  	compiler := compileModules([]string{module})
  1035  	queryCompiler := compiler.QueryCompiler()
  1036  
  1037  	compiledQuery, err := queryCompiler.Compile(ast.MustParseBody("{x | v = [1, 2, 3][_];  x = data.test.rule with input.v as v}"))
  1038  	if err != nil {
  1039  		t.Fatalf("unexpected error: %s", err)
  1040  	}
  1041  
  1042  	tracer := NewBufferTracer()
  1043  	query := NewQuery(compiledQuery).
  1044  		WithQueryCompiler(queryCompiler).
  1045  		WithCompiler(compiler).
  1046  		WithStore(inmem.New()).
  1047  		WithQueryTracer(tracer)
  1048  
  1049  	if _, err := query.Run(ctx); err != nil {
  1050  		t.Fatalf("unexpected error: %s", err)
  1051  	}
  1052  
  1053  	num := 1
  1054  	for i, evt := range *tracer {
  1055  		if evt.Op == ExitOp && evt.HasRule() {
  1056  			input := evt.Input().Value
  1057  			expected := ast.NewObject([2]*ast.Term{ast.StringTerm("v"), ast.IntNumberTerm(num)})
  1058  			if input.Compare(expected) != 0 {
  1059  				t.Errorf("%v != %v at index %d", input, expected, i)
  1060  			}
  1061  			num++
  1062  		}
  1063  	}
  1064  }
  1065  
  1066  func TestTracePlug(t *testing.T) {
  1067  	ctx := context.Background()
  1068  	module := `
  1069  		package test
  1070  
  1071  		rule[[a, b]] {
  1072  			a = [1, 2][_]
  1073  			b = [2, 1][_]
  1074  		}
  1075  	`
  1076  
  1077  	compiler := compileModules([]string{module})
  1078  	queryCompiler := compiler.QueryCompiler()
  1079  
  1080  	compiledQuery, err := queryCompiler.Compile(ast.MustParseBody("data.test.rule"))
  1081  	if err != nil {
  1082  		t.Fatalf("unexpected error: %s", err)
  1083  	}
  1084  
  1085  	tracer := plugRuleHeadKeyRecorder{}
  1086  	query := NewQuery(compiledQuery).
  1087  		WithQueryCompiler(queryCompiler).
  1088  		WithCompiler(compiler).
  1089  		WithStore(inmem.New()).
  1090  		WithQueryTracer(&tracer)
  1091  
  1092  	if _, err := query.Run(ctx); err != nil {
  1093  		t.Fatalf("unexpected error: %s", err)
  1094  	}
  1095  
  1096  	expected := []ast.Value{
  1097  		ast.NewArray(ast.NumberTerm("1"), ast.NumberTerm("2")),
  1098  		ast.NewArray(ast.NumberTerm("1"), ast.NumberTerm("1")),
  1099  		ast.NewArray(ast.NumberTerm("2"), ast.NumberTerm("2")),
  1100  		ast.NewArray(ast.NumberTerm("2"), ast.NumberTerm("1")),
  1101  	}
  1102  
  1103  	if len(tracer) != len(expected) {
  1104  		t.Fatalf("unexpected result length %d", len(tracer))
  1105  	}
  1106  
  1107  	for i, value := range tracer {
  1108  		if value.Compare(expected[i]) != 0 {
  1109  			t.Errorf("%v != %v at index %d", value, expected[i], i)
  1110  		}
  1111  	}
  1112  }
  1113  
  1114  type plugRuleHeadKeyRecorder []ast.Value
  1115  
  1116  func (plugRuleHeadKeyRecorder) Enabled() bool {
  1117  	return true
  1118  }
  1119  
  1120  func (pr *plugRuleHeadKeyRecorder) TraceEvent(evt Event) {
  1121  	if evt.Op == ExitOp && evt.HasRule() {
  1122  		*pr = append(*pr, evt.Plug(evt.Node.(*ast.Rule).Head.Key).Value)
  1123  	}
  1124  }
  1125  
  1126  func (plugRuleHeadKeyRecorder) Config() TraceConfig {
  1127  	return TraceConfig{PlugLocalVars: false}
  1128  }
  1129  
  1130  func compareBuffers(t *testing.T, expected, actual string) {
  1131  	t.Helper()
  1132  	a := strings.Split(expected, "\n")
  1133  	b := strings.Split(actual, "\n")
  1134  	min := len(a)
  1135  	if min > len(b) {
  1136  		min = len(b)
  1137  	}
  1138  
  1139  	for i := 0; i < min; i++ {
  1140  		if a[i] != b[i] {
  1141  			t.Errorf("Line %v in trace is incorrect. Expected %q but got: %q", i+1, a[i], b[i])
  1142  		}
  1143  	}
  1144  
  1145  	if len(a) < len(b) {
  1146  		t.Errorf("Extra lines in trace:\n%v", strings.Join(b[min:], "\n"))
  1147  	} else if len(b) < len(a) {
  1148  		t.Errorf("Missing lines in trace:\n%v", strings.Join(a[min:], "\n"))
  1149  	}
  1150  
  1151  	if t.Failed() {
  1152  		fmt.Println("Trace output:")
  1153  		fmt.Println(actual)
  1154  	}
  1155  }
  1156  
  1157  func TestPrettyTraceWithLocationForMetadataCall(t *testing.T) {
  1158  	module := `package test
  1159  rule_no_output_var := rego.metadata.rule()
  1160  
  1161  rule_with_output_var {
  1162  	foo := rego.metadata.rule()
  1163  	foo == {}
  1164  }
  1165  
  1166  chain_no_output_var := rego.metadata.chain()
  1167  
  1168  chain_with_output_var {
  1169  	foo := rego.metadata.chain()
  1170  	foo == []
  1171  }`
  1172  
  1173  	ctx := context.Background()
  1174  	compiler := compileModules([]string{module})
  1175  	data := loadSmallTestData()
  1176  	store := inmem.NewFromObject(data)
  1177  	txn := storage.NewTransactionOrDie(ctx, store)
  1178  	defer store.Abort(ctx, txn)
  1179  
  1180  	tracer := NewBufferTracer()
  1181  	query := NewQuery(ast.MustParseBody("data.test = _")).
  1182  		WithCompiler(compiler).
  1183  		WithStore(store).
  1184  		WithTransaction(txn).
  1185  		WithTracer(tracer)
  1186  
  1187  	_, err := query.Run(ctx)
  1188  	if err != nil {
  1189  		panic(err)
  1190  	}
  1191  
  1192  	expected := `query:1      Enter data.test = _
  1193  query:1      | Eval data.test = _
  1194  query:1      | Index data.test.chain_no_output_var (matched 1 rule)
  1195  query:9      | Enter data.test.chain_no_output_var
  1196  query:9      | | Eval __local8__ = [{"path": ["test", "chain_no_output_var"]}]
  1197  query:9      | | Eval true
  1198  query:9      | | Eval __local4__ = __local8__
  1199  query:9      | | Exit data.test.chain_no_output_var
  1200  query:9      | Redo data.test.chain_no_output_var
  1201  query:9      | | Redo __local4__ = __local8__
  1202  query:9      | | Redo true
  1203  query:9      | | Redo __local8__ = [{"path": ["test", "chain_no_output_var"]}]
  1204  query:1      | Index data.test.chain_with_output_var (matched 1 rule, early exit)
  1205  query:11     | Enter data.test.chain_with_output_var
  1206  query:12     | | Eval __local9__ = [{"path": ["test", "chain_with_output_var"]}]
  1207  query:12     | | Eval __local5__ = __local9__
  1208  query:12     | | Eval foo = __local5__
  1209  query:13     | | Eval foo = []
  1210  query:13     | | Fail foo = []
  1211  query:12     | | Redo foo = __local5__
  1212  query:12     | | Redo __local5__ = __local9__
  1213  query:12     | | Redo __local9__ = [{"path": ["test", "chain_with_output_var"]}]
  1214  query:1      | Index data.test.rule_no_output_var (matched 1 rule)
  1215  query:2      | Enter data.test.rule_no_output_var
  1216  query:2      | | Eval __local6__ = {}
  1217  query:2      | | Eval true
  1218  query:2      | | Eval __local2__ = __local6__
  1219  query:2      | | Exit data.test.rule_no_output_var
  1220  query:2      | Redo data.test.rule_no_output_var
  1221  query:2      | | Redo __local2__ = __local6__
  1222  query:2      | | Redo true
  1223  query:2      | | Redo __local6__ = {}
  1224  query:1      | Index data.test.rule_with_output_var (matched 1 rule, early exit)
  1225  query:4      | Enter data.test.rule_with_output_var
  1226  query:5      | | Eval __local7__ = {}
  1227  query:5      | | Eval __local3__ = __local7__
  1228  query:5      | | Eval foo = __local3__
  1229  query:6      | | Eval foo = {}
  1230  query:4      | | Exit data.test.rule_with_output_var early
  1231  query:4      | Redo data.test.rule_with_output_var
  1232  query:6      | | Redo foo = {}
  1233  query:5      | | Redo foo = __local3__
  1234  query:5      | | Redo __local3__ = __local7__
  1235  query:5      | | Redo __local7__ = {}
  1236  query:1      | Exit data.test = _
  1237  query:1      Redo data.test = _
  1238  query:1      | Redo data.test = _
  1239  `
  1240  
  1241  	var buf bytes.Buffer
  1242  	PrettyTraceWithLocation(&buf, *tracer)
  1243  	compareBuffers(t, expected, buf.String())
  1244  }