package ph7_test import ( "bytes" "errors" "fmt" "strings" "testing" ph7 "git.codemonkeysoftware.net/b/go-ph7" ) func mustSucceed(t *testing.T, err error) { t.Helper() if err != nil { t.Fatal(err.Error()) } } func mustFail(t *testing.T, err error) { t.Helper() if err == nil { t.Fatal("expected error, got nil") } } func mustSucceedF(t *testing.T, f func() error) { t.Helper() err := f() if err != nil { t.Fatal(err.Error()) } } func setupVM(t *testing.T, script string) (vm *ph7.VM, close func()) { t.Helper() engine, err := ph7.NewEngine() mustSucceed(t, err) vm, err = engine.Compile([]byte(script), true) if err != nil { mustSucceed(t, engine.Close()) mustSucceed(t, err) } return vm, func() { mustSucceed(t, vm.Close()) mustSucceed(t, engine.Close()) } } func expectOutput(t *testing.T, vm *ph7.VM, expected string) { t.Helper() mustSucceed(t, vm.Exec()) output, err := vm.ExtractOutput() mustSucceed(t, err) if output != expected { t.Fatalf("expected %q, got %q", expected, output) } } func TestCore(t *testing.T) { engine, err := ph7.NewEngine() mustSucceed(t, err) defer mustSucceedF(t, engine.Close) vm, err := engine.Compile([]byte(""), false) mustSucceed(t, err) defer mustSucceedF(t, vm.Close) mustSucceed(t, vm.Exec()) output, err := vm.ExtractOutput() mustSucceed(t, err) if output != "Hello world!" { t.Fatalf("unexpected output: %s", output) } mustSucceed(t, vm.Reset()) mustSucceed(t, vm.Exec()) output, err = vm.ExtractOutput() mustSucceed(t, err) if output != "Hello world!" { t.Fatalf("unexpected output: %s", output) } } func TestCompileFile(t *testing.T) { engine, err := ph7.NewEngine() mustSucceed(t, err) defer mustSucceedF(t, engine.Close) vm, err := engine.CompileFile("testdata/test_compile_file.php", false) mustSucceed(t, err) defer mustSucceedF(t, vm.Close) expectOutput(t, vm, "Hello world!") } func TestCompileError(t *testing.T) { engine, err := ph7.NewEngine() mustSucceed(t, err) defer mustSucceedF(t, engine.Close) _, err = engine.Compile([]byte(""), false) mustFail(t, err) var target ph7.CompileError isCompileError := errors.As(err, &target) if !isCompileError || target.Msg == "" { t.Fatalf("expected compile error, got %#v", err) } } func TestOutputWriter(t *testing.T) { const source = ` for ($x = 0; $x <= 1000; $x++) { echo $x; }` var expected bytes.Buffer for i := 0; i <= 1000; i++ { _, err := fmt.Fprint(&expected, i) mustSucceed(t, err) } vm, close := setupVM(t, source) defer close() var actual bytes.Buffer err := vm.SetOutputWriter(&actual) mustSucceed(t, err) mustSucceed(t, vm.Exec()) if !bytes.Equal(expected.Bytes(), actual.Bytes()) { t.Fatalf("expected %s, got %s", expected.String(), actual.String()) } } // TODO Test failing writer, including vm.OutputWriteError // TODO Test setting writer twice func CtxFunc(f func(*ph7.Context) error) ph7.Function { return func(ctx *ph7.Context, _ []*ph7.Value) ph7.ResultCode { err := f(ctx) if err != nil { return f(ctx).(ph7.Error).Code } return ph7.ResultCodeOK } } func testResultCode(t *testing.T, expectedOutput string, f func(*ph7.Context) error) { t.Helper() const script = "echo doStuff();" vm, close := setupVM(t, script) defer close() mustSucceed(t, vm.CreateFunction("doStuff", CtxFunc(f))) expectOutput(t, vm, expectedOutput) } func TestContextWrite(t *testing.T) { const script = "echo doStuff();" vm, close := setupVM(t, script) defer close() const s = "aoeu htns" var written int f := func(ctx *ph7.Context) error { var err error written, err = ctx.OutputWriter().Write([]byte(s)) return err } mustSucceed(t, vm.CreateFunction("doStuff", CtxFunc(f))) expectOutput(t, vm, s) if written != len(s) { t.Fatalf("wrote wrong number of bytes: expected %d, got %d", len(s), written) } } func TestContextThrowError(t *testing.T) { const script = "echo doStuff();" vm, close := setupVM(t, script) defer close() const msg = "aock.,rabkaeop" f := func(ctx *ph7.Context) error { return ctx.ThrowError(ph7.SeverityErr, msg) } mustSucceed(t, vm.CreateFunction("doStuff", CtxFunc(f))) mustSucceed(t, vm.ReportErrors()) mustSucceed(t, vm.Exec()) output, err := vm.ExtractOutput() mustSucceed(t, err) if !strings.Contains(output, msg) { t.Fatalf("expected output to contain %q, got %q", msg, output) } } func TestFunctionName(t *testing.T) { const script = "echo doStuff();" vm, close := setupVM(t, script) defer close() f := func(ctx *ph7.Context) error { _, err := fmt.Fprint(ctx.OutputWriter(), ctx.FunctionName()) return err } mustSucceed(t, vm.CreateFunction("doStuff", CtxFunc(f))) expectOutput(t, vm, "doStuff") } func TestContextResultInt(t *testing.T) { testResultCode(t, "42", func(ctx *ph7.Context) error { return ctx.ResultInt(42) }) } func TestContextResultInt64(t *testing.T) { testResultCode(t, "1234", func(ctx *ph7.Context) error { return ctx.ResultInt64(1234) }) } func TestContextResultBool(t *testing.T) { testResultCode(t, "TRUE", func(ctx *ph7.Context) error { return ctx.ResultBool(true) }) testResultCode(t, "", func(ctx *ph7.Context) error { return ctx.ResultBool(false) }) } func TestContextResultDouble(t *testing.T) { testResultCode(t, "1.23", func(ctx *ph7.Context) error { return ctx.ResultDouble(1.23) }) } func TestContextResultNull(t *testing.T) { testResultCode(t, "", (*ph7.Context).ResultNull) } func TestContextResultString(t *testing.T) { testResultCode(t, "hello", func(ctx *ph7.Context) error { return ctx.ResultString("hello") }) } // TODO TestContextResultValue // TODO TestContextResultResource