This adds the core unification helper functions that do the core work of solving the invariants. This includes the actual Unify, OccursCheck, and Extract which is sometimes known as "zonk". A few other small functions are also included. Co-authored-by: Samuel Gélineau <gelisam@gmail.com>
279 lines
7.0 KiB
Go
279 lines
7.0 KiB
Go
// Mgmt
|
|
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
|
// Written by James Shubin <james@shubin.ca> and the project contributors
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
//
|
|
// Additional permission under GNU GPL version 3 section 7
|
|
//
|
|
// If you modify this program, or any covered work, by linking or combining it
|
|
// with embedded mcl code and modules (and that the embedded mcl code and
|
|
// modules which link with this program, contain a copy of their source code in
|
|
// the authoritative form) containing parts covered by the terms of any other
|
|
// license, the licensors of this program grant you additional permission to
|
|
// convey the resulting work. Furthermore, the licensors of this program grant
|
|
// the original author, James Shubin, additional permission to update this
|
|
// additional permission if he deems it necessary to achieve the goals of this
|
|
// additional permission.
|
|
|
|
//go:build !root
|
|
|
|
package util
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/purpleidea/mgmt/lang/types"
|
|
"github.com/purpleidea/mgmt/util"
|
|
)
|
|
|
|
func TestUnify1(t *testing.T) {
|
|
typ1 := &types.Type{
|
|
Kind: types.KindUnification,
|
|
Uni: types.NewElem(), // unification variable, eg: ?1
|
|
}
|
|
typ2 := types.TypeStr
|
|
|
|
t.Logf("before: %v -- %v", typ1, typ2)
|
|
|
|
if err := Unify(typ1, typ2); err != nil {
|
|
t.Errorf("unify failed: %+v", err)
|
|
return
|
|
}
|
|
|
|
t1, t2 := Extract(typ1), Extract(typ2)
|
|
t.Logf("after: %v -- %v", t1, t2)
|
|
|
|
if err := t1.Cmp(t2); err != nil {
|
|
t.Errorf("cmp failed: %+v", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestUnify2(t *testing.T) {
|
|
typ1 := types.TypeStr
|
|
typ2 := types.TypeStr
|
|
|
|
t.Logf("before: %v -- %v", typ1, typ2)
|
|
|
|
if err := Unify(typ1, typ2); err != nil {
|
|
t.Errorf("unify failed: %+v", err)
|
|
return
|
|
}
|
|
|
|
t1, t2 := Extract(typ1), Extract(typ2)
|
|
t.Logf("after: %v -- %v", t1, t2)
|
|
|
|
if err := t1.Cmp(t2); err != nil {
|
|
t.Errorf("cmp failed: %+v", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestUnify3(t *testing.T) {
|
|
typ1 := types.TypeBool
|
|
typ2 := types.TypeStr
|
|
|
|
t.Logf("before: %v -- %v", typ1, typ2)
|
|
|
|
if err := Unify(typ1, typ2); err == nil {
|
|
t.Errorf("unify didn't fail")
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestUnify4(t *testing.T) {
|
|
uni1 := &types.Type{
|
|
Kind: types.KindUnification,
|
|
Uni: types.NewElem(), // unification variable, eg: ?1
|
|
}
|
|
|
|
typ1 := &types.Type{
|
|
Kind: types.KindList,
|
|
Val: uni1,
|
|
}
|
|
typ2 := types.TypeListStr
|
|
|
|
t.Logf("before: %v -- %v", typ1, typ2)
|
|
t.Logf("before (uni.Data): %v", uni1.Uni.Data)
|
|
|
|
if err := Unify(typ1, typ2); err != nil {
|
|
t.Errorf("unify failed: %+v", err)
|
|
return
|
|
}
|
|
|
|
t1, t2 := Extract(typ1), Extract(typ2)
|
|
//t1, t2 := typ1, typ2
|
|
t.Logf("after: %v -- %v", t1, t2)
|
|
//t.Logf("after (uni1): %v", uni1)
|
|
//t.Logf("after (uni1.Uni): %v", uni1.Uni)
|
|
//t.Logf("after (uni1.Uni.Data): %v", uni1.Uni.Data)
|
|
//t.Logf("after (typ): %v -- %v", typ1, typ2)
|
|
|
|
if err := t1.Cmp(t2); err != nil {
|
|
t.Errorf("cmp failed: %+v", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestUnifyTable(t *testing.T) {
|
|
type test struct { // an individual test
|
|
name string
|
|
typ1 *types.Type
|
|
typ2 *types.Type
|
|
fail bool
|
|
exp *types.Type // concrete type without unification variables
|
|
}
|
|
testCases := []test{}
|
|
|
|
testCases = append(testCases, test{ // 0
|
|
"nil",
|
|
nil,
|
|
nil,
|
|
true,
|
|
nil,
|
|
})
|
|
testCases = append(testCases, test{
|
|
name: "two strings",
|
|
typ1: types.TypeStr,
|
|
typ2: types.TypeStr,
|
|
fail: false,
|
|
})
|
|
testCases = append(testCases, test{
|
|
name: "different simple types",
|
|
typ1: types.TypeStr,
|
|
typ2: types.TypeBool,
|
|
fail: true,
|
|
})
|
|
testCases = append(testCases, test{
|
|
name: "two lists",
|
|
typ1: types.TypeListStr,
|
|
typ2: types.TypeListStr,
|
|
fail: false,
|
|
})
|
|
testCases = append(testCases, test{
|
|
name: "two lists, one elem",
|
|
typ1: types.TypeListStr, // []str
|
|
typ2: types.NewType("[]?1"), // []?1
|
|
fail: false,
|
|
})
|
|
testCases = append(testCases, test{
|
|
name: "two functions",
|
|
typ1: types.NewType("func([]str, ?42, float, int) ?42"),
|
|
typ2: types.NewType("func(?13, bool, ?4, int) ?42"),
|
|
fail: false,
|
|
exp: types.NewType("func([]str, bool, float, int) bool"),
|
|
})
|
|
|
|
names := []string{}
|
|
for index, tc := range testCases { // run all the tests
|
|
if tc.name == "" {
|
|
t.Errorf("test #%d: not named", index)
|
|
continue
|
|
}
|
|
if util.StrInList(tc.name, names) {
|
|
t.Errorf("test #%d: duplicate sub test name of: %s", index, tc.name)
|
|
continue
|
|
}
|
|
names = append(names, tc.name)
|
|
|
|
//if index != 3 { // hack to run a subset (useful for debugging)
|
|
//if tc.name != "nil" {
|
|
// continue
|
|
//}
|
|
|
|
t.Run(fmt.Sprintf("test #%d (%s)", index, tc.name), func(t *testing.T) {
|
|
name, typ1, typ2, fail, exp := tc.name, tc.typ1, tc.typ2, tc.fail, tc.exp
|
|
|
|
t.Logf("\n\ntest #%d (%s) ----------------\n\n", index, name)
|
|
|
|
t.Logf("test #%d: Unify: %v -- %v", index, typ1, typ2)
|
|
err := Unify(typ1, typ2)
|
|
if !fail && err != nil {
|
|
t.Errorf("test #%d: FAIL", index)
|
|
t.Errorf("test #%d: Unify failed with: %+v", index, err)
|
|
return
|
|
}
|
|
if fail && err == nil {
|
|
t.Errorf("test #%d: FAIL", index)
|
|
t.Errorf("test #%d: Unify passed, expected fail", index)
|
|
return
|
|
}
|
|
|
|
if fail { // can't compare types if it failed
|
|
//t.Logf("test #%d: PASS", index)
|
|
t.Logf("test #%d: Unify failed, expected fail", index)
|
|
return
|
|
}
|
|
|
|
t1, t2 := Extract(typ1), Extract(typ2)
|
|
t.Logf("test #%d: Extract: %v -- %v", index, t1, t2)
|
|
|
|
if err := t1.Cmp(t2); err != nil {
|
|
t.Errorf("test #%d: FAIL", index)
|
|
t.Errorf("test #%d: cmp error:\n%v", index, err)
|
|
return
|
|
}
|
|
if exp != nil {
|
|
if t1.HasUni() {
|
|
t.Errorf("test #%d: FAIL", index)
|
|
t.Errorf("test #%d: has uni: %+v", index, t1)
|
|
return
|
|
}
|
|
if exp.HasUni() {
|
|
t.Errorf("test #%d: FAIL", index)
|
|
t.Errorf("test #%d: exp has uni: %+v", index, exp)
|
|
return
|
|
}
|
|
if err := exp.Cmp(t1); err != nil {
|
|
t.Errorf("test #%d: FAIL", index)
|
|
t.Errorf("test #%d: cmp error:\n%v", index, err)
|
|
return
|
|
}
|
|
}
|
|
//t.Logf("test #%d: PASS", index)
|
|
//t.Logf("test #%d: Unify passed, cmp passed", index)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExtract1(t *testing.T) {
|
|
uni1 := &types.Type{
|
|
Kind: types.KindUnification,
|
|
Uni: types.NewElem(), // unification variable, eg: ?1
|
|
}
|
|
|
|
typ1 := &types.Type{
|
|
Kind: types.KindList,
|
|
Val: uni1,
|
|
}
|
|
typ2 := types.TypeListStr
|
|
|
|
t.Logf("before: %v -- %v", typ1, typ2)
|
|
t.Logf("before (uni.Data): %v", uni1.Uni.Data)
|
|
|
|
if err := Unify(typ1, typ2); err != nil {
|
|
t.Errorf("unify failed: %+v", err)
|
|
return
|
|
}
|
|
|
|
typ := Extract(typ1) // should produce a []str
|
|
|
|
if err := typ.Cmp(typ2); err != nil {
|
|
t.Errorf("cmp failed: %+v", err)
|
|
return
|
|
}
|
|
}
|