205 lines
5.6 KiB
Go
205 lines
5.6 KiB
Go
// Tests for the New...Execute API.
|
|
|
|
package interp_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"math"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/benhoyt/goawk/interp"
|
|
"github.com/benhoyt/goawk/parser"
|
|
)
|
|
|
|
// This definitely doesn't test that everything was reset, but it's a good start.
|
|
func TestNewExecute(t *testing.T) {
|
|
source := `{ print NR, OFMT, x, y, a["k"], $1, $3; OFMT="%g"; x++; y++; a["k"]++ }`
|
|
interpreter := newInterp(t, source)
|
|
|
|
// First execution.
|
|
var output bytes.Buffer
|
|
status, err := interpreter.Execute(&interp.Config{
|
|
Stdin: strings.NewReader("one two three\nfour five six\n"),
|
|
Output: &output,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("error executing: %v", err)
|
|
}
|
|
if status != 0 {
|
|
t.Fatalf("expected status 0, got %d", status)
|
|
}
|
|
normalized := normalizeNewlines(output.String())
|
|
expected := "1 %.6g one three\n2 %g 1 1 1 four six\n"
|
|
if normalized != expected {
|
|
t.Fatalf("expected %q, got %q", expected, normalized)
|
|
}
|
|
|
|
// Second execution, with ResetVars.
|
|
output.Reset()
|
|
interpreter.ResetVars()
|
|
status, err = interpreter.Execute(&interp.Config{
|
|
Stdin: strings.NewReader("ONE TWO THREE\nFOUR FIVE SIX\n"),
|
|
Output: &output,
|
|
Vars: []string{"x", "10"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("error executing: %v", err)
|
|
}
|
|
if status != 0 {
|
|
t.Fatalf("expected status 0, got %d", status)
|
|
}
|
|
normalized = normalizeNewlines(output.String())
|
|
expected = "1 %.6g 10 ONE THREE\n2 %g 11 1 1 FOUR SIX\n"
|
|
if normalized != expected {
|
|
t.Fatalf("expected %q, got %q", expected, normalized)
|
|
}
|
|
|
|
// Third execution, without ResetVars.
|
|
output.Reset()
|
|
status, err = interpreter.Execute(&interp.Config{
|
|
Stdin: strings.NewReader("1 2 3\n4 5 6\n"),
|
|
Output: &output,
|
|
Vars: []string{"x", "100"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("error executing: %v", err)
|
|
}
|
|
if status != 0 {
|
|
t.Fatalf("expected status 0, got %d", status)
|
|
}
|
|
normalized = normalizeNewlines(output.String())
|
|
expected = "1 %g 100 2 2 1 3\n2 %g 101 3 3 4 6\n"
|
|
if normalized != expected {
|
|
t.Fatalf("expected %q, got %q", expected, normalized)
|
|
}
|
|
}
|
|
|
|
func TestResetRand(t *testing.T) {
|
|
source := `BEGIN { print rand(), rand(), rand() }`
|
|
interpreter := newInterp(t, source)
|
|
var output bytes.Buffer
|
|
|
|
_, err := interpreter.Execute(&interp.Config{Output: &output})
|
|
if err != nil {
|
|
t.Fatalf("error executing: %v", err)
|
|
}
|
|
original := output.String()
|
|
|
|
output.Reset()
|
|
_, err = interpreter.Execute(&interp.Config{Output: &output})
|
|
if err != nil {
|
|
t.Fatalf("error executing: %v", err)
|
|
}
|
|
noResetRand := output.String()
|
|
if original == noResetRand {
|
|
t.Fatalf("expected different random numbers, got %q both times", original)
|
|
}
|
|
|
|
output.Reset()
|
|
interpreter.ResetRand()
|
|
_, err = interpreter.Execute(&interp.Config{Output: &output})
|
|
if err != nil {
|
|
t.Fatalf("error executing: %v", err)
|
|
}
|
|
withResetRand := output.String()
|
|
if original != withResetRand {
|
|
t.Fatalf("expected same random numbers (%q) as original (%q)", withResetRand, original)
|
|
}
|
|
}
|
|
|
|
func TestGetArrayValue(t *testing.T) {
|
|
interpreter := newInterp(t, `
|
|
BEGIN { Arr["key"]; f(); g(Arr) }
|
|
function f() { Arr["hello"]="world" }
|
|
function g(arr) { arr["a"]=1.23 }`)
|
|
_, err := interpreter.Execute(nil)
|
|
if err != nil {
|
|
t.Fatalf("error executing: %v", err)
|
|
}
|
|
arr := interpreter.Array("Arr")
|
|
if len(arr) != 3 {
|
|
t.Errorf("expected length 3, got %d", len(arr))
|
|
}
|
|
if arr["key"] != "" {
|
|
t.Errorf("expected value \"\", got %q", arr["key"])
|
|
}
|
|
if arr["hello"] != "world" {
|
|
t.Errorf("expected value \"world\", got %q", arr["hello"])
|
|
}
|
|
if math.Abs(arr["a"].(float64)-1.23) > 1e-9 {
|
|
t.Errorf("expected value 1.23, got %f", arr["a"])
|
|
}
|
|
if interpreter.Array("NonExistent") != nil {
|
|
t.Errorf("non existent name must resolve to nil")
|
|
}
|
|
}
|
|
|
|
func TestExecuteContextNoError(t *testing.T) {
|
|
interpreter := newInterp(t, `BEGIN {}`)
|
|
_, err := interpreter.ExecuteContext(context.Background(), nil)
|
|
if err != nil {
|
|
t.Fatalf("execute error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestExecuteContextTimeout(t *testing.T) {
|
|
interpreter := newInterp(t, `BEGIN { for (i=0; i<100000000; i++) s+=i }`) // would take about 4s
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Millisecond)
|
|
defer cancel()
|
|
_, err := interpreter.ExecuteContext(ctx, nil)
|
|
if !errors.Is(err, context.DeadlineExceeded) {
|
|
t.Fatalf("expected DeadlineExceeded error, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestExecuteContextCancel(t *testing.T) {
|
|
interpreter := newInterp(t, `BEGIN { for (i=0; i<100000000; i++) s+=i }`) // would take about 4s
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel() // cancel it right away
|
|
_, err := interpreter.ExecuteContext(ctx, nil)
|
|
if !errors.Is(err, context.Canceled) {
|
|
t.Fatalf("expected Canceled error, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestExecuteContextSystemTimeout(t *testing.T) {
|
|
start := time.Now()
|
|
interpreter := newInterp(t, `BEGIN { print system("sleep 1") }`)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Millisecond)
|
|
defer cancel()
|
|
|
|
_, err := interpreter.ExecuteContext(ctx, nil)
|
|
switch runtime.GOOS {
|
|
case "windows", "darwin":
|
|
// For some reason on Windows and macOS the returned error is nil, rather
|
|
// than DeadlineExceeded. Don't check it on those platforms.
|
|
default:
|
|
if !errors.Is(err, context.DeadlineExceeded) {
|
|
t.Fatalf("expected DeadlineExceeded error, got: %v", err)
|
|
}
|
|
}
|
|
|
|
elapsed := time.Since(start)
|
|
if elapsed > 500*time.Millisecond {
|
|
t.Fatalf("should have taken ~5ms, took %v", elapsed)
|
|
}
|
|
}
|
|
|
|
func newInterp(t *testing.T, src string) *interp.Interpreter {
|
|
t.Helper()
|
|
prog, err := parser.ParseProgram([]byte(src), nil)
|
|
if err != nil {
|
|
t.Fatalf("parse error: %v", err)
|
|
}
|
|
interpreter, err := interp.New(prog)
|
|
if err != nil {
|
|
t.Fatalf("interp.New error: %v", err)
|
|
}
|
|
return interpreter
|
|
}
|