714 lines
33 KiB
Go
714 lines
33 KiB
Go
// Copyright 2016-2020, Pulumi Corporation.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package model
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
func testTraverse(t *testing.T, receiver Traversable, traverser hcl.Traverser, expected Traversable, expectDiags bool) {
|
|
actual, diags := receiver.Traverse(traverser)
|
|
assert.Equal(t, expected, actual)
|
|
if expectDiags {
|
|
assert.Greater(t, len(diags), 0)
|
|
} else {
|
|
assert.Equal(t, 0, len(diags))
|
|
}
|
|
}
|
|
|
|
func TestDynamicType(t *testing.T) {
|
|
// Test that DynamicType is assignable to and from itself.
|
|
assert.True(t, DynamicType.AssignableFrom(DynamicType))
|
|
|
|
// Test that DynamicType is assignable from any type.
|
|
assert.True(t, DynamicType.AssignableFrom(BoolType))
|
|
assert.True(t, DynamicType.AssignableFrom(IntType))
|
|
assert.True(t, DynamicType.AssignableFrom(NumberType))
|
|
assert.True(t, DynamicType.AssignableFrom(StringType))
|
|
|
|
assert.True(t, DynamicType.AssignableFrom(NewOptionalType(BoolType)))
|
|
assert.True(t, DynamicType.AssignableFrom(NewOutputType(BoolType)))
|
|
assert.True(t, DynamicType.AssignableFrom(NewPromiseType(BoolType)))
|
|
assert.True(t, DynamicType.AssignableFrom(NewMapType(BoolType)))
|
|
assert.True(t, DynamicType.AssignableFrom(NewListType(BoolType)))
|
|
assert.True(t, DynamicType.AssignableFrom(NewUnionType(BoolType, IntType)))
|
|
assert.True(t, DynamicType.AssignableFrom(NewObjectType(map[string]Type{
|
|
"bool": BoolType,
|
|
"int": IntType,
|
|
})))
|
|
|
|
// Test that DynamicType is assignable to certain types and not assignable to others.
|
|
assert.True(t, NewOptionalType(DynamicType).AssignableFrom(DynamicType))
|
|
assert.True(t, NewOutputType(DynamicType).AssignableFrom(DynamicType))
|
|
assert.True(t, NewPromiseType(DynamicType).AssignableFrom(DynamicType))
|
|
assert.True(t, NewUnionType(BoolType, DynamicType).AssignableFrom(DynamicType))
|
|
|
|
assert.False(t, BoolType.AssignableFrom(DynamicType))
|
|
assert.False(t, IntType.AssignableFrom(DynamicType))
|
|
assert.False(t, NumberType.AssignableFrom(DynamicType))
|
|
assert.False(t, StringType.AssignableFrom(DynamicType))
|
|
|
|
assert.False(t, NewOptionalType(BoolType).AssignableFrom(DynamicType))
|
|
assert.False(t, NewOutputType(BoolType).AssignableFrom(DynamicType))
|
|
assert.False(t, NewPromiseType(BoolType).AssignableFrom(DynamicType))
|
|
assert.False(t, NewMapType(BoolType).AssignableFrom(DynamicType))
|
|
assert.False(t, NewListType(BoolType).AssignableFrom(DynamicType))
|
|
assert.False(t, NewUnionType(BoolType, IntType).AssignableFrom(DynamicType))
|
|
assert.False(t, NewObjectType(map[string]Type{
|
|
"bool": BoolType,
|
|
"int": IntType,
|
|
}).AssignableFrom(DynamicType))
|
|
|
|
// Test that DynamicType is convertible from any type.
|
|
assert.True(t, DynamicType.ConversionFrom(BoolType).Exists())
|
|
assert.True(t, DynamicType.ConversionFrom(IntType).Exists())
|
|
assert.True(t, DynamicType.ConversionFrom(NumberType).Exists())
|
|
assert.True(t, DynamicType.ConversionFrom(StringType).Exists())
|
|
|
|
assert.True(t, DynamicType.ConversionFrom(NewOptionalType(BoolType)).Exists())
|
|
assert.True(t, DynamicType.ConversionFrom(NewOutputType(BoolType)).Exists())
|
|
assert.True(t, DynamicType.ConversionFrom(NewPromiseType(BoolType)).Exists())
|
|
assert.True(t, DynamicType.ConversionFrom(NewMapType(BoolType)).Exists())
|
|
assert.True(t, DynamicType.ConversionFrom(NewListType(BoolType)).Exists())
|
|
assert.True(t, DynamicType.ConversionFrom(NewUnionType(BoolType, IntType)).Exists())
|
|
assert.True(t, DynamicType.ConversionFrom(NewObjectType(map[string]Type{
|
|
"bool": BoolType,
|
|
"int": IntType,
|
|
})).Exists())
|
|
|
|
// Test that DynamicType is convertible to any type.
|
|
assert.True(t, BoolType.ConversionFrom(DynamicType).Exists())
|
|
assert.True(t, IntType.ConversionFrom(DynamicType).Exists())
|
|
assert.True(t, NumberType.ConversionFrom(DynamicType).Exists())
|
|
assert.True(t, StringType.ConversionFrom(DynamicType).Exists())
|
|
|
|
assert.True(t, NewOptionalType(BoolType).ConversionFrom(DynamicType).Exists())
|
|
assert.True(t, NewOutputType(BoolType).ConversionFrom(DynamicType).Exists())
|
|
assert.True(t, NewPromiseType(BoolType).ConversionFrom(DynamicType).Exists())
|
|
assert.True(t, NewMapType(BoolType).ConversionFrom(DynamicType).Exists())
|
|
assert.True(t, NewListType(BoolType).ConversionFrom(DynamicType).Exists())
|
|
assert.True(t, NewUnionType(BoolType, IntType).ConversionFrom(DynamicType).Exists())
|
|
assert.True(t, NewObjectType(map[string]Type{
|
|
"bool": BoolType,
|
|
"int": IntType,
|
|
}).ConversionFrom(DynamicType).Exists())
|
|
|
|
// Test that traversals on DynamicType always succeed.
|
|
testTraverse(t, DynamicType, hcl.TraverseAttr{Name: "foo"}, DynamicType, false)
|
|
testTraverse(t, DynamicType, hcl.TraverseIndex{Key: cty.StringVal("foo")}, DynamicType, false)
|
|
testTraverse(t, DynamicType, hcl.TraverseIndex{Key: cty.NumberIntVal(0)}, DynamicType, false)
|
|
testTraverse(t, DynamicType, hcl.TraverseIndex{Key: encapsulateType(DynamicType)}, DynamicType, false)
|
|
}
|
|
|
|
func TestOptionalType(t *testing.T) {
|
|
typ := NewOptionalType(DynamicType)
|
|
|
|
// Test that creating an optional type with the same element type does not create a new type.
|
|
typ2 := NewOptionalType(DynamicType)
|
|
assert.EqualValues(t, typ, typ2)
|
|
|
|
// Test that creating an optional type with an element type that is also optional does not create a new type.
|
|
typ2 = NewOptionalType(typ)
|
|
assert.Equal(t, typ, typ2)
|
|
|
|
// Test that an optional type is assignable to and from itself.
|
|
assert.True(t, typ.AssignableFrom(typ))
|
|
|
|
// Test that an optional type is assignable from none.
|
|
assert.True(t, typ.AssignableFrom(NoneType))
|
|
|
|
// Test that an optional type is assignable from its element type.
|
|
assert.True(t, NewOptionalType(StringType).AssignableFrom(StringType))
|
|
|
|
// Test that an optional(T) is assignable from an U, where U is assignable to T.
|
|
assert.True(t, typ.AssignableFrom(BoolType))
|
|
|
|
// Test that an optional(T) is assignable from an optional(U), where U is assignable to T.
|
|
assert.True(t, typ.AssignableFrom(NewOptionalType(BoolType)))
|
|
|
|
// Test that traversing an optional(T) returns an optional(U), where U is the result of the inner traversal.
|
|
typ = NewOptionalType(NewMapType(StringType))
|
|
testTraverse(t, typ, hcl.TraverseAttr{Name: "foo"}, NewOptionalType(StringType), false)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: cty.StringVal("foo")}, NewOptionalType(StringType), false)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: cty.NumberIntVal(0)}, NewOptionalType(StringType), false)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: encapsulateType(IntType)}, NewOptionalType(StringType), false)
|
|
}
|
|
|
|
func TestOutputType(t *testing.T) {
|
|
typ := NewOutputType(DynamicType)
|
|
|
|
// Test that creating an output type with the same element type does not create a new type.
|
|
typ2 := NewOutputType(typ.ElementType)
|
|
assert.EqualValues(t, typ, typ2)
|
|
|
|
// Test that creating an output type with an element type that is also an output does not create a new type.
|
|
typ2 = NewOutputType(typ)
|
|
assert.Equal(t, typ, typ2)
|
|
|
|
// Test that an output type is assignable to and from itself.
|
|
assert.True(t, typ.AssignableFrom(typ))
|
|
|
|
// Test that an output type is assignable from its element type.
|
|
assert.True(t, NewOutputType(StringType).AssignableFrom(StringType))
|
|
|
|
// Test that output(T) is assignable from U, where U is assignable to T.
|
|
assert.True(t, typ.AssignableFrom(BoolType))
|
|
|
|
// Test that output(T) is assignable from output(U), where U is assignable to T.
|
|
assert.True(t, typ.AssignableFrom(NewOutputType(BoolType)))
|
|
|
|
// Test that output(T) is assignable from promise(U), where U is assignable to T.
|
|
assert.True(t, typ.AssignableFrom(NewPromiseType(BoolType)))
|
|
|
|
// Test that output(T) is _not_ assignable from U, where U is not assignable to T.
|
|
assert.False(t, NewOutputType(BoolType).AssignableFrom(IntType))
|
|
|
|
// Test that output(T) is _not_ assignable from output(U), where U is not assignable to T.
|
|
assert.False(t, NewOutputType(BoolType).AssignableFrom(NewOutputType(IntType)))
|
|
|
|
// Test that output(T) is _not_ assignable from promise(U), where U is not assignable to T.
|
|
assert.False(t, NewOutputType(BoolType).AssignableFrom(NewPromiseType(IntType)))
|
|
|
|
// Test that traversing an output(T) returns an output(U), where U is the result of the inner traversal.
|
|
typ = NewOutputType(NewMapType(StringType))
|
|
testTraverse(t, typ, hcl.TraverseAttr{Name: "foo"}, NewOutputType(StringType), false)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: cty.StringVal("foo")}, NewOutputType(StringType), false)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: cty.NumberIntVal(0)}, NewOutputType(StringType), false)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: encapsulateType(IntType)}, NewOutputType(StringType), false)
|
|
|
|
// Test that ResolveOutputs correctly handles nested outputs.
|
|
assert.Equal(t, NewOptionalType(BoolType), ResolveOutputs(NewOptionalType(NewOutputType(BoolType))))
|
|
assert.Equal(t, BoolType, ResolveOutputs(NewOutputType(BoolType)))
|
|
assert.Equal(t, BoolType, ResolveOutputs(NewPromiseType(BoolType)))
|
|
assert.Equal(t, NewMapType(BoolType), ResolveOutputs(NewMapType(NewOutputType(BoolType))))
|
|
assert.Equal(t, NewListType(BoolType), ResolveOutputs(NewListType(NewOutputType(BoolType))))
|
|
assert.Equal(t, NewUnionType(BoolType, IntType), ResolveOutputs(NewUnionType(NewOutputType(BoolType),
|
|
NewOutputType(IntType))))
|
|
assert.Equal(t, NewObjectType(map[string]Type{
|
|
"bool": BoolType,
|
|
"int": IntType,
|
|
}), ResolveOutputs(NewObjectType(map[string]Type{
|
|
"bool": NewOutputType(BoolType),
|
|
"int": NewOutputType(IntType),
|
|
})))
|
|
|
|
// Test that NewOutputType correctly handles nested outputs.
|
|
assert.Equal(t, NewOutputType(NewOptionalType(BoolType)), NewOutputType(NewOptionalType(NewOutputType(BoolType))))
|
|
assert.Equal(t, NewOutputType(BoolType), NewOutputType(NewOutputType(BoolType)))
|
|
assert.Equal(t, NewOutputType(BoolType), NewOutputType(NewPromiseType(BoolType)))
|
|
assert.Equal(t, NewOutputType(NewMapType(BoolType)), NewOutputType(NewMapType(NewOutputType(BoolType))))
|
|
assert.Equal(t, NewOutputType(NewListType(BoolType)), NewOutputType(NewListType(NewOutputType(BoolType))))
|
|
assert.Equal(t, NewOutputType(NewUnionType(BoolType, IntType)),
|
|
NewOutputType(NewUnionType(NewOutputType(BoolType), NewOutputType(IntType))))
|
|
assert.Equal(t, NewOutputType(NewObjectType(map[string]Type{
|
|
"bool": BoolType,
|
|
"int": IntType,
|
|
})), NewOutputType(NewObjectType(map[string]Type{
|
|
"bool": NewOutputType(BoolType),
|
|
"int": NewOutputType(IntType),
|
|
})))
|
|
}
|
|
|
|
func TestPromiseType(t *testing.T) {
|
|
typ := NewPromiseType(DynamicType)
|
|
|
|
// Test that creating an promise type with the same element type does not create a new type.
|
|
typ2 := NewPromiseType(typ.ElementType)
|
|
assert.EqualValues(t, typ, typ2)
|
|
|
|
// Test that creating an promise type with an element type that is also a promise does not create a new type.
|
|
typ2 = NewPromiseType(typ)
|
|
assert.Equal(t, typ, typ2)
|
|
|
|
// Test that a promise type is assignable to and from itself.
|
|
assert.True(t, typ.AssignableFrom(typ))
|
|
|
|
// Test that a promise type is assignable from its element type.
|
|
assert.True(t, NewPromiseType(StringType).AssignableFrom(StringType))
|
|
|
|
// Test that promise(T) is assignable from U, where U is assignable to T.
|
|
assert.True(t, typ.AssignableFrom(BoolType))
|
|
|
|
// Test that promise(T) is assignable from promise(U), where U is assignable to T.
|
|
assert.True(t, typ.AssignableFrom(NewPromiseType(BoolType)))
|
|
|
|
// Test that promise(T) is _not_ assignable from U, where U is not assignable to T.
|
|
assert.False(t, NewPromiseType(BoolType).AssignableFrom(IntType))
|
|
|
|
// Test that promise(T) is _not_ assignable from promise(U), where U is not assignable to T.
|
|
assert.False(t, NewPromiseType(BoolType).AssignableFrom(NewPromiseType(IntType)))
|
|
|
|
// Test that traversing an promise(T) returns an promise(U), where U is the result of the inner traversal.
|
|
typ = NewPromiseType(NewMapType(StringType))
|
|
testTraverse(t, typ, hcl.TraverseAttr{Name: "foo"}, NewPromiseType(StringType), false)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: cty.StringVal("foo")}, NewPromiseType(StringType), false)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: cty.NumberIntVal(0)}, NewPromiseType(StringType), false)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: encapsulateType(IntType)}, NewPromiseType(StringType), false)
|
|
|
|
// Test that ResolvePromises correctly handles nested promises.
|
|
assert.Equal(t, NewOptionalType(BoolType), ResolvePromises(NewOptionalType(NewPromiseType(BoolType))))
|
|
assert.Equal(t, BoolType, ResolvePromises(NewPromiseType(BoolType)))
|
|
assert.Equal(t, BoolType, ResolvePromises(NewPromiseType(BoolType)))
|
|
assert.Equal(t, NewMapType(BoolType), ResolvePromises(NewMapType(NewPromiseType(BoolType))))
|
|
assert.Equal(t, NewListType(BoolType), ResolvePromises(NewListType(NewPromiseType(BoolType))))
|
|
assert.Equal(t, NewUnionType(BoolType, IntType),
|
|
ResolvePromises(NewUnionType(NewPromiseType(BoolType), NewPromiseType(IntType))))
|
|
assert.Equal(t, NewObjectType(map[string]Type{
|
|
"bool": BoolType,
|
|
"int": IntType,
|
|
}), ResolvePromises(NewObjectType(map[string]Type{
|
|
"bool": NewPromiseType(BoolType),
|
|
"int": NewPromiseType(IntType),
|
|
})))
|
|
|
|
// Test that NewPromiseType correctly handles nested promises.
|
|
assert.Equal(t, NewPromiseType(NewOptionalType(BoolType)), NewPromiseType(NewOptionalType(NewPromiseType(BoolType))))
|
|
assert.Equal(t, NewPromiseType(BoolType), NewPromiseType(NewPromiseType(BoolType)))
|
|
assert.Equal(t, NewPromiseType(BoolType), NewPromiseType(NewPromiseType(BoolType)))
|
|
assert.Equal(t, NewPromiseType(NewMapType(BoolType)), NewPromiseType(NewMapType(NewPromiseType(BoolType))))
|
|
assert.Equal(t, NewPromiseType(NewListType(BoolType)), NewPromiseType(NewListType(NewPromiseType(BoolType))))
|
|
assert.Equal(t, NewPromiseType(NewUnionType(BoolType, IntType)),
|
|
NewPromiseType(NewUnionType(NewPromiseType(BoolType), NewPromiseType(IntType))))
|
|
assert.Equal(t, NewPromiseType(NewObjectType(map[string]Type{
|
|
"bool": BoolType,
|
|
"int": IntType,
|
|
})), NewPromiseType(NewObjectType(map[string]Type{
|
|
"bool": NewPromiseType(BoolType),
|
|
"int": NewPromiseType(IntType),
|
|
})))
|
|
}
|
|
|
|
func TestMapType(t *testing.T) {
|
|
typ := NewMapType(DynamicType)
|
|
|
|
// Test that creating an map type with the same element type does not create a new type.
|
|
typ2 := NewMapType(typ.ElementType)
|
|
assert.EqualValues(t, typ, typ2)
|
|
|
|
// Test that a map type is assignable to and from itself.
|
|
assert.True(t, typ.AssignableFrom(typ))
|
|
|
|
// Test that map(T) is _not_ assignable from U, where U is not map(T).
|
|
assert.False(t, typ.AssignableFrom(BoolType))
|
|
|
|
// Test that map(T) is assignable from map(U), where U is assignable to T.
|
|
assert.True(t, typ.AssignableFrom(NewMapType(BoolType)))
|
|
|
|
// Test that map(T) is convertible from object(K_0=U_0, .., K_N=U_N) where unify(U_0, ..., U_N) is assignable to T.
|
|
assert.True(t, typ.ConversionFrom(NewObjectType(map[string]Type{
|
|
"foo": IntType,
|
|
"bar": NumberType,
|
|
"baz": StringType,
|
|
})).Exists())
|
|
|
|
// Test that map(T) is _not_ assignable from map(U), where U is not assignable to T.
|
|
assert.False(t, NewMapType(BoolType).AssignableFrom(NewMapType(IntType)))
|
|
|
|
// Test that traversing a map(T) with a type that is convertible to string returns T.
|
|
testTraverse(t, typ, hcl.TraverseAttr{Name: "foo"}, typ.ElementType, false)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: cty.StringVal("foo")}, typ.ElementType, false)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: cty.NumberIntVal(0)}, typ.ElementType, false)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: encapsulateType(IntType)}, typ.ElementType, false)
|
|
|
|
// Test that traversing a map(T) with a type that is not convertible to string returns DynamicType and an error.
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: cty.ListVal([]cty.Value{cty.NumberIntVal(0)})}, typ.ElementType, true)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: encapsulateType(typ)}, typ.ElementType, true)
|
|
}
|
|
|
|
func TestListType(t *testing.T) {
|
|
typ := NewListType(DynamicType)
|
|
|
|
// Test that creating an list type with the same element type does not create a new type.
|
|
typ2 := NewListType(typ.ElementType)
|
|
assert.EqualValues(t, typ, typ2)
|
|
|
|
// Test that an list type is assignable to and from itself.
|
|
assert.True(t, typ.AssignableFrom(typ))
|
|
|
|
// Test that list(T) is _not_ assignable from U, where U is not list(T).
|
|
assert.False(t, typ.AssignableFrom(BoolType))
|
|
|
|
// Test that list(T) is assignable from list(U), where U is assignable to T.
|
|
assert.True(t, typ.AssignableFrom(NewListType(BoolType)))
|
|
|
|
// Test that list(T) is _not_ assignable from list(U), where U is not assignable to T.
|
|
assert.False(t, NewListType(BoolType).AssignableFrom(NewListType(IntType)))
|
|
|
|
// Test that traversing a list(T) with a type that is convertible to number returns T.
|
|
testTraverse(t, typ, hcl.TraverseAttr{Name: "0"}, typ.ElementType, false)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: cty.StringVal("0")}, typ.ElementType, false)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: cty.NumberIntVal(0)}, typ.ElementType, false)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: encapsulateType(IntType)}, typ.ElementType, false)
|
|
|
|
// Test that traversing a list(T) with a type that is not convertible to number returns DynamicType and an error.
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: cty.ListVal([]cty.Value{cty.NumberIntVal(0)})}, typ.ElementType, true)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: encapsulateType(typ)}, typ.ElementType, true)
|
|
}
|
|
|
|
func TestSetType(t *testing.T) {
|
|
typ := NewSetType(DynamicType)
|
|
|
|
// Test that creating an set type with the same element type does not create a new type.
|
|
typ2 := NewSetType(typ.ElementType)
|
|
assert.EqualValues(t, typ, typ2)
|
|
|
|
// Test that an set type is assignable to and from itself.
|
|
assert.True(t, typ.AssignableFrom(typ))
|
|
|
|
// Test that set(T) is _not_ assignable from U, where U is not set(T).
|
|
assert.False(t, typ.AssignableFrom(BoolType))
|
|
|
|
// Test that set(T) is assignable from set(U), where U is assignable to T.
|
|
assert.True(t, typ.AssignableFrom(NewSetType(BoolType)))
|
|
|
|
// Test that set(T) is _not_ assignable from set(U), where U is not assignable to T.
|
|
assert.False(t, NewSetType(BoolType).AssignableFrom(NewSetType(IntType)))
|
|
|
|
// Test that traversing a set(T) fails.
|
|
testTraverse(t, typ, hcl.TraverseAttr{Name: "0"}, DynamicType, true)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: cty.StringVal("0")}, DynamicType, true)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: cty.NumberIntVal(0)}, DynamicType, true)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: encapsulateType(IntType)}, DynamicType, true)
|
|
}
|
|
|
|
func TestUnionType(t *testing.T) {
|
|
typ := NewUnionType(BoolType, IntType, NumberType, StringType).(*UnionType)
|
|
|
|
// Test that creating a union with the same element types does not create a new type.
|
|
typ2 := NewUnionType(BoolType, IntType, NumberType, StringType).(*UnionType)
|
|
assert.EqualValues(t, typ, typ2)
|
|
|
|
// Test that creating a union with duplicated element types unifies all of the duplicated types.
|
|
assert.Equal(t, BoolType, NewUnionType(BoolType, BoolType))
|
|
assert.Equal(t, typ, NewUnionType(BoolType, IntType, IntType, NumberType, StringType))
|
|
|
|
// Test that a union type is assignable to and from itself.
|
|
assert.True(t, typ.AssignableFrom(typ))
|
|
|
|
// Test that a union type is assignable from each of its element types.
|
|
for _, et := range typ.ElementTypes {
|
|
assert.True(t, typ.AssignableFrom(et))
|
|
}
|
|
|
|
// Test that union(T_0, ..., T_N) is assignable from union(U_0, ..., U_M) if union(T_0, ..., T_N) is assignable
|
|
// from all of U_0 through U_M.
|
|
assert.True(t, NewUnionType(NoneType, StringType).ConversionFrom(typ).Exists())
|
|
|
|
// Test that union(T_0, ..., T_N) is _not_ assignable from union(U_0, ..., U_M) if union(T_0, ..., T_N) is not
|
|
// assignable from any of U_0 through U_M.
|
|
assert.False(t, typ.AssignableFrom(NewUnionType(BoolType, NewOptionalType(NumberType))))
|
|
|
|
// Test that traversing a union type fails if the element type cannot be traversed.
|
|
testTraverse(t, typ, hcl.TraverseAttr{Name: "foo"}, DynamicType, true)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: cty.StringVal("foo")}, DynamicType, true)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: cty.NumberIntVal(0)}, DynamicType, true)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: encapsulateType(typ)}, DynamicType, true)
|
|
|
|
// Test that traversing a union type succeeds if some element type can be traversed.
|
|
typ = NewUnionType(typ, NewObjectType(map[string]Type{"foo": StringType}), NewListType(StringType)).(*UnionType)
|
|
testTraverse(t, typ, hcl.TraverseAttr{Name: "foo"}, StringType, false)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: cty.StringVal("foo")}, StringType, false)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: cty.NumberIntVal(0)}, StringType, false)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: encapsulateType(StringType)}, StringType, false)
|
|
|
|
// Test that traversing a union type produces a union if more than one element can be traversed.
|
|
typ = NewUnionType(NewMapType(IntType), NewObjectType(map[string]Type{"foo": StringType})).(*UnionType)
|
|
testTraverse(t, typ, hcl.TraverseAttr{Name: "foo"}, NewUnionType(StringType, IntType), false)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: cty.StringVal("foo")}, NewUnionType(StringType, IntType), false)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: cty.NumberIntVal(0)}, IntType, false)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: encapsulateType(IntType)}, NewUnionType(StringType, IntType), false)
|
|
}
|
|
|
|
func TestObjectType(t *testing.T) {
|
|
typ := NewObjectType(map[string]Type{
|
|
"foo": BoolType,
|
|
"bar": IntType,
|
|
"baz": NumberType,
|
|
"qux": NewOptionalType(BoolType),
|
|
})
|
|
|
|
// Test that creating a union with the same element types does not create a new type.
|
|
typ2 := NewObjectType(map[string]Type{
|
|
"foo": BoolType,
|
|
"bar": IntType,
|
|
"baz": NumberType,
|
|
"qux": NewOptionalType(BoolType),
|
|
})
|
|
assert.EqualValues(t, typ, typ2)
|
|
|
|
// Test that an object type is assignable to and from itself.
|
|
assert.True(t, typ.AssignableFrom(typ))
|
|
|
|
// Test that object(K_0=T_0, ..., K_N=T_N) is assignable from object(K_0=U_0, ..., K_N=U_N) if for each key K_i
|
|
// T_i is assignable from U_i.
|
|
assert.True(t, typ.ConversionFrom(NewObjectType(map[string]Type{
|
|
"foo": BoolType,
|
|
"bar": IntType,
|
|
"baz": IntType,
|
|
"qux": BoolType,
|
|
})).Exists())
|
|
// Test that object(K_0=T_0, ..., K_N=T_N) is assignable from object(K_0=U_0, ..., K_M=U_M) if M < N and for each
|
|
// key K_i where 0 <= i <= M, T_i is assignable from U_i and for each K_j where M < j <= N, T_j is optional.
|
|
assert.True(t, typ.ConversionFrom(NewObjectType(map[string]Type{
|
|
"foo": BoolType,
|
|
"bar": IntType,
|
|
"baz": NumberType,
|
|
})).Exists())
|
|
|
|
// Test that object(K_0=T_0, ..., K_N=T_N) is _unsafely_ convertible from object(L_0=U_0, ..., L_M=U_M) if there exists
|
|
// some key K_i a matching key K_i exists and T_i is unsafely convertible from U_i.
|
|
assert.Equal(t, UnsafeConversion, typ.ConversionFrom(NewObjectType(map[string]Type{
|
|
"foo": BoolType,
|
|
"bar": IntType,
|
|
"baz": NumberType,
|
|
"qux": StringType,
|
|
})))
|
|
assert.Equal(t, UnsafeConversion, typ.ConversionFrom(NewObjectType(map[string]Type{
|
|
"foo": BoolType,
|
|
"bar": IntType,
|
|
"baz": StringType,
|
|
})))
|
|
|
|
// Test that traversing an object type with a property name K_i returns T_i.
|
|
testTraverse(t, typ, hcl.TraverseAttr{Name: "foo"}, BoolType, false)
|
|
testTraverse(t, typ, hcl.TraverseAttr{Name: "bar"}, IntType, false)
|
|
testTraverse(t, typ, hcl.TraverseAttr{Name: "baz"}, NumberType, false)
|
|
testTraverse(t, typ, hcl.TraverseAttr{Name: "qux"}, NewOptionalType(BoolType), false)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: cty.StringVal("foo")}, BoolType, false)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: cty.StringVal("bar")}, IntType, false)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: cty.StringVal("baz")}, NumberType, false)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: cty.StringVal("qux")}, NewOptionalType(BoolType), false)
|
|
|
|
// Test that traversing an object type with a dynamic value produces the union of the object's property types..
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: encapsulateType(StringType)}, NewUnionType(BoolType, IntType,
|
|
NumberType, NewOptionalType(BoolType)), false)
|
|
|
|
// Test that traversing an object type with any other type fails.
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: cty.NumberIntVal(0)}, DynamicType, true)
|
|
testTraverse(t, typ, hcl.TraverseIndex{Key: encapsulateType(typ)}, DynamicType, true)
|
|
}
|
|
|
|
func TestOpaqueType(t *testing.T) {
|
|
foo, err := NewOpaqueType("foo")
|
|
assert.NotNil(t, foo)
|
|
assert.NoError(t, err)
|
|
|
|
foo2, ok := GetOpaqueType("foo")
|
|
assert.EqualValues(t, foo, foo2)
|
|
assert.True(t, ok)
|
|
|
|
foo3, err := NewOpaqueType("foo")
|
|
assert.Nil(t, foo3)
|
|
assert.Error(t, err)
|
|
|
|
bar, ok := GetOpaqueType("bar")
|
|
assert.Nil(t, bar)
|
|
assert.False(t, ok)
|
|
|
|
bar, err = NewOpaqueType("bar")
|
|
assert.NotNil(t, bar)
|
|
assert.NoError(t, err)
|
|
|
|
assert.NotEqual(t, foo, bar)
|
|
}
|
|
|
|
func TestInputType(t *testing.T) {
|
|
// Test that InputType(DynamicType) just returns DynamicType.
|
|
assert.Equal(t, DynamicType, InputType(DynamicType))
|
|
|
|
// Test that InputType(T) correctly recurses through constructed types. The result of InputType(T) should be
|
|
// union(innerInputType(T), output(innerInputType(T))), where innerInputType(T) recurses thorough constructed
|
|
// types.
|
|
|
|
assert.Equal(t, NewUnionType(BoolType, NewOutputType(BoolType)), InputType(BoolType))
|
|
|
|
assert.Equal(t, NewUnionType(
|
|
NewOptionalType(NewUnionType(BoolType, NewOutputType(BoolType))),
|
|
NewOutputType(NewOptionalType(BoolType))), InputType(NewOptionalType(BoolType)))
|
|
|
|
assert.Equal(t, NewUnionType(
|
|
NewPromiseType(NewUnionType(BoolType, NewOutputType(BoolType))),
|
|
NewOutputType(BoolType)), InputType(NewPromiseType(BoolType)))
|
|
|
|
assert.Equal(t, NewUnionType(
|
|
NewMapType(NewUnionType(BoolType, NewOutputType(BoolType))),
|
|
NewOutputType(NewMapType(BoolType))), InputType(NewMapType(BoolType)))
|
|
|
|
assert.Equal(t, NewUnionType(
|
|
NewListType(NewUnionType(BoolType, NewOutputType(BoolType))),
|
|
NewOutputType(NewListType(BoolType))), InputType(NewListType(BoolType)))
|
|
|
|
assert.Equal(t, NewUnionType(
|
|
NewUnionType(BoolType, IntType, NewOutputType(BoolType), NewOutputType(IntType)),
|
|
NewOutputType(NewUnionType(BoolType, IntType))),
|
|
InputType(NewUnionType(BoolType, IntType)))
|
|
|
|
assert.Equal(t, NewUnionType(
|
|
NewObjectType(map[string]Type{"foo": NewUnionType(BoolType, NewOutputType(BoolType))}),
|
|
NewOutputType(NewObjectType(map[string]Type{"foo": BoolType}))),
|
|
InputType(NewObjectType(map[string]Type{"foo": BoolType})))
|
|
|
|
assert.True(t, InputType(BoolType).ConversionFrom(BoolType).Exists())
|
|
assert.True(t, InputType(NumberType).ConversionFrom(NumberType).Exists())
|
|
}
|
|
|
|
func assertUnified(t *testing.T, expectedSafe, expectedUnsafe Type, types ...Type) {
|
|
actualSafe, actualUnsafe := UnifyTypes(types...)
|
|
assert.Equal(t, expectedSafe, actualSafe)
|
|
assert.Equal(t, expectedUnsafe, actualUnsafe)
|
|
|
|
// Reverse the types and ensure we get the same results.
|
|
for i, j := 0, len(types)-1; i < j; i, j = i+1, j-1 {
|
|
types[i], types[j] = types[j], types[i]
|
|
}
|
|
actualSafe2, actualUnsafe2 := UnifyTypes(types...)
|
|
assert.Equal(t, actualSafe, actualSafe2)
|
|
assert.Equal(t, actualUnsafe, actualUnsafe2)
|
|
}
|
|
|
|
func TestUnifyType(t *testing.T) {
|
|
// Number, int, and bool unify with string by preferring string.
|
|
assertUnified(t, StringType, StringType, NumberType, StringType)
|
|
assertUnified(t, StringType, StringType, IntType, StringType)
|
|
assertUnified(t, StringType, StringType, BoolType, StringType)
|
|
|
|
// Number and int unify by preferring number.
|
|
assertUnified(t, NumberType, NumberType, IntType, NumberType)
|
|
|
|
// Number or int and bool unify by preferring number or int.
|
|
assertUnified(t, NewUnionType(NumberType, BoolType), NumberType, BoolType, NumberType)
|
|
assertUnified(t, NewUnionType(IntType, BoolType), IntType, BoolType, IntType)
|
|
|
|
// Two collection types of the same kind unify according to the unification of their element types.
|
|
assertUnified(t, NewMapType(StringType), NewMapType(StringType), NewMapType(BoolType), NewMapType(StringType))
|
|
assertUnified(t, NewListType(StringType), NewListType(StringType), NewListType(BoolType), NewListType(StringType))
|
|
assertUnified(t, NewSetType(StringType), NewSetType(StringType), NewSetType(BoolType), NewSetType(StringType))
|
|
|
|
// List and set types unify by preferring the list type.
|
|
assertUnified(t, NewListType(StringType), NewListType(StringType), NewListType(StringType), NewSetType(BoolType))
|
|
assertUnified(t, NewListType(StringType), NewListType(StringType), NewListType(BoolType), NewSetType(StringType))
|
|
|
|
assert.True(t, StringType.ConversionFrom(NewOptionalType(NewUnionType(NewMapType(StringType), BoolType))).Exists())
|
|
|
|
// Map and object types unify by preferring the map type.
|
|
m0, m1 := NewObjectType(map[string]Type{"foo": StringType}), NewObjectType(map[string]Type{"foo": BoolType})
|
|
assertUnified(t, NewMapType(StringType), NewMapType(StringType), m0, NewMapType(BoolType))
|
|
assertUnified(t, NewMapType(StringType), NewMapType(StringType), m1, NewMapType(StringType))
|
|
|
|
// List or set and tuple types unify by preferring the list or set type.
|
|
t0, t1 := NewTupleType(NumberType, BoolType), NewTupleType(StringType, NumberType)
|
|
assertUnified(t, NewListType(StringType), NewListType(StringType), t0, NewListType(StringType))
|
|
assertUnified(t, NewListType(StringType), NewListType(StringType), t1, NewListType(BoolType))
|
|
assertUnified(t, NewUnionType(t0, NewSetType(StringType)), NewSetType(StringType), t0, NewSetType(StringType))
|
|
assertUnified(t, NewUnionType(t1, NewSetType(BoolType)), NewSetType(StringType), t1, NewSetType(BoolType))
|
|
|
|
// The dynamic type unifies with any other type by selecting the other type.
|
|
assertUnified(t, NewUnionType(BoolType, DynamicType), BoolType, BoolType, DynamicType)
|
|
|
|
// Object types unify by constructing a new object type whose attributes are the unification of the two input types.
|
|
m2 := NewObjectType(map[string]Type{"bar": StringType})
|
|
m3 := NewObjectType(map[string]Type{"foo": NewOptionalType(StringType), "bar": NewOptionalType(StringType)})
|
|
m4 := NewObjectType(map[string]Type{"foo": NewMapType(StringType), "bar": NewListType(StringType)})
|
|
m5 := NewObjectType(map[string]Type{
|
|
"foo": NewOptionalType(NewUnionType(NewMapType(StringType), StringType, NoneType)),
|
|
"bar": NewOptionalType(NewUnionType(NewListType(StringType), StringType, NoneType)),
|
|
})
|
|
assertUnified(t, m0, m0, m0, m1)
|
|
assertUnified(t, m3, m3, m0, m2)
|
|
assertUnified(t, m5, m5, m4, m2, m0, m1)
|
|
assertUnified(t, m5, m5, m4, m0, m2, m1)
|
|
|
|
// Tuple types unify by constructing a new tuple type whose element types are the unification of the corresponding
|
|
// element types.
|
|
t2 := NewTupleType(StringType, NumberType)
|
|
t3 := NewTupleType(StringType, IntType)
|
|
t4 := NewTupleType(NumberType, BoolType, StringType)
|
|
t5 := NewTupleType(NumberType, BoolType, NewOptionalType(StringType))
|
|
assertUnified(t, NewUnionType(t0, t1), t2, t0, t1)
|
|
assertUnified(t, t2, t2, t3, t1)
|
|
assertUnified(t, t5, t5, t4, t0)
|
|
|
|
//
|
|
// assertUnified(t, NewUnionType(BoolType, IntType), IntType, BoolType, IntType)
|
|
// assertUnified(t, NewOptionalType(NumberType), NewOptionalType(NumberType), IntType, NewOptionalType(NumberType))
|
|
// assertUnified(t, NewOptionalType(BoolType), NewOptionalType(BoolType), BoolType, NewOptionalType(BoolType))
|
|
// assertUnified(t, NewOutputType(BoolType), NewOutputType(BoolType), BoolType, NewOutputType(BoolType))
|
|
// assertUnified(t, NewPromiseType(BoolType), NewPromiseType(BoolType), BoolType, NewPromiseType(BoolType))
|
|
// assertUnified(t, AnyType, AnyType, BoolType, IntType, AnyType)
|
|
//
|
|
// assertUnified(t, BoolType, BoolType, DynamicType, BoolType)
|
|
|
|
// t0 := Type(NewObjectType(map[string]Type{"foo": IntType}))
|
|
// t1 := Type(NewObjectType(map[string]Type{"foo": IntType, "bar": NewOptionalType(NumberType)}))
|
|
//
|
|
// assert.Equal(t, NewMapType(AnyType), unifyTypes(NewMapType(AnyType), t0))
|
|
// assert.Equal(t, t1, unifyTypes(t0, t1))
|
|
// assert.Equal(t, t1, unifyTypes(t1, t0))
|
|
//
|
|
// t0 = NewOutputType(NumberType)
|
|
// t1 = NewOutputType(NewUnionType(NumberType, IntType))
|
|
// assert.Equal(t, t0, unifyTypes(t0, t1))
|
|
// assert.Equal(t, t0, unifyTypes(t1, t0))
|
|
}
|
|
|
|
func TestRecursiveObjectType(t *testing.T) {
|
|
makeType := func(prop string) Type {
|
|
props := map[string]Type{
|
|
prop: NewOutputType(IntType),
|
|
}
|
|
objType := NewObjectType(props)
|
|
props["sibling"] = objType
|
|
listType := NewListType(objType)
|
|
linkedListType := NewOptionalType(listType)
|
|
props["next"] = linkedListType
|
|
return linkedListType
|
|
}
|
|
|
|
linkedListType := makeType("data")
|
|
linkedListTypeEqual := makeType("data")
|
|
linkedListTypeNonEqual := makeType("data1")
|
|
|
|
// Equals
|
|
assert.True(t, linkedListType.Equals(linkedListTypeEqual))
|
|
assert.False(t, linkedListType.Equals(linkedListTypeNonEqual))
|
|
|
|
// String conversion
|
|
// Note: 'next' property is not visible because the string value is memoized at the time of Optional creation.
|
|
assert.Equal(t, "union(list(object({data = output(int), sibling = ...})), none)", linkedListType.String())
|
|
|
|
// Convert from another type
|
|
assert.Equal(t, UnsafeConversion, linkedListType.ConversionFrom(linkedListTypeNonEqual))
|
|
|
|
// Contains eventuals
|
|
hasOutputs, hasPromises := ContainsEventuals(linkedListType)
|
|
assert.True(t, hasOutputs)
|
|
assert.False(t, hasPromises)
|
|
|
|
// Resolving eventuals
|
|
resolvedLinkedListType := ResolveOutputs(linkedListType)
|
|
data := resolvedLinkedListType.(*UnionType).ElementTypes[0].(*ListType).ElementType.(*ObjectType).Properties["data"]
|
|
assert.True(t, data.Equals(IntType))
|
|
hasOutputs, _ = ContainsEventuals(resolvedLinkedListType)
|
|
assert.False(t, hasOutputs)
|
|
|
|
// InputType conversion
|
|
inputLinkedListType := InputType(resolvedLinkedListType)
|
|
hasOutputs, _ = ContainsEventuals(inputLinkedListType)
|
|
assert.True(t, hasOutputs)
|
|
}
|