blob: 9c1d3d740151f79d0894cfd2606f4c111572b23a [file] [log] [blame]
// Copyright 2020 CUE Authors
//
// 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 eval
import (
"fmt"
"strings"
"testing"
"cuelang.org/go/internal/core/adt"
)
func TestInsert(t *testing.T) {
testCases := []struct {
tree []CloseDef
typ func(c *acceptor, at adt.ID, p adt.Node) adt.ID
at adt.ID
wantID adt.ID
out string
}{{
tree: nil,
at: 0,
typ: (*acceptor).InsertDefinition,
wantID: 1,
out: "&( 0[] *1[])",
}, {
tree: []CloseDef{{}},
at: 0,
typ: (*acceptor).InsertDefinition,
wantID: 1,
out: "&( 0[] *1[])",
}, {
tree: []CloseDef{0: {And: 1}, {And: 0, IsDef: true}},
at: 0,
typ: (*acceptor).InsertDefinition,
wantID: 2,
out: "&( 0[] *2[] *1[])",
}, {
tree: []CloseDef{0: {And: 1}, 1: {And: 0, IsDef: true}},
at: 1,
typ: (*acceptor).InsertDefinition,
wantID: 2,
out: "&( 0[] 1[] *2[])",
}, {
tree: []CloseDef{
0: {And: 1},
1: {And: 2, IsDef: true},
2: {And: 0, IsDef: true},
},
at: 1,
typ: (*acceptor).InsertDefinition,
wantID: 3,
out: "&( 0[] 1[] *3[] *2[])",
}, {
tree: []CloseDef{
0: {And: 1},
1: {And: 0, NextEmbed: 2, IsDef: true},
2: {And: embedRoot},
3: {And: 3},
},
at: 1,
typ: (*acceptor).InsertDefinition,
wantID: 4,
out: "&( 0[] 1[&( 3[])] *4[])",
}, {
tree: []CloseDef{
0: {And: 1},
1: {And: 0, NextEmbed: 2, IsDef: true},
2: {And: embedRoot},
3: {And: 3},
},
at: 3,
typ: (*acceptor).InsertDefinition,
wantID: 4,
out: "&( 0[] *1[&( 3[] *4[])])",
}, {
tree: nil,
at: 0,
typ: (*acceptor).InsertEmbed,
wantID: 2,
out: "&( 0[&( 2[])])",
}, {
tree: []CloseDef{{}},
at: 0,
typ: (*acceptor).InsertEmbed,
wantID: 2,
out: "&( 0[&( 2[])])",
}, {
tree: []CloseDef{0: {And: 1}, 1: {And: 0, IsDef: true}},
at: 1,
typ: (*acceptor).InsertEmbed,
wantID: 3,
out: "&( 0[] *1[&( 3[])])",
}, {
tree: []CloseDef{0: {NextEmbed: 1}, 1: {And: embedRoot}, 2: {And: 2}},
at: 0,
typ: (*acceptor).InsertEmbed,
wantID: 4,
out: "&( 0[&( 4[])&( 2[])])",
}, {
tree: []CloseDef{0: {NextEmbed: 1}, 1: {And: embedRoot}, 2: {And: 2}},
at: 1,
typ: (*acceptor).InsertEmbed,
wantID: 4,
out: "&( 0[&( 2[])&( 4[])])",
}, {
tree: []CloseDef{
0: {And: 1},
1: {And: 2},
2: {And: 0},
},
at: 2,
typ: (*acceptor).InsertEmbed,
wantID: 4,
out: "&( 0[] 1[] 2[&( 4[])])",
}}
for i, tc := range testCases {
t.Run(fmt.Sprint(i), func(t *testing.T) {
c := &acceptor{Canopy: tc.tree}
got := tc.typ(c, tc.at, nil)
if got != tc.wantID {
t.Errorf("id: got %d; want %d", got, tc.wantID)
}
w := &strings.Builder{}
// fmt.Fprintf(w, "%#v", c.Canopy)
writeConjuncts(w, c)
if got := w.String(); got != tc.out {
t.Errorf("id: got %s; want %s", got, tc.out)
}
})
}
}
func TestInsertSubtree(t *testing.T) {
testCases := []struct {
tree []CloseDef
at adt.ID
sub []CloseDef
out string
}{{
tree: nil,
at: 0,
sub: nil,
out: "&( 0[&( 2[])])",
}, {
tree: []CloseDef{{}},
at: 0,
sub: nil,
out: "&( 0[&( 2[])])",
}, {
tree: []CloseDef{0: {And: 1}, {And: 0, IsDef: true}},
at: 0,
sub: []CloseDef{{}},
out: "&( 0[&( 3[])] *1[])",
}, {
tree: []CloseDef{0: {And: 1}, {And: 0, IsDef: true}},
at: 0,
sub: []CloseDef{{And: 1}, {And: 0, IsDef: true}},
out: "&( 0[&( 3[] *4[])] *1[])",
}, {
tree: []CloseDef{0: {NextEmbed: 1}, 1: {And: embedRoot}, 2: {And: 2}},
at: 0,
sub: []CloseDef{0: {NextEmbed: 1}, 1: {And: embedRoot}, 2: {And: 2}},
out: "&( 0[&( 4[&( 6[])])&( 2[])])",
}, {
tree: []CloseDef{0: {NextEmbed: 1}, 1: {And: embedRoot}, 2: {And: 2}},
at: 2,
sub: []CloseDef{0: {NextEmbed: 1}, 1: {And: embedRoot}, 2: {And: 2}},
out: "&( 0[&( 2[&( 4[&( 6[])])])])",
}}
for i, tc := range testCases {
t.Run(fmt.Sprint(i), func(t *testing.T) {
c := &acceptor{Canopy: tc.tree}
d := &acceptor{Canopy: tc.sub}
n := &nodeContext{nodeShared: &nodeShared{node: &adt.Vertex{}}}
c.InsertSubtree(tc.at, n, &adt.Vertex{Closed: d}, false)
w := &strings.Builder{}
// fmt.Fprintf(w, "%#v", c.Canopy)
writeConjuncts(w, c)
if got := w.String(); got != tc.out {
t.Errorf("id: got %s; want %s", got, tc.out)
}
})
}
}
func TestVerifyArcAllowed(t *testing.T) {
fields := func(a ...adt.Feature) []adt.Feature { return a }
results := func(a ...bool) []bool { return a }
fieldSets := func(a ...[]adt.Feature) [][]adt.Feature { return a }
testCases := []struct {
desc string
isClosed bool
tree []CloseDef
fields [][]adt.Feature
check []adt.Feature
want []bool
}{{
desc: "required and remains closed with embedding",
isClosed: true,
tree: []CloseDef{
{And: 0},
},
fields: fieldSets(
fields(1),
),
check: fields(2),
want: results(false),
}, {
// desc: "empty tree accepts everything",
// tree: nil,
// check: feats(1),
// want: results(true),
// }, {
desc: "feature required in one",
tree: []CloseDef{
0: {And: 1},
1: {And: 0, IsDef: true},
},
fields: fieldSets(
fields(1),
fields(2),
),
check: fields(1, 2, 3, 4),
want: results(false, true, false, false),
}, {
desc: "feature required in both",
tree: []CloseDef{
0: {And: 1, IsDef: true},
1: {And: 0, IsDef: true},
},
fields: fieldSets(
fields(1, 3),
fields(2, 3),
),
check: fields(1, 2, 3, 4),
want: results(false, false, true, false),
}, {
desc: "feature required in neither",
tree: []CloseDef{
0: {And: 1},
1: {And: 0},
},
fields: fieldSets(
fields(1, 3),
fields(2, 3),
),
check: fields(1, 2, 3, 4),
want: results(true, true, true, true),
}, {
desc: "closedness of embed",
tree: []CloseDef{
0: {And: 1},
1: {And: 0, IsDef: true, NextEmbed: 2},
2: {And: -1},
3: {And: 4},
4: {And: 3, IsDef: true},
},
fields: fieldSets(
fields(3, 4),
fields(),
fields(),
fields(),
fields(3),
),
check: fields(1, 3, 4),
want: results(false, true, false),
}, {
desc: "implied required through embedding",
tree: []CloseDef{
0: {And: 0, NextEmbed: 1},
1: {And: -1},
2: {And: 3},
3: {And: 2, IsDef: true},
},
fields: fieldSets(
fields(3, 4),
fields(),
fields(),
fields(3, 2),
),
check: fields(1, 2, 3, 4),
want: results(false, true, true, true),
}, {
desc: "implied required through recursive embedding",
tree: []CloseDef{
0: {And: 0, NextEmbed: 1},
1: {And: -1},
2: {And: 2, NextEmbed: 3},
3: {And: -1},
4: {And: 5},
5: {And: 4, IsDef: true},
},
fields: fieldSets(
fields(3, 4),
fields(),
fields(),
fields(),
fields(),
fields(3, 2),
),
check: fields(1, 2, 3, 4),
want: results(false, true, true, true),
}, {
desc: "nil fieldSets",
tree: []CloseDef{
0: {And: 0, NextEmbed: 1},
1: {And: -1},
2: {And: 3},
3: {And: 2, IsDef: true},
},
fields: fieldSets(
nil,
nil,
fields(1),
fields(2),
),
check: fields(1, 2, 3),
want: results(false, true, false),
}, {
desc: "required and remains closed with embedding",
tree: []CloseDef{
{And: 1},
{And: 0, NextEmbed: 2, IsDef: true},
{And: -1},
{And: 3},
},
fields: fieldSets(
fields(1),
fields(),
nil,
fields(2),
),
check: fields(0, 1, 2, 3),
want: results(false, false, true, false),
}}
for i, tc := range testCases {
t.Run(fmt.Sprint(i, "/", tc.desc), func(t *testing.T) {
c := &acceptor{Canopy: tc.tree, isClosed: tc.isClosed}
for _, f := range tc.fields {
if f == nil {
c.Fields = append(c.Fields, nil)
continue
}
fs := &fieldSet{}
c.Fields = append(c.Fields, fs)
for _, id := range f {
fs.fields = append(fs.fields, field{label: id})
}
}
ctx := &adt.OpContext{}
for i, f := range tc.check {
got := c.verify(ctx, f)
if got != tc.want[i] {
t.Errorf("%d/%d: got %v; want %v", i, f, got, tc.want[i])
}
}
})
}
}
func TestCompact(t *testing.T) {
testCases := []struct {
desc string
tree []CloseDef
conjuncts []adt.ID
out string
}{{
desc: "leave nil as is",
tree: nil,
conjuncts: []adt.ID{0, 0},
out: "nil",
}, {
desc: "simplest case",
tree: []CloseDef{{}},
conjuncts: []adt.ID{0, 0},
out: "&( 0[])",
}, {
desc: "required ands should not be removed",
tree: []CloseDef{{And: 1}, {And: 0, IsDef: true}},
conjuncts: []adt.ID{0, 0},
out: "&( 0[] *1[])",
// }, {
// // TODO:
// desc: "required ands should not be removed",
// tree: []Node{{And: 1}, {And: 0, Required: false}},
// conjuncts: []adt.ID{1},
// out: "&( 0[] 1[])",
}, {
desc: "remove embedding",
tree: []CloseDef{{NextEmbed: 1}, {And: embedRoot}, {And: 2}},
conjuncts: []adt.ID{0},
out: "&( 0[])",
}, {
desc: "keep embedding if used",
tree: []CloseDef{{NextEmbed: 1}, {And: embedRoot}, {And: 2}},
conjuncts: []adt.ID{0, 2},
out: "&( 0[&( 2[])])",
}}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
c := &acceptor{Canopy: tc.tree}
all := []adt.Conjunct{}
for _, id := range tc.conjuncts {
all = append(all, adt.Conjunct{CloseID: id})
}
c.Canopy = c.Compact(all)
w := &strings.Builder{}
// fmt.Fprintf(w, "%#v", c.Canopy)
writeConjuncts(w, c)
if got := w.String(); got != tc.out {
t.Errorf("id: got %s; want %s", got, tc.out)
}
})
}
}
func writeConjuncts(w *strings.Builder, c *acceptor) {
if len(c.Canopy) == 0 {
w.WriteString("nil")
return
}
writeAnd(w, c, 0)
}
func writeAnd(w *strings.Builder, c *acceptor, id adt.ID) {
w.WriteString("&(")
c.visitAnd(id, func(id adt.ID, n CloseDef) bool {
w.WriteString(" ")
if n.IsDef || n.IsClosed {
w.WriteString("*")
}
fmt.Fprintf(w, "%d[", id)
c.visitEmbed(id, func(id adt.ID, n CloseDef) bool {
writeAnd(w, c, id)
return true
})
w.WriteString("]")
return true
})
w.WriteString(")")
}