mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-24 09:15:31 -06:00
671fa2aab9
This commit adds initial support for varargs on function declarations. Varargs are only supported on void functions for now. In the future, varargs can be supported on any function, but care will have to be take in the compiler to handle calls that make use of the legacy calling convention for functions where the output is captured in the argument list (e.g., plus(1,2,3)). Signed-off-by: Torin Sandall <torinsandall@gmail.com>
493 lines
12 KiB
Go
493 lines
12 KiB
Go
// Copyright 2017 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 types
|
|
|
|
import (
|
|
"encoding/json"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/open-policy-agent/opa/util"
|
|
)
|
|
|
|
var dynamicPropertyAnyAny = NewDynamicProperty(A, A)
|
|
|
|
func TestAnySorted(t *testing.T) {
|
|
a := NewAny(S, N)
|
|
if Compare(a[0], N) != 0 {
|
|
t.Fatal("expected any type to be sorted")
|
|
}
|
|
}
|
|
|
|
func TestAnyMerge(t *testing.T) {
|
|
x := NewAny(S, B)
|
|
|
|
if Compare(x.Merge(N)[1], N) != 0 {
|
|
t.Fatal("expected number to be inserted into middle")
|
|
}
|
|
|
|
if Compare(x.Merge(NewNull())[0], NewNull()) != 0 {
|
|
t.Fatal("expected null to be inserted at front")
|
|
}
|
|
|
|
if Compare(x.Merge(NewArray(nil, A))[2], NewArray(nil, A)) != 0 {
|
|
t.Fatal("expected array to be inserted at back")
|
|
}
|
|
}
|
|
|
|
func TestAnyUnion(t *testing.T) {
|
|
x := NewAny(NewNull(), N)
|
|
y := NewAny(S, B)
|
|
z := x.Union(y)
|
|
exp := []Type{NewNull(), B, N, S}
|
|
if len(z) != len(exp) {
|
|
t.Fatalf("expected %v elements in result of union", len(exp))
|
|
}
|
|
for i := range z {
|
|
if Compare(z[i], exp[i]) != 0 {
|
|
t.Fatal("expected", exp[i], "but got", z[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStrings(t *testing.T) {
|
|
|
|
tpe := NewObject([]*StaticProperty{
|
|
{"foo", NewNull()},
|
|
{"bar", NewBoolean()},
|
|
{"baz", NewNumber()},
|
|
{"qux", NewString()},
|
|
{"corge", NewArray(
|
|
[]Type{
|
|
NewAny(),
|
|
NewAny(NewNull(), NewString()),
|
|
NewSet(NewString()),
|
|
}, NewString(),
|
|
)},
|
|
{"nil", nil},
|
|
}, NewDynamicProperty(S, N))
|
|
|
|
expected := `object<bar: boolean, baz: number, corge: array<any, any<null, string>, set[string]>[string], foo: null, nil: ???, qux: string>[string: number]`
|
|
|
|
if tpe.String() != expected {
|
|
t.Fatalf("Expected %v but got: %v", expected, tpe)
|
|
}
|
|
|
|
ftpe := NewFunction([]Type{S, S}, N)
|
|
expected = "(string, string) => number"
|
|
|
|
if ftpe.String() != expected {
|
|
t.Fatalf("Expected %v but got: %v", expected, ftpe)
|
|
}
|
|
|
|
ftpe = NewVariadicFunction([]Type{N}, S, nil)
|
|
expected = "(number, string...) => ???"
|
|
|
|
if ftpe.String() != expected {
|
|
t.Fatal("expected", expected, "but got:", ftpe)
|
|
}
|
|
}
|
|
|
|
func TestCompare(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
a Type
|
|
b Type
|
|
cmp int
|
|
}{
|
|
{NewNull(), NewNull(), 0},
|
|
{NewNull(), NewBoolean(), -1},
|
|
{NewBoolean(), NewNull(), 1},
|
|
{NewBoolean(), NewBoolean(), 0},
|
|
{NewBoolean(), NewNumber(), -1},
|
|
{NewNumber(), NewNumber(), 0},
|
|
{NewNumber(), NewString(), -1},
|
|
{NewString(), NewString(), 0},
|
|
{NewString(), NewArray(NewAny(), nil), -1},
|
|
{NewArray(NewAny(), nil), NewArray(NewAny(), NewAny()), -1},
|
|
{NewArray(NewAny(), NewAny()), NewArray(NewAny(), NewAny()), 0},
|
|
{NewArray(NewAny(), NewAny()), NewArray(NewAny(), NewString()), 1},
|
|
{NewArray(NewAny(), NewAny()), NewArray(NewAny(), nil), 1},
|
|
{NewArray([]Type{NewString()}, nil), NewArray([]Type{NewNumber()}, nil), 1},
|
|
{NewObject(nil, nil), NewObject(nil, dynamicPropertyAnyAny), -1},
|
|
{NewObject(nil, dynamicPropertyAnyAny), NewObject(nil, nil), 1},
|
|
{NewObject(nil, dynamicPropertyAnyAny), NewObject(nil, dynamicPropertyAnyAny), 0},
|
|
{NewObject(nil, NewDynamicProperty(S, NewAny(NewString(), NewNull()))), NewObject(nil, dynamicPropertyAnyAny), -1},
|
|
{NewSet(NewNull()), NewSet(NewAny()), -1},
|
|
{
|
|
NewObject(
|
|
[]*StaticProperty{{"foo", NewString()}},
|
|
nil),
|
|
NewObject(
|
|
[]*StaticProperty{{"foo", NewString()}, {"bar", NewNumber()}},
|
|
nil),
|
|
1,
|
|
},
|
|
{
|
|
NewObject(
|
|
[]*StaticProperty{{"foo", NewString()}, {"bar", NewNumber()}},
|
|
nil),
|
|
NewObject(
|
|
[]*StaticProperty{{"foo", NewString()}},
|
|
nil),
|
|
-1,
|
|
},
|
|
{
|
|
NewObject(
|
|
[]*StaticProperty{{"foo", NewString()}},
|
|
nil),
|
|
NewObject(
|
|
[]*StaticProperty{{"foo", NewNull()}},
|
|
nil),
|
|
1,
|
|
},
|
|
{
|
|
NewObject(
|
|
[]*StaticProperty{{"foo", NewString()}},
|
|
nil),
|
|
NewObject(
|
|
[]*StaticProperty{{"foo", NewString()}, {"foo-2", NewNumber()}},
|
|
nil),
|
|
-1,
|
|
},
|
|
{
|
|
NewObject(
|
|
[]*StaticProperty{{"foo", NewString()}, {"foo-2", NewNumber()}},
|
|
nil),
|
|
NewObject(
|
|
[]*StaticProperty{{"foo", NewString()}},
|
|
nil),
|
|
1,
|
|
},
|
|
{NewFunction(nil, nil), NewAny(), 1},
|
|
{NewFunction([]Type{B}, N), NewFunction([]Type{S}, N), -1},
|
|
{NewFunction(nil, S), NewFunction(nil, N), 1},
|
|
{NewFunction(nil, S), NewFunction([]Type{N}, S), -1},
|
|
{NewFunction([]Type{S}, N), NewFunction(nil, S), 1},
|
|
{NewFunction([]Type{S}, N), NewFunction([]Type{S}, N), 0},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
result := Compare(tc.a, tc.b)
|
|
if result != tc.cmp {
|
|
t.Fatalf("For Compare(%v, %v) expected %v but got: %v", tc.a, tc.b, tc.cmp, result)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContains(t *testing.T) {
|
|
tests := []struct {
|
|
a Type
|
|
b Type
|
|
expected bool
|
|
}{
|
|
{S, S, true},
|
|
{A, S, true},
|
|
{NewAny(N, B), S, false},
|
|
{N, S, false},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
if Contains(tc.a, tc.b) != tc.expected {
|
|
t.Fatalf("Expected Contains(%v, %v) == %v", tc.a, tc.b, tc.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestOr(t *testing.T) {
|
|
tests := []struct {
|
|
a Type
|
|
b Type
|
|
expected Type
|
|
}{
|
|
{nil, NewString(), NewString()},
|
|
{NewString(), nil, NewString()},
|
|
{NewNull(), NewNull(), NewNull()},
|
|
{NewString(), NewNumber(), NewAny(NewNumber(), NewString())},
|
|
{NewAny(), NewNull(), NewAny()},
|
|
{NewNull(), NewAny(), NewAny()},
|
|
{NewNull(), NewAny(NewString(), NewNumber()), NewAny(NewString(), NewNumber(), NewNull())},
|
|
{NewAny(), NewAny(), NewAny()},
|
|
{NewAny(NewNull(), NewNumber()), NewAny(), NewAny()},
|
|
{NewAny(NewNumber(), NewString()), NewAny(NewNull(), NewBoolean()), NewAny(NewNull(), NewBoolean(), NewString(), NewNumber())},
|
|
{NewAny(NewNull(), NewNumber()), NewNull(), NewAny(NewNull(), NewNumber())},
|
|
{NewFunction([]Type{S}, B), NewFunction([]Type{N}, B), NewFunction([]Type{NewAny(S, N)}, B)},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
c := Or(tc.a, tc.b)
|
|
if Compare(c, tc.expected) != 0 {
|
|
t.Fatalf("Expected Or(%v, %v) to be %v but got: %v", tc.a, tc.b, tc.expected, c)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func TestSelect(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
note string
|
|
a Type
|
|
k interface{}
|
|
expected Type
|
|
}{
|
|
{"static", NewArray([]Type{S}, nil), json.Number("0"), S},
|
|
{"dynamic", NewArray(nil, S), json.Number("100"), S},
|
|
{"out of range", NewArray([]Type{S, N, B}, nil), json.Number("4"), nil},
|
|
{"out of range negative", NewArray([]Type{S, N, B}, nil), json.Number("-4"), nil},
|
|
{"negative", NewArray([]Type{S, N, B}, nil), json.Number("-2"), nil},
|
|
{"non int", NewArray([]Type{S, N, B}, nil), json.Number("1.5"), nil},
|
|
{"non int-2", NewArray([]Type{S, N, B}, nil), 1, nil},
|
|
{"static", NewObject([]*StaticProperty{NewStaticProperty("hello", S)}, nil), "hello", S},
|
|
{"dynamic", NewObject([]*StaticProperty{NewStaticProperty("hello", S)}, NewDynamicProperty(S, N)), "goodbye", N},
|
|
{"non exist", NewObject([]*StaticProperty{NewStaticProperty("hello", S)}, nil), "deadbeef", nil},
|
|
{"non string", NewObject([]*StaticProperty{NewStaticProperty(json.Number("1"), S), NewStaticProperty(json.Number("2"), N)}, nil), json.Number("2"), N},
|
|
{"member of", NewSet(N), json.Number("2"), N},
|
|
{"non exist", NewSet(N), "foo", nil},
|
|
{"superset", A, A, A},
|
|
{"union", NewAny(NewArray(nil, N), NewArray(nil, S)), json.Number("10"), NewAny(N, S)},
|
|
{"union set", NewSet(NewAny(S, N)), json.Number("1"), N},
|
|
{"scalar", N, "1", nil},
|
|
{"scalar-2", S, "1", nil},
|
|
{"scalar-3", B, "1", nil},
|
|
{"scalar-4", NewNull(), "1", nil},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.note, func(t *testing.T) {
|
|
result := Select(tc.a, tc.k)
|
|
if Compare(result, tc.expected) != 0 {
|
|
t.Fatalf("Expected Select(%v, %v) to be %v but got: %v", tc.a, tc.k, tc.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestKeys(t *testing.T) {
|
|
tests := []struct {
|
|
note string
|
|
tpe Type
|
|
expected Type
|
|
}{
|
|
{"array", NewArray(nil, nil), N},
|
|
{"object", NewObject(nil, NewDynamicProperty(S, S)), S},
|
|
{"set", NewSet(N), N},
|
|
{"any", NewAny(NewArray(nil, nil), NewSet(S)), NewAny(S, N)},
|
|
{"any", NewAny(NewArray(nil, nil), S), N},
|
|
{"superset", A, A},
|
|
{"scalar-1", N, nil},
|
|
{"scalar-2", S, nil},
|
|
{"scalar-3", B, nil},
|
|
{"scalar-4", NewNull(), nil},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.note, func(t *testing.T) {
|
|
result := Keys(tc.tpe)
|
|
if Compare(result, tc.expected) != 0 {
|
|
t.Fatalf("Expected Keys(%v) to be %v but got: %v", tc.tpe, tc.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValues(t *testing.T) {
|
|
tests := []struct {
|
|
note string
|
|
tpe Type
|
|
expected Type
|
|
}{
|
|
{"array", NewArray([]Type{N}, nil), N},
|
|
{"array dynamic", NewArray([]Type{N, S}, B), NewAny(S, N, B)},
|
|
{"object", NewObject([]*StaticProperty{NewStaticProperty("a", S), NewStaticProperty("b", N)}, nil), NewAny(S, N)},
|
|
{"object dynamic", NewObject([]*StaticProperty{NewStaticProperty("a", S), NewStaticProperty("b", N)}, NewDynamicProperty(A, B)), NewAny(S, N, B)},
|
|
{"set", NewSet(N), N},
|
|
{"superset", A, A},
|
|
{"any", NewAny(NewArray(nil, N), S), N},
|
|
{"scalar-1", N, nil},
|
|
{"scalar-2", S, nil},
|
|
{"scalar-3", B, nil},
|
|
{"scalar-4", NewNull(), nil},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.note, func(t *testing.T) {
|
|
result := Values(tc.tpe)
|
|
if Compare(result, tc.expected) != 0 {
|
|
t.Fatalf("Expected Keys(%v) to be %v but got: %v", tc.tpe, tc.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTypeOf(t *testing.T) {
|
|
tpe := TypeOf(map[interface{}]interface{}{
|
|
"foo": []interface{}{
|
|
json.Number("1"), true, nil, "hello",
|
|
},
|
|
})
|
|
|
|
exp := NewObject([]*StaticProperty{
|
|
NewStaticProperty("foo", NewArray(
|
|
[]Type{
|
|
N, B, NewNull(), S,
|
|
}, nil,
|
|
)),
|
|
}, nil)
|
|
|
|
if Compare(exp, tpe) != 0 {
|
|
t.Fatalf("Expected %v but got: %v", exp, tpe)
|
|
}
|
|
}
|
|
|
|
func TestTypeOfMapOfString(t *testing.T) {
|
|
tpe := TypeOf(map[string]interface{}{
|
|
"foo": "bar",
|
|
"baz": "qux",
|
|
})
|
|
|
|
exp := NewObject([]*StaticProperty{
|
|
NewStaticProperty("foo", S),
|
|
NewStaticProperty("baz", S),
|
|
}, nil)
|
|
|
|
if Compare(exp, tpe) != 0 {
|
|
t.Fatalf("Expected %v but got: %v", exp, tpe)
|
|
}
|
|
}
|
|
|
|
func TestNil(t *testing.T) {
|
|
|
|
tpe := NewObject([]*StaticProperty{
|
|
NewStaticProperty("foo", NewArray(
|
|
[]Type{
|
|
N, B, NewNull(), S, NewSet(nil),
|
|
}, nil,
|
|
)),
|
|
}, nil)
|
|
|
|
if !Nil(tpe) {
|
|
t.Fatalf("Expected %v type to be unknown", tpe)
|
|
}
|
|
|
|
}
|
|
|
|
func TestMarshalJSON(t *testing.T) {
|
|
|
|
tpe := NewAny(
|
|
NewObject(
|
|
[]*StaticProperty{
|
|
{"foo", N},
|
|
{"func", NewFunction([]Type{S}, N)},
|
|
},
|
|
NewDynamicProperty(S, NewArray([]Type{NewSet(B)}, N)),
|
|
),
|
|
)
|
|
|
|
bs, err := json.Marshal(tpe)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
expected := util.MustUnmarshalJSON([]byte(`
|
|
{
|
|
"type": "any",
|
|
"of": [
|
|
{
|
|
"type": "object",
|
|
"static": [
|
|
{
|
|
"key": "foo",
|
|
"value": {"type": "number"}
|
|
},
|
|
{
|
|
"key": "func",
|
|
"value": {
|
|
"type": "function",
|
|
"args": [
|
|
{
|
|
"type": "string"
|
|
}
|
|
],
|
|
"result": {
|
|
"type": "number"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"dynamic": {
|
|
"key": {"type": "string"},
|
|
"value": {
|
|
"type": "array",
|
|
"static": [
|
|
{
|
|
"type": "set",
|
|
"of": {"type": "boolean"}
|
|
}
|
|
],
|
|
"dynamic": {"type": "number"}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
`))
|
|
|
|
result := util.MustUnmarshalJSON(bs)
|
|
|
|
if !reflect.DeepEqual(expected, result) {
|
|
t.Fatalf("Expected:\n\n%s\n\nGot:\n\n%s", util.MustMarshalJSON(expected), util.MustMarshalJSON(result))
|
|
}
|
|
|
|
}
|
|
|
|
func TestRoundtripJSON(t *testing.T) {
|
|
tpe := NewFunction([]Type{
|
|
NewArray([]Type{S, NewNull()}, N),
|
|
NewObject(
|
|
[]*StaticProperty{
|
|
NewStaticProperty("foo", B),
|
|
},
|
|
NewDynamicProperty(S, NewSet(N))),
|
|
NewObject(
|
|
[]*StaticProperty{
|
|
NewStaticProperty("bar", N),
|
|
},
|
|
nil,
|
|
),
|
|
}, NewAny(S, N))
|
|
|
|
bs, err := json.Marshal(tpe)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
result, err := Unmarshal(bs)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if Compare(result, tpe) != 0 {
|
|
t.Fatalf("Got: %v\n\nExpected: %v", result, tpe)
|
|
}
|
|
}
|
|
|
|
func TestRoundtripJSONVariadicFunction(t *testing.T) {
|
|
tpe := NewVariadicFunction([]Type{S}, N, nil)
|
|
bs, err := json.Marshal(tpe)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
result, err := Unmarshal(bs)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if Compare(result, tpe) != 0 {
|
|
t.Fatalf("Got: %v\n\nExpected: %v", result, tpe)
|
|
}
|
|
}
|