Files
sausage/userland/goawk/source/interp/newexecute_test.go
T

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
}