mirror of
https://github.com/google/go-jsonnet.git
synced 2025-09-29 09:21:03 +02:00
Basic refactoring - towards shared e2e tests
This commit is contained in:
parent
8ade994928
commit
c3551f4f61
118
jsonnet_test.go
Normal file
118
jsonnet_test.go
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
package jsonnet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
type errorFormattingTest struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
errString string
|
||||||
|
}
|
||||||
|
|
||||||
|
func genericTestErrorMessage(t *testing.T, tests []errorFormattingTest, format func(RuntimeError) string) {
|
||||||
|
for _, test := range tests {
|
||||||
|
vm := MakeVM()
|
||||||
|
rawOutput, err := vm.evaluateSnippet(test.name, test.input, evalKindRegular)
|
||||||
|
var errString string
|
||||||
|
if err != nil {
|
||||||
|
switch typedErr := err.(type) {
|
||||||
|
case RuntimeError:
|
||||||
|
errString = format(typedErr)
|
||||||
|
default:
|
||||||
|
t.Errorf("%s: unexpected error: %v", test.name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output := rawOutput.(string)
|
||||||
|
if errString != test.errString {
|
||||||
|
t.Errorf("%s: error result does not match. got\n\t%+#v\nexpected\n\t%+#v",
|
||||||
|
test.name, errString, test.errString)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("%s, Expected error, but execution succeded and the here's the result:\n %v\n", test.name, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(sbarzowski) Perhaps we should have just one set of tests with all the variants?
|
||||||
|
// TODO(sbarzowski) Perhaps this should be handled in external tests?
|
||||||
|
var oneLineTests = []errorFormattingTest{
|
||||||
|
{"error", `error "x"`, "RUNTIME ERROR: x"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOneLineError(t *testing.T) {
|
||||||
|
genericTestErrorMessage(t, oneLineTests, func(r RuntimeError) string {
|
||||||
|
return r.Error()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(sbarzowski) checking if the whitespace is right is quite unpleasant, what can we do about it?
|
||||||
|
var minimalErrorTests = []errorFormattingTest{
|
||||||
|
{"error", `error "x"`, "RUNTIME ERROR: x\n" +
|
||||||
|
" error:1:1-10 $\n" + // TODO(sbarzowski) if seems we have off-by-one in location
|
||||||
|
" During evaluation \n" +
|
||||||
|
""},
|
||||||
|
{"error_in_func", `local x(n) = if n == 0 then error "x" else x(n - 1); x(3)`, "RUNTIME ERROR: x\n" +
|
||||||
|
" error_in_func:1:29-38 function <x>\n" +
|
||||||
|
" error_in_func:1:44-52 function <x>\n" +
|
||||||
|
" error_in_func:1:44-52 function <x>\n" +
|
||||||
|
" error_in_func:1:44-52 function <x>\n" +
|
||||||
|
" error_in_func:1:54-58 $\n" +
|
||||||
|
" During evaluation \n" +
|
||||||
|
""},
|
||||||
|
{"error_in_error", `error (error "x")`, "RUNTIME ERROR: x\n" +
|
||||||
|
" error_in_error:1:8-17 $\n" +
|
||||||
|
" During evaluation \n" +
|
||||||
|
""},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMinimalError(t *testing.T) {
|
||||||
|
formatter := termErrorFormatter{maxStackTraceSize: 20}
|
||||||
|
genericTestErrorMessage(t, minimalErrorTests, func(r RuntimeError) string {
|
||||||
|
return formatter.Format(r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(sbarzowski) test pretty errors once they are stable-ish
|
||||||
|
// probably "golden" pattern is the right one for that
|
||||||
|
|
||||||
|
func removeExcessiveWhitespace(s string) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
separated := true
|
||||||
|
for i, w := 0, 0; i < len(s); i += w {
|
||||||
|
runeValue, width := utf8.DecodeRuneInString(s[i:])
|
||||||
|
if runeValue == '\n' || runeValue == ' ' {
|
||||||
|
if !separated {
|
||||||
|
buf.WriteString(" ")
|
||||||
|
separated = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buf.WriteRune(runeValue)
|
||||||
|
separated = false
|
||||||
|
}
|
||||||
|
w = width
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomImporter(t *testing.T) {
|
||||||
|
vm := MakeVM()
|
||||||
|
vm.Importer(&MemoryImporter{
|
||||||
|
map[string]string{
|
||||||
|
"a.jsonnet": "2 + 2",
|
||||||
|
"b.jsonnet": "3 + 3",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
input := `[import "a.jsonnet", importstr "b.jsonnet"]`
|
||||||
|
expected := `[ 4, "3 + 3" ] `
|
||||||
|
actual, err := vm.EvaluateSnippet("custom_import.jsonnet", input)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
actual = removeExcessiveWhitespace(actual)
|
||||||
|
if actual != expected {
|
||||||
|
t.Errorf("Expected %q, but got %q", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
275
main_test.go
275
main_test.go
@ -24,7 +24,6 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"github.com/google/go-jsonnet/ast"
|
"github.com/google/go-jsonnet/ast"
|
||||||
"github.com/sergi/go-diff/diffmatchpatch"
|
"github.com/sergi/go-diff/diffmatchpatch"
|
||||||
@ -70,6 +69,106 @@ type mainTest struct {
|
|||||||
meta *testMetadata
|
meta *testMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var jsonToString = &NativeFunction{
|
||||||
|
Name: "jsonToString",
|
||||||
|
Params: ast.Identifiers{"x"},
|
||||||
|
Func: func(x []interface{}) (interface{}, error) {
|
||||||
|
bytes, err := json.Marshal(x[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return string(bytes), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsonnetInput struct {
|
||||||
|
name string
|
||||||
|
input []byte
|
||||||
|
eKind evalKind
|
||||||
|
extVars map[string]string
|
||||||
|
extCode map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsonnetResult struct {
|
||||||
|
output string
|
||||||
|
isError bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func runJsonnet(i jsonnetInput) jsonnetResult {
|
||||||
|
vm := MakeVM()
|
||||||
|
errFormatter := termErrorFormatter{pretty: true, maxStackTraceSize: 9}
|
||||||
|
|
||||||
|
for name, value := range i.extVars {
|
||||||
|
vm.ExtVar(name, value)
|
||||||
|
}
|
||||||
|
for name, value := range i.extCode {
|
||||||
|
vm.ExtCode(name, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
vm.NativeFunction(jsonToString)
|
||||||
|
|
||||||
|
var output string
|
||||||
|
|
||||||
|
rawOutput, err := vm.evaluateSnippet(i.name, string(i.input), i.eKind)
|
||||||
|
var isError bool
|
||||||
|
if err != nil {
|
||||||
|
// TODO(sbarzowski) perhaps somehow mark that we are processing
|
||||||
|
// an error. But for now we can treat them the same.
|
||||||
|
output = errFormatter.Format(err)
|
||||||
|
output += "\n"
|
||||||
|
isError = true
|
||||||
|
} else {
|
||||||
|
output = rawOutput.(string)
|
||||||
|
isError = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonnetResult{
|
||||||
|
output: output,
|
||||||
|
isError: isError,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTest(t *testing.T, test *mainTest) {
|
||||||
|
|
||||||
|
read := func(file string) []byte {
|
||||||
|
bytz, err := ioutil.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("reading file: %s: %v", file, err)
|
||||||
|
}
|
||||||
|
return bytz
|
||||||
|
}
|
||||||
|
|
||||||
|
input := read(test.input)
|
||||||
|
|
||||||
|
result := runJsonnet(jsonnetInput{
|
||||||
|
name: test.name,
|
||||||
|
input: input,
|
||||||
|
eKind: evalKindRegular,
|
||||||
|
extVars: test.meta.extVars,
|
||||||
|
extCode: test.meta.extCode,
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO(sbarzowski) report which files were updated
|
||||||
|
if *update {
|
||||||
|
err := ioutil.WriteFile(test.golden, []byte(result.output), 0666)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error updating golden files: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
golden := read(test.golden)
|
||||||
|
if bytes.Compare(golden, []byte(result.output)) != 0 {
|
||||||
|
// TODO(sbarzowski) better reporting of differences in whitespace
|
||||||
|
// missing newline issues can be very subtle now
|
||||||
|
t.Fail()
|
||||||
|
t.Errorf("Mismatch when running %s.jsonnet. Golden: %s\n", test.name, test.golden)
|
||||||
|
data := diff(result.output, string(golden))
|
||||||
|
t.Errorf("diff %s jsonnet %s.jsonnet\n", test.golden, test.name)
|
||||||
|
t.Errorf(string(data))
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMain(t *testing.T) {
|
func TestMain(t *testing.T) {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
var mainTests []mainTest
|
var mainTests []mainTest
|
||||||
@ -77,6 +176,7 @@ func TestMain(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, input := range match {
|
for _, input := range match {
|
||||||
golden := input
|
golden := input
|
||||||
name := input
|
name := input
|
||||||
@ -90,69 +190,9 @@ func TestMain(t *testing.T) {
|
|||||||
}
|
}
|
||||||
mainTests = append(mainTests, mainTest{name: name, input: input, golden: golden, meta: &meta})
|
mainTests = append(mainTests, mainTest{name: name, input: input, golden: golden, meta: &meta})
|
||||||
}
|
}
|
||||||
errFormatter := termErrorFormatter{pretty: true, maxStackTraceSize: 9}
|
|
||||||
for _, test := range mainTests {
|
for _, test := range mainTests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
vm := MakeVM()
|
runTest(t, &test)
|
||||||
|
|
||||||
for name, value := range test.meta.extVars {
|
|
||||||
vm.ExtVar(name, value)
|
|
||||||
}
|
|
||||||
for name, value := range test.meta.extCode {
|
|
||||||
vm.ExtCode(name, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
vm.NativeFunction(&NativeFunction{
|
|
||||||
Name: "jsonToString",
|
|
||||||
Params: ast.Identifiers{"x"},
|
|
||||||
Func: func(x []interface{}) (interface{}, error) {
|
|
||||||
bytes, err := json.Marshal(x[0])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return string(bytes), nil
|
|
||||||
},
|
|
||||||
})
|
|
||||||
read := func(file string) []byte {
|
|
||||||
bytz, err := ioutil.ReadFile(file)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("reading file: %s: %v", file, err)
|
|
||||||
}
|
|
||||||
return bytz
|
|
||||||
}
|
|
||||||
|
|
||||||
input := read(test.input)
|
|
||||||
rawOutput, err := vm.evaluateSnippet(test.name, string(input), evalKindRegular)
|
|
||||||
var output string
|
|
||||||
if err != nil {
|
|
||||||
// TODO(sbarzowski) perhaps somehow mark that we are processing
|
|
||||||
// an error. But for now we can treat them the same.
|
|
||||||
output = errFormatter.Format(err)
|
|
||||||
output += "\n"
|
|
||||||
} else {
|
|
||||||
output = rawOutput.(string)
|
|
||||||
}
|
|
||||||
if *update {
|
|
||||||
err := ioutil.WriteFile(test.golden, []byte(output), 0666)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("error updating golden files: %v", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
golden := read(test.golden)
|
|
||||||
if bytes.Compare(golden, []byte(output)) != 0 {
|
|
||||||
// TODO(sbarzowski) better reporting of differences in whitespace
|
|
||||||
// missing newline issues can be very subtle now
|
|
||||||
t.Fail()
|
|
||||||
t.Errorf("Mismatch when running %s.jsonnet. Golden: %s\n", test.name, test.golden)
|
|
||||||
data := diff(output, string(golden))
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("computing diff: %s", err)
|
|
||||||
}
|
|
||||||
t.Errorf("diff %s jsonnet %s.jsonnet\n", test.golden, test.name)
|
|
||||||
t.Errorf(string(data))
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -162,114 +202,3 @@ func diff(a, b string) string {
|
|||||||
diffs := dmp.DiffMain(a, b, false)
|
diffs := dmp.DiffMain(a, b, false)
|
||||||
return dmp.DiffPrettyText(diffs)
|
return dmp.DiffPrettyText(diffs)
|
||||||
}
|
}
|
||||||
|
|
||||||
type errorFormattingTest struct {
|
|
||||||
name string
|
|
||||||
input string
|
|
||||||
errString string
|
|
||||||
}
|
|
||||||
|
|
||||||
func genericTestErrorMessage(t *testing.T, tests []errorFormattingTest, format func(RuntimeError) string) {
|
|
||||||
for _, test := range tests {
|
|
||||||
vm := MakeVM()
|
|
||||||
rawOutput, err := vm.evaluateSnippet(test.name, test.input, evalKindRegular)
|
|
||||||
var errString string
|
|
||||||
if err != nil {
|
|
||||||
switch typedErr := err.(type) {
|
|
||||||
case RuntimeError:
|
|
||||||
errString = format(typedErr)
|
|
||||||
default:
|
|
||||||
t.Errorf("%s: unexpected error: %v", test.name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
output := rawOutput.(string)
|
|
||||||
if errString != test.errString {
|
|
||||||
t.Errorf("%s: error result does not match. got\n\t%+#v\nexpected\n\t%+#v",
|
|
||||||
test.name, errString, test.errString)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("%s, Expected error, but execution succeded and the here's the result:\n %v\n", test.name, output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(sbarzowski) Perhaps we should have just one set of tests with all the variants?
|
|
||||||
// TODO(sbarzowski) Perhaps this should be handled in external tests?
|
|
||||||
var oneLineTests = []errorFormattingTest{
|
|
||||||
{"error", `error "x"`, "RUNTIME ERROR: x"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOneLineError(t *testing.T) {
|
|
||||||
genericTestErrorMessage(t, oneLineTests, func(r RuntimeError) string {
|
|
||||||
return r.Error()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(sbarzowski) checking if the whitespace is right is quite unpleasant, what can we do about it?
|
|
||||||
var minimalErrorTests = []errorFormattingTest{
|
|
||||||
{"error", `error "x"`, "RUNTIME ERROR: x\n" +
|
|
||||||
" error:1:1-10 $\n" + // TODO(sbarzowski) if seems we have off-by-one in location
|
|
||||||
" During evaluation \n" +
|
|
||||||
""},
|
|
||||||
{"error_in_func", `local x(n) = if n == 0 then error "x" else x(n - 1); x(3)`, "RUNTIME ERROR: x\n" +
|
|
||||||
" error_in_func:1:29-38 function <x>\n" +
|
|
||||||
" error_in_func:1:44-52 function <x>\n" +
|
|
||||||
" error_in_func:1:44-52 function <x>\n" +
|
|
||||||
" error_in_func:1:44-52 function <x>\n" +
|
|
||||||
" error_in_func:1:54-58 $\n" +
|
|
||||||
" During evaluation \n" +
|
|
||||||
""},
|
|
||||||
{"error_in_error", `error (error "x")`, "RUNTIME ERROR: x\n" +
|
|
||||||
" error_in_error:1:8-17 $\n" +
|
|
||||||
" During evaluation \n" +
|
|
||||||
""},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMinimalError(t *testing.T) {
|
|
||||||
formatter := termErrorFormatter{maxStackTraceSize: 20}
|
|
||||||
genericTestErrorMessage(t, minimalErrorTests, func(r RuntimeError) string {
|
|
||||||
return formatter.Format(r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(sbarzowski) test pretty errors once they are stable-ish
|
|
||||||
// probably "golden" pattern is the right one for that
|
|
||||||
|
|
||||||
func removeExcessiveWhitespace(s string) string {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
separated := true
|
|
||||||
for i, w := 0, 0; i < len(s); i += w {
|
|
||||||
runeValue, width := utf8.DecodeRuneInString(s[i:])
|
|
||||||
if runeValue == '\n' || runeValue == ' ' {
|
|
||||||
if !separated {
|
|
||||||
buf.WriteString(" ")
|
|
||||||
separated = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
buf.WriteRune(runeValue)
|
|
||||||
separated = false
|
|
||||||
}
|
|
||||||
w = width
|
|
||||||
}
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCustomImporter(t *testing.T) {
|
|
||||||
vm := MakeVM()
|
|
||||||
vm.Importer(&MemoryImporter{
|
|
||||||
map[string]string{
|
|
||||||
"a.jsonnet": "2 + 2",
|
|
||||||
"b.jsonnet": "3 + 3",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
input := `[import "a.jsonnet", importstr "b.jsonnet"]`
|
|
||||||
expected := `[ 4, "3 + 3" ] `
|
|
||||||
actual, err := vm.EvaluateSnippet("custom_import.jsonnet", input)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
actual = removeExcessiveWhitespace(actual)
|
|
||||||
if actual != expected {
|
|
||||||
t.Errorf("Expected %q, but got %q", expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user