go-jsonnet/main_test.go
Stanisław Barzowski 5da57ac417 Interpreter & runtime - minimal usable version (#24)
* Interpreter & runtime - minimal usable version

Accomplishments in this commit:
* Majority of language features implemented:
	* Unary operators
	* Binary operators
	* Conditionals
	* Errors
	* Indexing arrays
	* Indexing objects
	* Object inheritance
	* Imports
	* Functions
	* Function calls
* There is a quite nice way for creating builtins
* Static analyzer is there with most of the functionality
* Standard library is included and parts unaffected by missing features
work
* Some bugs in existing parts fixed
* Most positive tests from C++ version pass, the rest is failing mostly
due to missing builtins and comprehensions.
* Some initial structure was created that should allow more incremental
  and focused changes in the future PRs.
* Some comments/explanations added
* Panics translated to a little bit more gentle internal errors (with a
  link to issues on github).

What still sucks:
* Stack traces & error messages (there's some stuff in place)
* Almost everything is in the same package
* Stuff is exported or unexporeted randomly (see above)
* Missing a few lexing/parsing features
* Missing builtins
* Missing support for extvars and top-level-args
* Checking function arguments is missing
* No clean Go API that commandline and compatibility layer to C can use
* No compatibility layer to C
* Assertions don't work (desugaring level, assertEquals works).
* Manifestation stack traces (and generally it could use some work).
* The way environments are constructed is sometimes suboptimal/clumsy.
2017-08-24 20:09:10 -04:00

196 lines
5.5 KiB
Go

/*
Copyright 2016 Google Inc. All rights reserved.
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 jsonnet
import (
"bytes"
"flag"
"io/ioutil"
"path/filepath"
"strings"
"testing"
"unicode/utf8"
"github.com/sergi/go-diff/diffmatchpatch"
)
var update = flag.Bool("update", false, "update .golden files")
// Just some simple sanity tests for now. Eventually we'll share end-to-end tests with the C++
// implementation but unsure if that should be done here or via some external framework.
// TODO(sbarzowski) figure out how to measure coverage on the external tests
type mainTest struct {
name string
input string
golden string
}
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 TestMain(t *testing.T) {
flag.Parse()
var mainTests []mainTest
match, err := filepath.Glob("testdata/*.input")
if err != nil {
t.Fatal(err)
}
for _, input := range match {
golden := input
name := input
if strings.HasSuffix(input, ".input") {
name = input[:len(input)-len(".input")]
golden = name + ".golden"
}
mainTests = append(mainTests, mainTest{name: name, input: input, golden: golden})
}
for _, test := range mainTests {
t.Run(test.name, func(t *testing.T) {
vm := MakeVM()
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)
output, err := vm.evaluateSnippet(test.name, string(input))
if err != nil {
// TODO(sbarzowski) perhaps somehow mark that we are processing
// an error. But for now we can treat them the same.
output = err.Error()
}
output += "\n"
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.Errorf("%#v %#v\n", golden, []byte(output))
t.Fail()
t.Errorf("Mismatch when running %s.input. 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.input\n", test.golden, test.name)
t.Errorf(string(data))
}
})
}
}
func diff(a, b string) string {
dmp := diffmatchpatch.New()
diffs := dmp.DiffMain(a, b, false)
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()
output, err := vm.evaluateSnippet(test.name, test.input)
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)
}
}
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" +
" During evaluation \n" +
" error:1:1-9 <main>\n"}, // TODO(sbarzowski) if seems we have off-by-one in location
{"error_in_func", `local x(n) = if n == 0 then error "x" else x(n - 1); x(3)`, "RUNTIME ERROR: x\n" +
" During evaluation \n" +
" error_in_func:1:54-58 <main>\n" +
" error_in_func:1:44-52 function <anonymous>\n" +
" error_in_func:1:44-52 function <anonymous>\n" +
" error_in_func:1:44-52 function <anonymous>\n" +
" error_in_func:1:29-37 function <anonymous>\n" +
""},
{"error_in_error", `error (error "x")`, "RUNTIME ERROR: x\n" +
" During evaluation \n" +
" error_in_error:1:8-16 <main>\n" +
""},
}
func TestMinimalError(t *testing.T) {
formatter := ErrorFormatter{}
genericTestErrorMessage(t, minimalErrorTests, func(r RuntimeError) string {
return formatter.format(r)
})
}