blob: 404deeb43af2f2c9ac0e0ad90ae90384c432c7ac [file] [log] [blame]
// Copyright 2020 The LUCI 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 builtins
import (
"testing"
"go.starlark.net/resolve"
"go.starlark.net/starlark"
. "github.com/smartystreets/goconvey/convey"
. "go.chromium.org/luci/common/testing/assertions"
)
func init() {
resolve.AllowFloat = true
resolve.AllowSet = true
}
func TestToGoNative(t *testing.T) {
t.Parallel()
runScript := func(code string) (starlark.StringDict, error) {
return starlark.ExecFile(&starlark.Thread{}, "main", code, nil)
}
run := func(code string, expected interface{}) {
out, err := runScript(code)
So(err, ShouldBeNil)
result, err := ToGoNative(out["val"])
So(err, ShouldBeNil)
So(result, ShouldResemble, expected)
}
mustFail := func(code string, expectErr string) {
out, err := runScript(code)
_, err = ToGoNative(out["val"])
So(err, ShouldErrLike, expectErr)
}
Convey("Happy cases", t, func() {
Convey("Scalar types", func() {
run(`val = None`, nil)
run(`val = True`, true)
run(`val = 123`, int64(123))
run(`val = 123.5`, 123.5)
run(`val = "hi"`, "hi")
})
Convey("Dict", func() {
run(`val = {}`, map[string]interface{}{})
run(`val = {"a": 1, "b": 2}`, map[string]interface{}{"a": int64(1), "b": int64(2)})
})
Convey("Iterables", func() {
run(`val = []`, []interface{}{})
run(`val = ()`, []interface{}{})
run(`val = set()`, []interface{}{})
run(`val = [1, 2, 3]`, []interface{}{int64(1), int64(2), int64(3)})
run(`val = (1, 2, 3)`, []interface{}{int64(1), int64(2), int64(3)})
run(`val = set([1, 2, 3])`, []interface{}{int64(1), int64(2), int64(3)})
})
Convey("Everything at once", func() {
run(`val = {"a": None, "b": ["c", "d", ["e"]]}`, map[string]interface{}{
"a": nil,
"b": []interface{}{"c", "d", []interface{}{"e"}},
})
})
})
Convey("Unhappy cases", t, func() {
mustFail(`val = list`, `unsupported type builtin_function_or_method`)
mustFail(`val = 18446744073709551616`, `can't convert "18446744073709551616" to int64`)
mustFail(`val = {1: 2}`, `dict keys should be strings, got int`)
// Errors propagate when serializing containers.
mustFail(`val = {"a": list}`, `unsupported type builtin_function_or_method`)
mustFail(`val = [list]`, `unsupported type builtin_function_or_method`)
mustFail(`val = (list, list)`, `unsupported type builtin_function_or_method`)
mustFail(`val = set([list])`, `unsupported type builtin_function_or_method`)
})
Convey("Detects recursive structures", t, func() {
const msg = "detected recursion in the data structure"
// With dict.
mustFail(`val = {}; val['k'] = val`, msg)
// With list.
mustFail(`val = []; val.append(val)`, msg)
// With tuple.
mustFail(`l = []; val = (1, l); l.append(val)`, msg)
// And there can't be recursion involving sets, since Starlark sets require
// all items to be hashable, and this automatically forbids containers that
// can host recursive structures.
})
}