package mruby import ( "fmt" "reflect" "testing" ) func TestNewMrb(t *testing.T) { mrb := NewMrb() mrb.Close() } func TestMrbArena(t *testing.T) { mrb := NewMrb() defer mrb.Close() idx := mrb.ArenaSave() mrb.ArenaRestore(idx) } func TestMrbModule(t *testing.T) { mrb := NewMrb() defer mrb.Close() module := mrb.Module("Kernel") if module == nil { t.Fatal("module was nil and should not be") } } func TestMrbClass(t *testing.T) { mrb := NewMrb() defer mrb.Close() class := mrb.Class("Object", nil) if class == nil { t.Fatal("class should not be nil") } mrb.DefineClass("Hello", mrb.ObjectClass()) class = mrb.Class("Hello", mrb.ObjectClass()) if class == nil { t.Fatal("class should not be nil") } } func TestMrbConstDefined(t *testing.T) { mrb := NewMrb() defer mrb.Close() if !mrb.ConstDefined("Object", mrb.ObjectClass()) { t.Fatal("Object should be defined") } mrb.DefineClass("Hello", mrb.ObjectClass()) if !mrb.ConstDefined("Hello", mrb.ObjectClass()) { t.Fatal("Hello should be defined") } } func TestMrbDefineClass(t *testing.T) { mrb := NewMrb() defer mrb.Close() mrb.DefineClass("Hello", mrb.ObjectClass()) _, err := mrb.LoadString("Hello") if err != nil { t.Fatalf("err: %s", err) } mrb.DefineClass("World", nil) _, err = mrb.LoadString("World") if err != nil { t.Fatalf("err: %s", err) } } func TestMrbDefineClass_methodException(t *testing.T) { mrb := NewMrb() defer mrb.Close() cb := func(m *Mrb, self *MrbValue) (Value, Value) { v, err := m.LoadString(`raise "exception"`) if err != nil { exc := err.(*Exception) return nil, exc.MrbValue } return v, nil } class := mrb.DefineClass("Hello", mrb.ObjectClass()) class.DefineClassMethod("foo", cb, ArgsNone()) _, err := mrb.LoadString(`Hello.foo`) if err == nil { t.Fatal("should error") } } func TestMrbDefineClassUnder(t *testing.T) { mrb := NewMrb() defer mrb.Close() // Define an outer hello := mrb.DefineClass("Hello", mrb.ObjectClass()) _, err := mrb.LoadString("Hello") if err != nil { t.Fatalf("err: %s", err) } // Inner mrb.DefineClassUnder("World", nil, hello) _, err = mrb.LoadString("Hello::World") if err != nil { t.Fatalf("err: %s", err) } // Inner defaults mrb.DefineClassUnder("Another", nil, nil) _, err = mrb.LoadString("Another") if err != nil { t.Fatalf("err: %s", err) } } func TestMrbDefineModule(t *testing.T) { mrb := NewMrb() defer mrb.Close() mrb.DefineModule("Hello") _, err := mrb.LoadString("Hello") if err != nil { t.Fatalf("err: %s", err) } } func TestMrbDefineModuleUnder(t *testing.T) { mrb := NewMrb() defer mrb.Close() // Define an outer hello := mrb.DefineModule("Hello") _, err := mrb.LoadString("Hello") if err != nil { t.Fatalf("err: %s", err) } // Inner mrb.DefineModuleUnder("World", hello) _, err = mrb.LoadString("Hello::World") if err != nil { t.Fatalf("err: %s", err) } // Inner defaults mrb.DefineModuleUnder("Another", nil) _, err = mrb.LoadString("Another") if err != nil { t.Fatalf("err: %s", err) } } func TestMrbFixnumValue(t *testing.T) { mrb := NewMrb() defer mrb.Close() value := mrb.FixnumValue(42) if value.Type() != TypeFixnum { t.Fatalf("should be fixnum") } } func TestMrbFullGC(t *testing.T) { mrb := NewMrb() defer mrb.Close() ai := mrb.ArenaSave() value := mrb.StringValue("foo") if value.IsDead() { t.Fatal("should not be dead") } mrb.ArenaRestore(ai) mrb.FullGC() if !value.IsDead() { t.Fatal("should be dead") } } type testcase struct { args string types []ValueType result []string } func TestMrbGetArgs(t *testing.T) { cases := []testcase{ { `("foo")`, []ValueType{TypeString}, []string{`"foo"`}, }, { `(true)`, []ValueType{TypeTrue}, []string{`true`}, }, { `(Hello)`, []ValueType{TypeClass}, []string{`Hello`}, }, { `() { }`, []ValueType{TypeProc}, nil, }, { `(Hello, "bar", true)`, []ValueType{TypeClass, TypeString, TypeTrue}, []string{`Hello`, `"bar"`, "true"}, }, { `("bar", true) {}`, []ValueType{TypeString, TypeTrue, TypeProc}, nil, }, } // lots of this effort is centered around testing multithreaded behavior. for i := 0; i < 1000; i++ { errChan := make(chan error, len(cases)) for _, tc := range cases { go func(tc testcase) { var actual []*MrbValue testFunc := func(m *Mrb, self *MrbValue) (Value, Value) { actual = m.GetArgs() return self, nil } mrb := NewMrb() defer mrb.Close() class := mrb.DefineClass("Hello", mrb.ObjectClass()) class.DefineClassMethod("test", testFunc, ArgsAny()) _, err := mrb.LoadString(fmt.Sprintf("Hello.test%s", tc.args)) if err != nil { errChan <- fmt.Errorf("err: %s", err) return } if tc.result != nil { if len(actual) != len(tc.result) { errChan <- fmt.Errorf("%s: expected %d, got %d", tc.args, len(tc.result), len(actual)) return } } actualStrings := make([]string, len(actual)) actualTypes := make([]ValueType, len(actual)) for i, v := range actual { str, err := v.Call("inspect") if err != nil { errChan <- err } actualStrings[i] = str.String() actualTypes[i] = v.Type() } if !reflect.DeepEqual(actualTypes, tc.types) { errChan <- fmt.Errorf("code: %s\nexpected: %#v\nactual: %#v", tc.args, tc.types, actualTypes) return } if tc.result != nil { if !reflect.DeepEqual(actualStrings, tc.result) { errChan <- fmt.Errorf("expected: %#v\nactual: %#v", tc.result, actualStrings) return } } errChan <- nil }(tc) } for range cases { if err := <-errChan; err != nil { t.Fatal(err) } } } } func TestMrbGlobalVariable(t *testing.T) { const ( TestValue = "HELLO" ) mrb := NewMrb() defer mrb.Close() if _, err := mrb.LoadString(fmt.Sprintf(`$a = "%s"`, TestValue)); err != nil { t.Fatalf("err: %s", err) } value := mrb.GetGlobalVariable("$a") if value.String() != TestValue { t.Fatalf("wrong value for $a: expected '%s', found '%s'", TestValue, value.String()) } mrb.SetGlobalVariable("$b", mrb.StringValue(TestValue)) value, err := mrb.LoadString(`$b`) if err != nil { t.Fatalf("err: %s", err) } if value.String() != TestValue { t.Fatalf("wrong value for $b: expected '%s', found '%s'", TestValue, value.String()) } } func TestMrbInstanceVariable(t *testing.T) { const ( GoldenRetriever = "golden retriever" Husky = "Husky" ) mrb := NewMrb() defer mrb.Close() _, err := mrb.LoadString(` class Dog def initialize(breed) @breed = breed end def breed "cocker spaniel" # this line exists to ensure that it's not invoking the accessor method end def real_breed @breed end end `) if err != nil { t.Fatalf("err: %s", err) } dogClass := mrb.Class("Dog", nil) if dogClass == nil { t.Fatalf("dog class not found") } inst, err := dogClass.New(mrb.StringValue(GoldenRetriever)) if err != nil { t.Fatalf("err: %s", err) } value := inst.GetInstanceVariable("@breed") if value.String() != GoldenRetriever { t.Fatalf("wrong value for Dog.@breed. expected: '%s', found: '%s'", GoldenRetriever, value.String()) } inst.SetInstanceVariable("@breed", mrb.StringValue(Husky)) value = inst.GetInstanceVariable("@breed") if value.String() != Husky { t.Fatalf("wrong value for Dog.@breed. expected: '%s', found: '%s'", Husky, value.String()) } } func TestMrbLoadString(t *testing.T) { mrb := NewMrb() defer mrb.Close() value, err := mrb.LoadString(`"HELLO"`) if err != nil { t.Fatalf("err: %s", err) } if value == nil { t.Fatalf("should have value") } } func TestMrbLoadString_twice(t *testing.T) { mrb := NewMrb() defer mrb.Close() value, err := mrb.LoadString(`"HELLO"`) if err != nil { t.Fatalf("err: %s", err) } if value == nil { t.Fatalf("should have value") } value, err = mrb.LoadString(`"WORLD"`) if err != nil { t.Fatalf("err: %s", err) } if value.String() != "WORLD" { t.Fatalf("bad: %s", value) } } func TestMrbLoadStringException(t *testing.T) { mrb := NewMrb() defer mrb.Close() _, err := mrb.LoadString(`raise "An exception"`) if err == nil { t.Fatal("exception expected") } value, err := mrb.LoadString(`"test"`) if err != nil { t.Fatal("exception should have been cleared") } if value.String() != "test" { t.Fatal("bad test value returned") } } func TestMrbRaise(t *testing.T) { mrb := NewMrb() defer mrb.Close() cb := func(m *Mrb, self *MrbValue) (Value, Value) { return nil, m.GetArgs()[0] } class := mrb.DefineClass("Hello", mrb.ObjectClass()) class.DefineClassMethod("foo", cb, ArgsReq(1)) _, err := mrb.LoadString(`Hello.foo(ArgumentError.new("ouch"))`) if err == nil { t.Fatal("should have error") } if err.Error() != "ouch" { t.Fatalf("bad: %s", err) } } func TestMrbYield(t *testing.T) { mrb := NewMrb() defer mrb.Close() cb := func(m *Mrb, self *MrbValue) (Value, Value) { result, err := m.Yield(m.GetArgs()[0], Int(12), Int(30)) if err != nil { t.Fatalf("err: %s", err) } return result, nil } class := mrb.DefineClass("Hello", mrb.ObjectClass()) class.DefineClassMethod("foo", cb, ArgsBlock()) value, err := mrb.LoadString(`Hello.foo { |a, b| a + b }`) if err != nil { t.Fatalf("err: %s", err) } if value.Fixnum() != 42 { t.Fatalf("bad: %s", value) } } func TestMrbYieldException(t *testing.T) { mrb := NewMrb() defer mrb.Close() cb := func(m *Mrb, self *MrbValue) (Value, Value) { result, err := m.Yield(m.GetArgs()[0]) if err != nil { exc := err.(*Exception) return nil, exc.MrbValue } return result, nil } class := mrb.DefineClass("Hello", mrb.ObjectClass()) class.DefineClassMethod("foo", cb, ArgsBlock()) _, err := mrb.LoadString(`Hello.foo { raise "exception" }`) if err == nil { t.Fatal("should error") } _, err = mrb.LoadString(`Hello.foo { 1 }`) if err != nil { t.Fatal("exception should have been cleared") } } func TestMrbRun(t *testing.T) { mrb := NewMrb() defer mrb.Close() parser := NewParser(mrb) defer parser.Close() context := NewCompileContext(mrb) defer context.Close() parser.Parse(` if $do_raise raise "exception" else "rval" end`, context, ) proc := parser.GenerateCode() // Enable proc exception raising & verify mrb.LoadString(`$do_raise = true`) _, err := mrb.Run(proc, nil) if err == nil { t.Fatalf("expected exception, %#v", err) } // Disable proc exception raising // If we still have an exception, it wasn't cleared from the previous invocation. mrb.LoadString(`$do_raise = false`) rval, err := mrb.Run(proc, nil) if err != nil { t.Fatalf("unexpected exception, %#v", err) } if rval.String() != "rval" { t.Fatalf("expected return value 'rval', got %#v", rval) } parser.Parse(`a = 10`, context) proc = parser.GenerateCode() stackKeep, _, err := mrb.RunWithContext(proc, nil, 0) if err != nil { t.Fatal(err) } if stackKeep != 2 { t.Fatalf("stack value was %d not 2; some variables may not have been captured", stackKeep) } parser.Parse(`a`, context) proc = parser.GenerateCode() var ret *MrbValue _, ret, err = mrb.RunWithContext(proc, nil, stackKeep) if err != nil { t.Fatal(err) } if ret.String() != "10" { t.Fatalf("Captured variable was not expected value: was %q", ret.String()) } } func TestMrbDefineMethodConcurrent(t *testing.T) { concurrency := 100 numFuncs := 100 cb := func(m *Mrb, self *MrbValue) (Value, Value) { return m.GetArgs()[0], nil } syncChan := make(chan struct{}, concurrency) for i := 0; i < concurrency; i++ { go func() { mrb := NewMrb() defer mrb.Close() for i := 0; i < numFuncs; i++ { mrb.TopSelf().SingletonClass().DefineMethod(fmt.Sprintf("test%d", i), cb, ArgsAny()) } syncChan <- struct{}{} }() } for i := 0; i < concurrency; i++ { <-syncChan } } func TestMrbStackedException(t *testing.T) { var testClass *Class createException := func(m *Mrb, msg string) Value { val, err := m.Class("Exception", nil).New(String(msg)) if err != nil { panic(fmt.Sprintf("could not construct exception for return: %v", err)) } return val } testFunc := func(m *Mrb, self *MrbValue) (Value, Value) { args := m.GetArgs() t, err := testClass.New() if err != nil { return nil, createException(m, err.Error()) } argv := []Value{} for _, arg := range args { argv = append(argv, Value(arg)) } v, err := t.Call("dotest!", argv...) if err != nil { return nil, createException(m, err.Error()) } return v, nil } doTestFunc := func(m *Mrb, self *MrbValue) (Value, Value) { err := createException(m, "Fail us!") return nil, err } mrb := NewMrb() testClass = mrb.DefineClass("TestClass", nil) testClass.DefineMethod("dotest!", doTestFunc, ArgsReq(0)|ArgsOpt(3)) mrb.TopSelf().SingletonClass().DefineMethod("test", testFunc, ArgsReq(0)|ArgsOpt(3)) _, err := mrb.LoadString("test") if err == nil { t.Fatal("No exception when one was expected") return } mrb.Close() mrb = NewMrb() evalFunc := func(m *Mrb, self *MrbValue) (Value, Value) { arg := m.GetArgs()[0] result, err := self.CallBlock("instance_eval", arg) if err != nil { return result, createException(m, err.Error()) } return result, nil } mrb.TopSelf().SingletonClass().DefineMethod("myeval", evalFunc, ArgsBlock()) result, err := mrb.LoadString("myeval { raise 'foo' }") if err == nil { t.Fatal("did not error") return } if result != nil { t.Fatal("result was not cleared") return } mrb.Close() }