1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
|
// Tests for the New...Execute API.
package interp_test
import (
"bytes"
"context"
"errors"
"math"
"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) {
t.Skip("TODO: skipping for now due to #122")
interpreter := newInterp(t, `BEGIN { print system("sleep 4") }`)
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 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
}
|