Files
releases/repl/repl_test.go
T
Patrick East 94cd44ed4e topdown: Improve pretty trace location details
Previously we had a hard set width for location info in pretty trace
outputs. This changes to use a dynamic width up to a reasonable max
and then starts to shorten paths as possible by swapping in `...` for
the longest common substring of all paths.

To get the longest substring this brings in a couple files from
https://github.com/vmarkovtsev/go-lcss which implements an efficient
algorithm for it (rather than us implementing something fancy from
scratch). It's pretty isolated and is unlikely to need any updates
over time so the maintenance should be low.

Fixes: #2143
Signed-off-by: Patrick East <east.patrick@gmail.com>
2020-04-02 11:10:13 -07:00

2154 lines
46 KiB
Go

// Copyright 2016 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package repl
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"sort"
"strings"
"testing"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/internal/presentation"
"github.com/open-policy-agent/opa/storage"
"github.com/open-policy-agent/opa/storage/inmem"
"github.com/open-policy-agent/opa/util"
)
func TestFunction(t *testing.T) {
store := newTestStore()
ctx := context.Background()
txn := storage.NewTransactionOrDie(ctx, store, storage.WriteParams)
mod1 := []byte(`package a.b.c
foo(x) = y {
split(x, ".", y)
}
bar([x, y]) = z {
trim(x, y, z)
}
`)
mod2 := []byte(`package a.b.d
baz(_) = y {
data.a.b.c.foo("barfoobar.bar", x)
data.a.b.c.bar(x, y)
}`)
if err := store.UpsertPolicy(ctx, txn, "mod1", mod1); err != nil {
panic(err)
}
if err := store.UpsertPolicy(ctx, txn, "mod2", mod2); err != nil {
panic(err)
}
if err := store.Commit(ctx, txn); err != nil {
panic(err)
}
var buf bytes.Buffer
repl := newRepl(store, &buf)
repl.OneShot(ctx, "json")
repl.OneShot(ctx, "data.a.b.d.baz(null, x)")
exp := util.MustUnmarshalJSON([]byte(`{"result": [{"expressions": [{"text":"data.a.b.d.baz(null, x)", "value": true, "location": {"row": 1, "col": 1}}], "bindings": {"x": "foo"}}]}`))
result := util.MustUnmarshalJSON(buf.Bytes())
if !reflect.DeepEqual(exp, result) {
t.Fatalf("expected data.a.b.d.baz(x) to be %v, got %v", exp, result)
}
err := repl.OneShot(ctx, "p(x) = y { y = x+4 }")
if err != nil {
t.Fatalf("failed to compile repl function: %v", err)
}
buf.Reset()
repl.OneShot(ctx, "data.repl.p(5, y)")
exp = util.MustUnmarshalJSON([]byte(`{
"result": [
{
"expressions": [
{
"text": "data.repl.p(5, y)",
"value": true,
"location": {
"col": 1,
"row": 1
}
}
],
"bindings": {
"y": 9
}
}
]
}`))
result = util.MustUnmarshalJSON(buf.Bytes())
if !reflect.DeepEqual(exp, result) {
t.Fatalf("expected datrepl.p(x) to be %v, got %v", exp, result)
}
repl.OneShot(ctx, "f(1, x) = y { y = x }")
repl.OneShot(ctx, "f(2, x) = y { y = x*2 }")
buf.Reset()
repl.OneShot(ctx, "data.repl.f(1, 2, y)")
exp = util.MustUnmarshalJSON([]byte(`{
"result": [
{
"expressions": [
{
"text": "data.repl.f(1, 2, y)",
"location": {
"col": 1,
"row": 1
},
"value": true
}
],
"bindings": {
"y": 2
}
}
]
}`))
result = util.MustUnmarshalJSON(buf.Bytes())
if !reflect.DeepEqual(exp, result) {
t.Fatalf("expected data.repl.f(1, 2, y) to be %v, got %v", exp, result)
}
buf.Reset()
repl.OneShot(ctx, "data.repl.f(2, 2, y)")
exp = util.MustUnmarshalJSON([]byte(`{
"result": [
{
"expressions": [
{
"text": "data.repl.f(2, 2, y)",
"location": {
"col": 1,
"row": 1
},
"value": true
}
],
"bindings": {
"y": 4
}
}
]
}`))
result = util.MustUnmarshalJSON(buf.Bytes())
if !reflect.DeepEqual(exp, result) {
t.Fatalf("expected data.repl.f(2, 2, y) to be %v, got %v", exp, result)
}
}
func TestComplete(t *testing.T) {
ctx := context.Background()
store := newTestStore()
txn := storage.NewTransactionOrDie(ctx, store, storage.WriteParams)
mod1 := []byte(`package a.b.c
p = 1 { true }
q = 2 { true }
q = 3 { false }`)
mod2 := []byte(`package a.b.d
r = 3 { true }`)
if err := store.UpsertPolicy(ctx, txn, "mod1", mod1); err != nil {
panic(err)
}
if err := store.UpsertPolicy(ctx, txn, "mod2", mod2); err != nil {
panic(err)
}
if err := store.Commit(ctx, txn); err != nil {
panic(err)
}
var buf bytes.Buffer
repl := newRepl(store, &buf)
repl.OneShot(ctx, "s = 4")
buf.Reset()
result := repl.complete("")
expected := []string{
"data.a.b.c.p",
"data.a.b.c.q",
"data.a.b.d.r",
"data.repl.s",
}
sort.Strings(result)
sort.Strings(expected)
if !reflect.DeepEqual(result, expected) {
t.Fatalf("Expected %v but got: %v", expected, result)
}
result = repl.complete("data.a.b")
expected = []string{
"data.a.b.c.p",
"data.a.b.c.q",
"data.a.b.d.r",
}
sort.Strings(result)
sort.Strings(expected)
if !reflect.DeepEqual(result, expected) {
t.Fatalf("Expected %v but got: %v", expected, result)
}
result = repl.complete("data.a.b.c.p[x]")
expected = []string{}
if !reflect.DeepEqual(result, expected) {
t.Fatalf("Expected %v but got: %v", expected, result)
}
repl.OneShot(ctx, "import data.a.b.c.p as xyz")
repl.OneShot(ctx, "import data.a.b.d")
result = repl.complete("x")
expected = []string{
"xyz",
}
if !reflect.DeepEqual(result, expected) {
t.Fatalf("Expected %v but got: %v", expected, result)
}
}
func TestDump(t *testing.T) {
ctx := context.Background()
input := `{"a": [1,2,3,4]}`
var data map[string]interface{}
err := util.UnmarshalJSON([]byte(input), &data)
if err != nil {
panic(err)
}
store := inmem.NewFromObject(data)
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.OneShot(ctx, "dump")
expectOutput(t, buffer.String(), "{\"a\":[1,2,3,4]}\n")
}
func TestDumpPath(t *testing.T) {
ctx := context.Background()
input := `{"a": [1,2,3,4]}`
var data map[string]interface{}
err := util.UnmarshalJSON([]byte(input), &data)
if err != nil {
panic(err)
}
store := inmem.NewFromObject(data)
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
dir, err := ioutil.TempDir("", "dump-path-test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
file := filepath.Join(dir, "tmpfile")
repl.OneShot(ctx, fmt.Sprintf("dump %s", file))
if buffer.String() != "" {
t.Errorf("Expected no output but got: %v", buffer.String())
}
bs, err := ioutil.ReadFile(file)
if err != nil {
t.Fatalf("Expected file read to succeed but got: %v", err)
}
var result map[string]interface{}
if err := util.UnmarshalJSON(bs, &result); err != nil {
t.Fatalf("Expected json unmarshal to succeed but got: %v", err)
}
if !reflect.DeepEqual(data, result) {
t.Fatalf("Expected dumped json to equal %v but got: %v", data, result)
}
}
func TestHelp(t *testing.T) {
topics["deadbeef"] = topicDesc{
fn: func(w io.Writer) error {
fmt.Fprintln(w, "blah blah blah")
return nil
},
}
ctx := context.Background()
store := inmem.New()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.OneShot(ctx, "help deadbeef")
expected := "blah blah blah\n"
if buffer.String() != expected {
t.Fatalf("Unexpected output from help topic: %v", buffer.String())
}
}
func TestShowDebug(t *testing.T) {
ctx := context.Background()
store := inmem.New()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.OneShot(ctx, "show debug")
var result replDebugState
if err := util.Unmarshal(buffer.Bytes(), &result); err != nil {
t.Fatal(err)
}
var exp replDebugState
exp.Explain = explainOff
if !reflect.DeepEqual(result, exp) {
t.Fatalf("Expected %+v but got %+v", exp, result)
}
buffer.Reset()
repl.OneShot(ctx, "trace")
repl.OneShot(ctx, "metrics")
repl.OneShot(ctx, "instrument")
repl.OneShot(ctx, "profile")
repl.OneShot(ctx, "show debug")
exp.Explain = explainFull
exp.Metrics = true
exp.Instrument = true
exp.Profile = true
if err := util.Unmarshal(buffer.Bytes(), &result); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(result, exp) {
t.Fatalf("Expected %+v but got %+v", exp, result)
}
}
func TestShow(t *testing.T) {
ctx := context.Background()
store := inmem.New()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.OneShot(ctx, `package repl_test`)
repl.OneShot(ctx, "show")
assertREPLText(t, buffer, "package repl_test\n")
buffer.Reset()
repl.OneShot(ctx, "import input.xyz")
repl.OneShot(ctx, "show")
expected := `package repl_test
import input.xyz` + "\n"
assertREPLText(t, buffer, expected)
buffer.Reset()
repl.OneShot(ctx, "import data.foo as bar")
repl.OneShot(ctx, "show")
expected = `package repl_test
import data.foo as bar
import input.xyz` + "\n"
assertREPLText(t, buffer, expected)
buffer.Reset()
repl.OneShot(ctx, `p[1] { true }`)
buffer.Reset()
repl.OneShot(ctx, `p[2] { true }`)
buffer.Reset()
repl.OneShot(ctx, "show")
expected = `package repl_test
import data.foo as bar
import input.xyz
p[1]
p[2]` + "\n"
assertREPLText(t, buffer, expected)
buffer.Reset()
repl.OneShot(ctx, "package abc")
repl.OneShot(ctx, "show")
assertREPLText(t, buffer, "package abc\n")
buffer.Reset()
repl.OneShot(ctx, "package repl_test")
repl.OneShot(ctx, "show")
assertREPLText(t, buffer, expected)
buffer.Reset()
}
func TestTypes(t *testing.T) {
ctx := context.Background()
store := inmem.New()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.OneShot(ctx, "types")
repl.OneShot(ctx, `p[x] = y { x := "a"; y := 1 }`)
repl.OneShot(ctx, `p[x]`)
output := strings.TrimSpace(buffer.String())
exp := []string{
"# data.repl.p[x]: number",
"# x: string",
}
for i := range exp {
if !strings.Contains(output, exp[i]) {
t.Fatalf("Expected output to contain %q but got: %v", exp[i], output)
}
}
}
func TestUnknown(t *testing.T) {
ctx := context.Background()
store := inmem.New()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.OneShot(ctx, "xs = [1,2,3]")
buffer.Reset()
err := repl.OneShot(ctx, "unknown input")
if err != nil {
t.Fatal("Unexpected command error:", err)
}
repl.OneShot(ctx, "data.repl.xs[i] = x; input.x = x")
output := strings.TrimSpace(buffer.String())
expected := strings.TrimSpace(`
+---------+-------------+
| Query 1 | input.x = 1 |
| | i = 0 |
| | x = 1 |
+---------+-------------+
| Query 2 | input.x = 2 |
| | i = 1 |
| | x = 2 |
+---------+-------------+
| Query 3 | input.x = 3 |
| | i = 2 |
| | x = 3 |
+---------+-------------+
`)
if output != expected {
t.Fatalf("Unexpected output. Expected:\n\n%v\n\nGot:\n\n%v", expected, output)
}
}
func TestUnknownMetrics(t *testing.T) {
ctx := context.Background()
store := inmem.New()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.OneShot(ctx, "xs = [1,2,3]")
buffer.Reset()
err := repl.OneShot(ctx, "unknown input")
if err != nil {
t.Fatal("Unexpected command error:", err)
}
repl.OneShot(ctx, "metrics")
repl.OneShot(ctx, "data.repl.xs[i] = x; input.x = x")
output := strings.TrimSpace(buffer.String())
expected := strings.TrimSpace(`
+---------+-------------+
| Query 1 | input.x = 1 |
| | i = 0 |
| | x = 1 |
+---------+-------------+
| Query 2 | input.x = 2 |
| | i = 1 |
| | x = 2 |
+---------+-------------+
| Query 3 | input.x = 3 |
| | i = 2 |
| | x = 3 |
+---------+-------------+
`)
if !strings.HasPrefix(output, expected) {
t.Fatalf("Unexpected partial eval results. Expected:\n\n%v\n\nGot:\n\n%v", expected, output)
}
if !strings.Contains(output, "timer_rego_partial_eval_ns") {
t.Fatal("Expected timer_rego_partial_eval_ns but got:\n\n", output)
}
}
func TestUnknownJSON(t *testing.T) {
ctx := context.Background()
store := inmem.New()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.OneShot(ctx, "xs = [1,2,3]")
buffer.Reset()
err := repl.OneShot(ctx, "unknown input")
if err != nil {
t.Fatal("Unexpected command error:", err)
}
repl.OneShot(ctx, "json")
repl.OneShot(ctx, "data.repl.xs[i] = x; input.x = x")
var result presentation.Output
if err := json.NewDecoder(&buffer).Decode(&result); err != nil {
t.Fatal(err)
}
if len(result.Partial.Queries) != 3 {
t.Fatalf("Expected exactly 3 queries in partial evaluation output but got: %v", result)
}
}
func TestUnknownInvalid(t *testing.T) {
ctx := context.Background()
store := inmem.New()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
err := repl.OneShot(ctx, "unknown x-1")
if err == nil || !strings.Contains(err.Error(), "usage: unknown <input/data reference>") {
t.Fatal("expected error from setting bad unknown but got:", err)
}
// Ensure that partial evaluation has not been enabled.
buffer.Reset()
repl.OneShot(ctx, "1+2")
result := strings.TrimSpace(buffer.String())
if result != "3" {
t.Fatal("want true but got:", result)
}
}
func TestUnset(t *testing.T) {
ctx := context.Background()
store := inmem.New()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
var err error
repl.OneShot(ctx, "magic = 23")
repl.OneShot(ctx, "p = 3.14")
repl.OneShot(ctx, "unset p")
err = repl.OneShot(ctx, "p")
if _, ok := err.(ast.Errors); !ok {
t.Fatalf("Expected AST error but got: %v", err)
}
buffer.Reset()
repl.OneShot(ctx, "p = 3.14")
repl.OneShot(ctx, `p = 3 { false }`)
repl.OneShot(ctx, "unset p")
err = repl.OneShot(ctx, "p")
if _, ok := err.(ast.Errors); !ok {
t.Fatalf("Expected AST error but got err: %v, output: %v", err, buffer.String())
}
if err := repl.OneShot(ctx, "unset "); err == nil {
t.Fatalf("Expected unset error for bad syntax but got: %v", buffer.String())
}
if err := repl.OneShot(ctx, "unset 1=1"); err == nil {
t.Fatalf("Expected unset error for bad syntax but got: %v", buffer.String())
}
if err := repl.OneShot(ctx, `unset "p"`); err == nil {
t.Fatalf("Expected unset error for bad syntax but got: %v", buffer.String())
}
buffer.Reset()
repl.OneShot(ctx, "p(x) = y { y = x }")
repl.OneShot(ctx, "unset p")
err = repl.OneShot(ctx, "data.repl.p(1, 2)")
if err == nil || err.Error() != `1 error occurred: 1:1: rego_type_error: undefined function data.repl.p` {
t.Fatalf("Expected eval error (undefined built-in) but got err: '%v'", err)
}
buffer.Reset()
repl.OneShot(ctx, "p(1, x) = y { y = x }")
repl.OneShot(ctx, "p(2, x) = y { y = x+1 }")
repl.OneShot(ctx, "unset p")
err = repl.OneShot(ctx, "data.repl.p(1, 2, 3)")
if err == nil || err.Error() != `1 error occurred: 1:1: rego_type_error: undefined function data.repl.p` {
t.Fatalf("Expected eval error (undefined built-in) but got err: '%v'", err)
}
buffer.Reset()
repl.OneShot(ctx, `unset q`)
if buffer.String() != "warning: no matching rules in current module\n" {
t.Fatalf("Expected unset error for missing rule but got: %v", buffer.String())
}
buffer.Reset()
repl.OneShot(ctx, `unset q`)
if buffer.String() != "warning: no matching rules in current module\n" {
t.Fatalf("Expected unset error for missing function but got: %v", buffer.String())
}
buffer.Reset()
repl.OneShot(ctx, `magic`)
if buffer.String() != "23\n" {
t.Fatalf("Expected magic to be defined but got: %v", buffer.String())
}
buffer.Reset()
repl.OneShot(ctx, `package data.other`)
err = repl.OneShot(ctx, `unset magic`)
if buffer.String() != "warning: no matching rules in current module\n" {
t.Fatalf("Expected unset error for bad syntax but got: %v", buffer.String())
}
repl.OneShot(ctx, `input = {}`)
if err := repl.OneShot(ctx, `unset input`); err != nil {
t.Fatalf("Expected unset to succeed for input: %v", err)
}
buffer.Reset()
repl.OneShot(ctx, `not input`)
if buffer.String() != "true\n" {
t.Fatalf("Expected unset input to remove input document: %v", buffer.String())
}
}
func TestOneShotEmptyBufferOneExpr(t *testing.T) {
ctx := context.Background()
store := newTestStore()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.OneShot(ctx, "data.a[i].b.c[j] = 2")
expectOutput(t, buffer.String(), "+---+---+\n| i | j |\n+---+---+\n| 0 | 1 |\n+---+---+\n")
buffer.Reset()
repl.OneShot(ctx, "data.a[i].b.c[j] = \"deadbeef\"")
expectOutput(t, buffer.String(), "undefined\n")
}
func TestOneShotEmptyBufferOneRule(t *testing.T) {
ctx := context.Background()
store := newTestStore()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.OneShot(ctx, `p[x] { data.a[i] = x }`)
expectOutput(t, buffer.String(), "Rule 'p' defined in package repl. Type 'show' to see rules.\n")
}
func TestOneShotBufferedExpr(t *testing.T) {
ctx := context.Background()
store := newTestStore()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.OneShot(ctx, "data.a[i].b.c[j] = ")
expectOutput(t, buffer.String(), "")
repl.OneShot(ctx, "2")
expectOutput(t, buffer.String(), "")
repl.OneShot(ctx, "")
expectOutput(t, buffer.String(), "+---+---+\n| i | j |\n+---+---+\n| 0 | 1 |\n+---+---+\n")
}
func TestOneShotBufferedRule(t *testing.T) {
ctx := context.Background()
store := newTestStore()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.OneShot(ctx, "p[x] { ")
expectOutput(t, buffer.String(), "")
repl.OneShot(ctx, "data.a[i].b.c[1]")
expectOutput(t, buffer.String(), "")
repl.OneShot(ctx, " = ")
expectOutput(t, buffer.String(), "")
repl.OneShot(ctx, "x")
expectOutput(t, buffer.String(), "")
repl.OneShot(ctx, "}")
expectOutput(t, buffer.String(), "")
repl.OneShot(ctx, "")
expectOutput(t, buffer.String(), "Rule 'p' defined in package repl. Type 'show' to see rules.\n")
buffer.Reset()
repl.OneShot(ctx, "p[2]")
expectOutput(t, buffer.String(), "2\n")
}
func TestOneShotJSON(t *testing.T) {
ctx := context.Background()
store := newTestStore()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.outputFormat = "json"
repl.OneShot(ctx, "data.a[i] = x")
var expected interface{}
if err := util.UnmarshalJSON([]byte(`{
"result": [
{
"expressions": [
{
"value": true,
"text": "data.a[i] = x",
"location": {
"row": 1,
"col": 1
}
}
],
"bindings": {
"i": 0,
"x": {
"b": {
"c": [
true,
2,
false
]
}
}
}
},
{
"expressions": [
{
"value": true,
"text": "data.a[i] = x",
"location": {
"row": 1,
"col": 1
}
}
],
"bindings": {
"i": 1,
"x": {
"b": {
"c": [
false,
true,
1
]
}
}
}
}
]
}`), &expected); err != nil {
panic(err)
}
var result interface{}
if err := util.UnmarshalJSON(buffer.Bytes(), &result); err != nil {
t.Errorf("Unexpected output format: %v", err)
return
}
if !reflect.DeepEqual(expected, result) {
t.Errorf("Expected %v but got: %v", expected, buffer.String())
}
}
func TestEvalData(t *testing.T) {
ctx := context.Background()
store := newTestStore()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
testMod := []byte(`package ex
p = [1, 2, 3] { true }`)
txn := storage.NewTransactionOrDie(ctx, store, storage.WriteParams)
if err := store.UpsertPolicy(ctx, txn, "test", testMod); err != nil {
panic(err)
}
if err := store.Commit(ctx, txn); err != nil {
panic(err)
}
repl.OneShot(ctx, "data")
expected := parseJSON(`
{
"a": [
{
"b": {
"c": [
true,
2,
false
]
}
},
{
"b": {
"c": [
false,
true,
1
]
}
}
],
"ex": {
"p": [
1,
2,
3
]
}
}`)
result := parseJSON(buffer.String())
// Strip REPL documents out as these change depending on build settings.
data := result.(map[string]interface{})
delete(data, "repl")
if !reflect.DeepEqual(result, expected) {
t.Fatalf("Expected:\n%v\n\nGot:\n%v", expected, result)
}
}
func TestEvalFalse(t *testing.T) {
ctx := context.Background()
store := newTestStore()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.OneShot(ctx, "false")
result := buffer.String()
if result != "false\n" {
t.Errorf("Expected result to be false but got: %v", result)
}
}
func TestEvalConstantRule(t *testing.T) {
ctx := context.Background()
store := newTestStore()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.OneShot(ctx, "pi = 3.14")
result := buffer.String()
if result != "Rule 'pi' defined in package repl. Type 'show' to see rules.\n" {
t.Errorf("Expected rule to be defined but got: %v", result)
return
}
buffer.Reset()
repl.OneShot(ctx, "pi")
result = buffer.String()
expected := "3.14\n"
if result != expected {
t.Errorf("Expected pi to evaluate to 3.14 but got: %v", result)
return
}
buffer.Reset()
err := repl.OneShot(ctx, "pi.deadbeef")
result = buffer.String()
if result != "" || !strings.Contains(err.Error(), "undefined ref: data.repl.pi.deadbeef") {
t.Fatalf("Expected pi.deadbeef to fail/error but got:\nresult: %q\nerr: %v", result, err)
}
buffer.Reset()
repl.OneShot(ctx, "pi > 3")
result = buffer.String()
if result != "true\n" {
t.Errorf("Expected pi > 3 to be true but got: %v", result)
return
}
}
func TestEvalConstantRuleDefaultRootDoc(t *testing.T) {
ctx := context.Background()
store := newTestStore()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.OneShot(ctx, "input = 1")
buffer.Reset()
repl.OneShot(ctx, "input = 2")
assertREPLText(t, buffer, "undefined\n")
buffer.Reset()
repl.OneShot(ctx, "input = 1")
assertREPLText(t, buffer, "true\n")
}
func TestEvalConstantRuleAssignment(t *testing.T) {
ctx := context.Background()
store := newTestStore()
var buffer bytes.Buffer
defined := "Rule 'x' defined in package repl. Type 'show' to see rules.\n"
redefined := "Rule 'x' re-defined in package repl. Type 'show' to see rules.\n"
definedInput := "Rule 'input' defined in package repl. Type 'show' to see rules.\n"
redefinedInput := "Rule 'input' re-defined in package repl. Type 'show' to see rules.\n"
repl := newRepl(store, &buffer)
repl.OneShot(ctx, "x = 1")
assertREPLText(t, buffer, defined)
buffer.Reset()
repl.OneShot(ctx, "x := 2")
assertREPLText(t, buffer, redefined)
buffer.Reset()
repl.OneShot(ctx, "show")
assertREPLText(t, buffer, `package repl
x := 2
`)
buffer.Reset()
repl.OneShot(ctx, "x := 3")
assertREPLText(t, buffer, redefined)
buffer.Reset()
repl.OneShot(ctx, "x")
result := buffer.String()
if result != "3\n" {
t.Fatalf("Expected 3 but got: %v", result)
}
buffer.Reset()
repl.OneShot(ctx, "x = 3")
result = buffer.String()
if result != "true\n" {
t.Fatalf("Expected true but got: %v", result)
}
buffer.Reset()
repl.OneShot(ctx, "input = 0")
assertREPLText(t, buffer, definedInput)
buffer.Reset()
repl.OneShot(ctx, "input := 1")
assertREPLText(t, buffer, redefinedInput)
buffer.Reset()
repl.OneShot(ctx, "input")
result = buffer.String()
if result != "1\n" {
t.Fatalf("Expected 1 but got: %v", result)
}
buffer.Reset()
err := repl.OneShot(ctx, "assign()")
if err == nil || !strings.Contains(err.Error(), "too few arguments") {
t.Fatal("Expected type check error but got:", err)
}
}
func TestEvalSingleTermMultiValue(t *testing.T) {
ctx := context.Background()
store := newTestStore()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.outputFormat = "json"
input := `{
"result": [
{
"expressions": [
{
"value": true,
"text": "data.a[i].b.c[_]",
"location": {
"row": 1,
"col": 1
}
}
],
"bindings": {
"i": 0
}
},
{
"expressions": [
{
"value": 2,
"text": "data.a[i].b.c[_]",
"location": {
"row": 1,
"col": 1
}
}
],
"bindings": {
"i": 0
}
},
{
"expressions": [
{
"value": true,
"text": "data.a[i].b.c[_]",
"location": {
"row": 1,
"col": 1
}
}
],
"bindings": {
"i": 1
}
},
{
"expressions": [
{
"value": 1,
"text": "data.a[i].b.c[_]",
"location": {
"row": 1,
"col": 1
}
}
],
"bindings": {
"i": 1
}
}
]
}`
var expected interface{}
if err := util.UnmarshalJSON([]byte(input), &expected); err != nil {
panic(err)
}
repl.OneShot(ctx, "data.a[i].b.c[_]")
var result interface{}
if err := util.UnmarshalJSON(buffer.Bytes(), &result); err != nil {
t.Errorf("Expected valid JSON document: %v: %v", err, buffer.String())
return
}
if !reflect.DeepEqual(expected, result) {
t.Errorf("Expected %v but got: %v", expected, buffer.String())
return
}
buffer.Reset()
repl.OneShot(ctx, "data.deadbeef[x]")
s := buffer.String()
if s != "{}\n" {
t.Errorf("Expected undefined from reference but got: %v", s)
return
}
buffer.Reset()
repl.OneShot(ctx, `p[x] { a = [1, 2, 3, 4]; a[_] = x }`)
buffer.Reset()
repl.OneShot(ctx, "p[x]")
input = `
{
"result": [
{
"expressions": [
{
"value": 1,
"text": "p[x]",
"location": {
"row": 1,
"col": 1
}
}
],
"bindings": {
"x": 1
}
},
{
"expressions": [
{
"value": 2,
"text": "p[x]",
"location": {
"row": 1,
"col": 1
}
}
],
"bindings": {
"x": 2
}
},
{
"expressions": [
{
"value": 3,
"text": "p[x]",
"location": {
"row": 1,
"col": 1
}
}
],
"bindings": {
"x": 3
}
},
{
"expressions": [
{
"value": 4,
"text": "p[x]",
"location": {
"row": 1,
"col": 1
}
}
],
"bindings": {
"x": 4
}
}
]
}
`
if err := util.UnmarshalJSON([]byte(input), &expected); err != nil {
panic(err)
}
if err := util.UnmarshalJSON(buffer.Bytes(), &result); err != nil {
t.Errorf("Expected valid JSON document: %v: %v", err, buffer.String())
return
}
if !reflect.DeepEqual(expected, result) {
t.Errorf("Exepcted %v but got: %v", expected, buffer.String())
}
}
func TestEvalSingleTermMultiValueSetRef(t *testing.T) {
ctx := context.Background()
store := newTestStore()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.outputFormat = "json"
repl.OneShot(ctx, `p[1] { true }`)
repl.OneShot(ctx, `p[2] { true }`)
repl.OneShot(ctx, `q = {3, 4} { true }`)
repl.OneShot(ctx, `r = [x, y] { x = {5, 6}; y = [7, 8] }`)
repl.OneShot(ctx, "p[x]")
expected := parseJSON(`{
"result": [
{
"expressions": [
{
"value": 1,
"text": "p[x]",
"location": {
"row": 1,
"col": 1
}
}
],
"bindings": {
"x": 1
}
},
{
"expressions": [
{
"value": 2,
"text": "p[x]",
"location": {
"row": 1,
"col": 1
}
}
],
"bindings": {
"x": 2
}
}
]
}`)
result := parseJSON(buffer.String())
if !reflect.DeepEqual(result, expected) {
t.Fatalf("Expected %v but got: %v", expected, buffer.String())
}
buffer.Reset()
repl.OneShot(ctx, "q[x]")
expected = parseJSON(`{
"result": [
{
"expressions": [
{
"value": 3,
"text": "q[x]",
"location": {
"row": 1,
"col": 1
}
}
],
"bindings": {
"x": 3
}
},
{
"expressions": [
{
"value": 4,
"text": "q[x]",
"location": {
"row": 1,
"col": 1
}
}
],
"bindings": {
"x": 4
}
}
]
}`)
result = parseJSON(buffer.String())
if !reflect.DeepEqual(result, expected) {
t.Fatalf("Expected %v but got: %v", expected, buffer.String())
}
// Example below shows behavior for ref that iterates an embedded set. The
// tricky part here is that r[_] may refer to multiple collection types. If
// we eventually have a way of distinguishing between the bindings added for
// refs to sets, then those bindings could be filtered out. For now this is
// acceptable, as it should be an edge case.
buffer.Reset()
repl.OneShot(ctx, "r[_][x]")
expected = parseJSON(`{
"result": [
{
"expressions": [
{
"value": 5,
"text": "r[_][x]",
"location": {
"row": 1,
"col": 1
}
}
],
"bindings": {
"x": 5
}
},
{
"expressions": [
{
"value": 6,
"text": "r[_][x]",
"location": {
"row": 1,
"col": 1
}
}
],
"bindings": {
"x": 6
}
},
{
"expressions": [
{
"value": 7,
"text": "r[_][x]",
"location": {
"row": 1,
"col": 1
}
}
],
"bindings": {
"x": 0
}
},
{
"expressions": [
{
"value": 8,
"text": "r[_][x]",
"location": {
"row": 1,
"col": 1
}
}
],
"bindings": {
"x": 1
}
}
]
}`)
result = parseJSON(buffer.String())
if !reflect.DeepEqual(result, expected) {
t.Fatalf("Expected %v but got: %v", expected, buffer.String())
}
}
func TestEvalRuleCompileError(t *testing.T) {
ctx := context.Background()
store := newTestStore()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
err := repl.OneShot(ctx, `p[x] { true }`)
expected := "x is unsafe"
if !strings.Contains(err.Error(), expected) {
t.Errorf("Expected error to contain %v but got: %v (err: %v)", expected, buffer.String(), err)
return
}
buffer.Reset()
err = repl.OneShot(ctx, `p = true { true }`)
result := buffer.String()
if err != nil || result != "Rule 'p' defined in package repl. Type 'show' to see rules.\n" {
t.Errorf("Expected valid rule to compile (because state should be unaffected) but got: %v (err: %v)", result, err)
}
}
func TestEvalBodyCompileError(t *testing.T) {
ctx := context.Background()
store := newTestStore()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.outputFormat = "json"
err := repl.OneShot(ctx, `x = 1; y > x`)
if _, ok := err.(ast.Errors); !ok {
t.Fatalf("Expected error message in output but got`: %v", buffer.String())
}
buffer.Reset()
repl.OneShot(ctx, `x = 1; y = 2; y > x`)
result := util.MustUnmarshalJSON(buffer.Bytes())
exp := util.MustUnmarshalJSON([]byte(`{
"result": [
{
"expressions": [
{
"value": true,
"text": "x = 1",
"location": {
"row": 1,
"col": 1
}
},
{
"value": true,
"text": "y = 2",
"location": {
"row": 1,
"col": 8
}
},
{
"value": true,
"text": "y \u003e x",
"location": {
"row": 1,
"col": 15
}
}
],
"bindings": {
"x": 1,
"y": 2
}
}
]
}`))
if !reflect.DeepEqual(exp, result) {
t.Errorf(`Expected %v but got: %v"`, exp, buffer.String())
return
}
}
func TestEvalBodyContainingWildCards(t *testing.T) {
ctx := context.Background()
store := newTestStore()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.OneShot(ctx, "data.a[_].b.c[_] = x")
expected := strings.TrimSpace(`
+-------+
| x |
+-------+
| true |
| 2 |
| false |
| false |
| true |
| 1 |
+-------+`)
result := strings.TrimSpace(buffer.String())
if result != expected {
t.Errorf("Expected only a single column of output but got:\n%v", result)
}
}
func TestEvalBodyInput(t *testing.T) {
ctx := context.Background()
store := newTestStore()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.OneShot(ctx, `package repl`)
repl.OneShot(ctx, `input["foo.bar"] = "hello" { true }`)
repl.OneShot(ctx, `input["baz"] = data.a[0].b.c[2] { true }`)
repl.OneShot(ctx, `package test`)
repl.OneShot(ctx, "import input.baz")
repl.OneShot(ctx, `p = true { input["foo.bar"] = "hello"; baz = false }`)
buffer.Reset()
repl.OneShot(ctx, "p")
result := buffer.String()
if result != "true\n" {
t.Fatalf("expected true but got: %v", result)
}
}
func TestEvalBodyInputComplete(t *testing.T) {
ctx := context.Background()
store := newTestStore()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
// Test that input can be defined completely:
// https://github.com/open-policy-agent/opa/issues/231
repl.OneShot(ctx, `package repl`)
repl.OneShot(ctx, `input = 1`)
buffer.Reset()
repl.OneShot(ctx, `input`)
result := buffer.String()
if result != "1\n" {
t.Fatalf("Expected 1 but got: %v", result)
}
buffer.Reset()
// Test that input is as expected
repl.OneShot(ctx, `package ex1`)
repl.OneShot(ctx, `x = input`)
buffer.Reset()
repl.OneShot(ctx, `x`)
result = buffer.String()
if result != "1\n" {
t.Fatalf("Expected 1 but got: %v", result)
}
// Test that local input replaces other inputs
repl.OneShot(ctx, `package ex2`)
repl.OneShot(ctx, `input = 2`)
buffer.Reset()
repl.OneShot(ctx, `input`)
result = buffer.String()
if result != "2\n" {
t.Fatalf("Expected 2 but got: %v", result)
}
buffer.Reset()
// Test that original input is intact
repl.OneShot(ctx, `package ex3`)
repl.OneShot(ctx, `input`)
result = buffer.String()
if result != "1\n" {
t.Fatalf("Expected 1 but got: %v", result)
}
// Test that deferencing undefined input results in undefined
buffer.Reset()
repl = newRepl(store, &buffer)
repl.OneShot(ctx, `input.p`)
result = buffer.String()
if result != "undefined\n" {
t.Fatalf("Expected undefined but got: %v", result)
}
buffer.Reset()
repl.OneShot(ctx, `input.p = false`)
result = buffer.String()
if result != "undefined\n" {
t.Fatalf("Expected undefined but got: %v", result)
}
}
func TestEvalBodyWith(t *testing.T) {
ctx := context.Background()
store := newTestStore()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.OneShot(ctx, `p = true { input.foo = "bar" }`)
buffer.Reset()
repl.OneShot(ctx, "p")
if buffer.String() != "undefined\n" {
t.Fatalf("Expected undefined but got: %v", buffer.String())
}
buffer.Reset()
repl.OneShot(ctx, `p with input.foo as "bar"`)
result := buffer.String()
expected := "true\n"
if result != expected {
t.Fatalf("Expected true but got: %v", result)
}
}
func TestEvalBodyRewrittenBuiltin(t *testing.T) {
ctx := context.Background()
store := newTestStore()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.OneShot(ctx, "json")
repl.OneShot(ctx, `p[x] { a[x]; a = [1,2,3,4] }`)
repl.OneShot(ctx, "p[x] > 1")
result := util.MustUnmarshalJSON(buffer.Bytes())
expected := util.MustUnmarshalJSON([]byte(`{
"result": [
{
"expressions": [
{
"value": true,
"text": "p[x] \u003e 1",
"location": {
"row": 1,
"col": 1
}
}
],
"bindings": {
"x": 2
}
},
{
"expressions": [
{
"value": true,
"text": "p[x] \u003e 1",
"location": {
"row": 1,
"col": 1
}
}
],
"bindings": {
"x": 3
}
}
]
}`))
if util.Compare(result, expected) != 0 {
t.Fatalf("Expected %v but got: %v", expected, buffer.String())
}
}
func TestEvalBodyRewrittenRef(t *testing.T) {
ctx := context.Background()
store := newTestStore()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.OneShot(ctx, "json")
repl.OneShot(ctx, `i = 1`)
repl.OneShot(ctx, `data.a[0].b.c[i]`)
result := util.MustUnmarshalJSON(buffer.Bytes())
expected := util.MustUnmarshalJSON([]byte(`{
"result": [
{
"expressions": [
{
"value": 2,
"text": "data.a[0].b.c[i]",
"location": {
"row": 1,
"col": 1
}
}
]
}
]
}`))
if util.Compare(result, expected) != 0 {
t.Fatalf("Expected %v but got: %v", expected, buffer.String())
}
buffer.Reset()
repl.OneShot(ctx, "p = {1,2,3}")
repl.OneShot(ctx, "p")
result = util.MustUnmarshalJSON(buffer.Bytes())
expected = util.MustUnmarshalJSON([]byte(`{
"result": [
{
"expressions": [
{
"value": [
1,
2,
3
],
"text": "p",
"location": {
"row": 1,
"col": 1
}
}
]
}
]
}`))
if util.Compare(result, expected) != 0 {
t.Fatalf("Expected %v but got: %v", expected, buffer.String())
}
buffer.Reset()
repl.OneShot(ctx, "p[x]")
result = util.MustUnmarshalJSON(buffer.Bytes())
expected = util.MustUnmarshalJSON([]byte(`{
"result": [
{
"expressions": [
{
"value": 1,
"text": "p[x]",
"location": {
"row": 1,
"col": 1
}
}
],
"bindings": {
"x": 1
}
},
{
"expressions": [
{
"value": 2,
"text": "p[x]",
"location": {
"row": 1,
"col": 1
}
}
],
"bindings": {
"x": 2
}
},
{
"expressions": [
{
"value": 3,
"text": "p[x]",
"location": {
"row": 1,
"col": 1
}
}
],
"bindings": {
"x": 3
}
}
]
}`))
if util.Compare(result, expected) != 0 {
t.Fatalf("Expected %v but got: %v", expected, buffer.String())
}
}
func TestEvalBodySomeDecl(t *testing.T) {
ctx := context.Background()
store := newTestStore()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.OneShot(ctx, "json")
repl.OneShot(ctx, "some x; x = 1")
exp := util.MustUnmarshalJSON([]byte(`{
"result": [
{
"expressions": [
{
"value": true,
"text": "x = 1",
"location": {
"row": 1,
"col": 9
}
}
],
"bindings": {
"x": 1
}
}
]
}`))
result := util.MustUnmarshalJSON(buffer.Bytes())
if util.Compare(result, exp) != 0 {
t.Fatalf("Expected %v but got: %v", exp, result)
}
}
func TestEvalImport(t *testing.T) {
ctx := context.Background()
store := newTestStore()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.OneShot(ctx, "import data.a")
if len(buffer.Bytes()) != 0 {
t.Errorf("Expected no output but got: %v", buffer.String())
return
}
buffer.Reset()
repl.OneShot(ctx, "a[0].b.c[0] = true")
result := buffer.String()
expected := "true\n"
if result != expected {
t.Errorf("Expected expression to evaluate successfully but got: %v", result)
return
}
// https://github.com/open-policy-agent/opa/issues/158 - re-run query to
// make sure import is not lost
buffer.Reset()
repl.OneShot(ctx, "a[0].b.c[0] = true")
result = buffer.String()
expected = "true\n"
if result != expected {
t.Fatalf("Expected expression to evaluate successfully but got: %v", result)
}
}
func TestEvalPackage(t *testing.T) {
ctx := context.Background()
store := newTestStore()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.OneShot(ctx, `package foo.bar`)
repl.OneShot(ctx, `p = true { true }`)
repl.OneShot(ctx, `package baz.qux`)
buffer.Reset()
err := repl.OneShot(ctx, "p")
if !strings.Contains(err.Error(), "p is unsafe") {
t.Fatalf("Expected unsafe variable error but got: %v", err)
}
repl.OneShot(ctx, "import data.foo.bar.p")
buffer.Reset()
repl.OneShot(ctx, "p")
if buffer.String() != "true\n" {
t.Errorf("Expected expression to eval successfully but got: %v", buffer.String())
return
}
}
func TestMetrics(t *testing.T) {
ctx := context.Background()
store := newTestStore()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.OneShot(ctx, "a = {[1,2], [3,4]}")
repl.OneShot(ctx, "metrics")
repl.OneShot(ctx, `[x | a[x]]`)
if !strings.Contains(buffer.String(), "timer_rego_query_compile_ns") {
t.Fatal("Expected output to contain well known metric key but got:", buffer.String())
}
buffer.Reset()
repl.OneShot(ctx, `[x | a[x]]`)
if !strings.Contains(buffer.String(), "timer_rego_query_compile_ns") {
t.Fatal("Expected output to contain well known metric key but got:", buffer.String())
}
buffer.Reset()
repl.OneShot(ctx, "metrics")
repl.OneShot(ctx, `[x | a[x]]`)
expected := `[
[
1,
2
],
[
3,
4
]
]
`
if expected != buffer.String() {
t.Fatalf("Expected output to be exactly:\n%v\n\nGot:\n\n%v\n", expected, buffer.String())
}
}
func TestProfile(t *testing.T) {
store := newTestStore()
ctx := context.Background()
txn := storage.NewTransactionOrDie(ctx, store, storage.WriteParams)
const numLines = 21
mod2 := []byte(`package rbac
input = {
"subject": "bob",
"resource": "foo123",
"action": "write",
}
bindings = [
{
"user": "alice",
"roles": ["dev", "test"],
},
{
"user": "bob",
"roles": ["test"],
},
]
roles = [
{
"name": "dev",
"permissions": [
{"resource": "foo123", "action": "write"},
{"resource": "foo123", "action": "read"},
],
},
{
"name": "test",
"permissions": [{"resource": "foo123", "action": "read"}],
},
]
default allow = false
allow {
user_has_role[role_name]
role_has_permission[role_name]
}
user_has_role[role_name] {
binding := bindings[_]
binding.user = input.subject
role_name := binding.roles[_]
}
role_has_permission[role_name] {
role := roles[_]
role_name := role.name
perm := role.permissions[_]
perm.resource = input.resource
perm.action = input.action
}`)
if err := store.UpsertPolicy(ctx, txn, "mod2", mod2); err != nil {
panic(err)
}
if err := store.Commit(ctx, txn); err != nil {
panic(err)
}
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.OneShot(ctx, "profile")
repl.OneShot(ctx, "data.rbac.allow")
result := buffer.String()
lines := strings.Split(result, "\n")
if len(lines) != numLines {
t.Fatal("Expected 21 lines, got :", len(lines))
}
buffer.Reset()
}
func TestInstrument(t *testing.T) {
ctx := context.Background()
store := newTestStore()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
// Turn on instrumentation w/o turning on metrics.
repl.OneShot(ctx, "instrument")
repl.OneShot(ctx, "true")
result := buffer.String()
if !strings.Contains(result, "histogram_eval_op_plug") {
t.Fatal("Expected plug histogram in output but got:", result)
}
buffer.Reset()
// Turn off instrumentation.
repl.OneShot(ctx, "instrument")
repl.OneShot(ctx, "true")
result = buffer.String()
if strings.Contains(result, "histogram_eval_op_plug") {
t.Fatal("Expected instrumentation to be turned off but got:", result)
}
buffer.Reset()
// Turn on metrics and then turn on instrumentation.
repl.OneShot(ctx, "metrics")
repl.OneShot(ctx, "true")
result = buffer.String()
if strings.Contains(result, "histogram_eval_op_plug") {
t.Fatal("Expected instrumentation to be turned off but got:", result)
}
if !strings.Contains(result, "timer_rego_query_eval_ns") {
t.Fatal("Expected metrics to be turned on but got:", result)
}
buffer.Reset()
repl.OneShot(ctx, "instrument")
repl.OneShot(ctx, "true")
result = buffer.String()
if !strings.Contains(result, "histogram_eval_op_plug") {
t.Fatal("Expected instrumentation to be turned on but got:", result)
}
if !strings.Contains(result, "timer_rego_query_eval_ns") {
t.Fatal("Expected metrics to be turned on but got:", result)
}
}
func TestEvalTrace(t *testing.T) {
ctx := context.Background()
store := newTestStore()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.OneShot(ctx, "trace")
repl.OneShot(ctx, `data.a[i].b.c[j] = x; data.a[k].b.c[x] = 1`)
expected := strings.TrimSpace(`
query:1 Enter data.a[i].b.c[j] = x; data.a[k].b.c[x] = 1
query:1 | Eval data.a[i].b.c[j] = x
query:1 | Eval data.a[k].b.c[x] = 1
query:1 | Fail data.a[k].b.c[x] = 1
query:1 | Redo data.a[i].b.c[j] = x
query:1 | Eval data.a[k].b.c[x] = 1
query:1 | Exit data.a[i].b.c[j] = x; data.a[k].b.c[x] = 1
query:1 Redo data.a[i].b.c[j] = x; data.a[k].b.c[x] = 1
query:1 | Redo data.a[k].b.c[x] = 1
query:1 | Redo data.a[i].b.c[j] = x
query:1 | Eval data.a[k].b.c[x] = 1
query:1 | Fail data.a[k].b.c[x] = 1
query:1 | Redo data.a[i].b.c[j] = x
query:1 | Eval data.a[k].b.c[x] = 1
query:1 | Fail data.a[k].b.c[x] = 1
query:1 | Redo data.a[i].b.c[j] = x
query:1 | Eval data.a[k].b.c[x] = 1
query:1 | Fail data.a[k].b.c[x] = 1
query:1 | Redo data.a[i].b.c[j] = x
query:1 | Eval data.a[k].b.c[x] = 1
query:1 | Fail data.a[k].b.c[x] = 1
query:1 | Redo data.a[i].b.c[j] = x
+---+---+---+---+
| i | j | k | x |
+---+---+---+---+
| 0 | 1 | 1 | 2 |
+---+---+---+---+`)
expected += "\n"
if expected != buffer.String() {
t.Fatalf("Expected output to be exactly:\n%v\n\nGot:\n\n%v\n", expected, buffer.String())
}
}
func TestEvalNotes(t *testing.T) {
ctx := context.Background()
store := newTestStore()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.OneShot(ctx, `p { a = [1,2,3]; a[i] = x; x > 1; trace(sprintf("x = %d", [x])) }`)
repl.OneShot(ctx, "notes")
buffer.Reset()
repl.OneShot(ctx, "p")
expected := strings.TrimSpace(`query:1 Enter data.repl.p = _
query:1 | Enter data.repl.p
note | | Note "x = 2"
query:1 Redo data.repl.p = _
query:1 | Redo data.repl.p
note | | Note "x = 3"
true`)
expected += "\n"
if expected != buffer.String() {
t.Fatalf("Expected output to be exactly:\n%v\n\nGot:\n\n%v\n", expected, buffer.String())
}
}
func TestTruncatePrettyOutput(t *testing.T) {
ctx := context.Background()
store := inmem.New()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.prettyLimit = 1000 // crank up limit to test repl command
repl.OneShot(ctx, "pretty-limit 80")
repl.OneShot(ctx, "data[x]")
for _, line := range strings.Split(buffer.String(), "\n") {
// | "repl" | {"version": <elided>... |
if len(line) > 96 {
t.Fatalf("Expected len(line) to be < 96 but got:\n\n%v", buffer)
}
}
buffer.Reset()
if err := repl.OneShot(ctx, "pretty-limit"); err == nil || !strings.Contains(err.Error(), "usage: pretty-limit <n>") {
t.Fatalf("Expected usage error but got: %v", err)
}
}
func TestUnsetPackage(t *testing.T) {
ctx := context.Background()
store := inmem.New()
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.OneShot(ctx, "package a")
if err := repl.OneShot(ctx, `unset-package 5`); err == nil {
t.Fatalf("Expected package-unset error for bad package but got: %v", buffer.String())
}
buffer.Reset()
repl.OneShot(ctx, "package a")
repl.OneShot(ctx, "unset-package b")
if buffer.String() != "warning: no matching package\n" {
t.Fatalf("Expected unset-package warning no matching package but got: %v", buffer.String())
}
buffer.Reset()
repl.OneShot(ctx, `package a`)
if err := repl.OneShot(ctx, `unset-package b`); err != nil {
t.Fatalf("Expected unset-package to succeed for input: %v", err)
}
buffer.Reset()
repl.OneShot(ctx, "package a")
repl.OneShot(ctx, "unset-package a")
repl.OneShot(ctx, "show")
if buffer.String() != "no rules defined\n" {
t.Fatalf("Expected unset-package to return to default but got: %v", buffer.String())
}
}
func assertREPLText(t *testing.T, buf bytes.Buffer, expected string) {
t.Helper()
result := buf.String()
if result != expected {
t.Fatalf("Expected:\n%v\n\nString:\n\n%v\nGot:\n%v\n\nString:\n\n%v", []byte(expected), expected, []byte(result), result)
}
}
func expectOutput(t *testing.T, output string, expected string) {
t.Helper()
if output != expected {
t.Errorf("Repl output: expected %#v but got %#v", expected, output)
}
}
func newRepl(store storage.Store, buffer *bytes.Buffer) *REPL {
repl := New(store, "", buffer, "", 0, "")
return repl
}
func newTestStore() storage.Store {
input := `
{
"a": [
{
"b": {
"c": [true,2,false]
}
},
{
"b": {
"c": [false,true,1]
}
}
]
}
`
var data map[string]interface{}
err := util.UnmarshalJSON([]byte(input), &data)
if err != nil {
panic(err)
}
return inmem.NewFromObject(data)
}
func parseJSON(s string) interface{} {
var v interface{}
if err := util.UnmarshalJSON([]byte(s), &v); err != nil {
panic(err)
}
return v
}