zombiezen.com/go/lua@v0.0.0-20231013005828-290725fb9140/lua_test.go (about) 1 // Copyright 2023 Ross Light 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 // this software and associated documentation files (the “Software”), to deal in 5 // the Software without restriction, including without limitation the rights to 6 // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 // the Software, and to permit persons to whom the Software is furnished to do so, 8 // subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in all 11 // copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 // 20 // SPDX-License-Identifier: MIT 21 22 package lua 23 24 import ( 25 "errors" 26 "io" 27 "strings" 28 "testing" 29 "testing/iotest" 30 "unsafe" 31 32 "zombiezen.com/go/lua/internal/lua54" 33 ) 34 35 func TestLoad(t *testing.T) { 36 t.Run("Success", func(t *testing.T) { 37 state := new(State) 38 defer func() { 39 if err := state.Close(); err != nil { 40 t.Error("Close:", err) 41 } 42 }() 43 44 const source = "return 2 + 2" 45 if err := state.Load(strings.NewReader(source), source, "t"); err != nil { 46 t.Fatal(err) 47 } 48 if err := state.Call(0, 1, 0); err != nil { 49 t.Fatal(err) 50 } 51 if !state.IsNumber(-1) { 52 t.Fatalf("top of stack is %v; want number", state.Type(-1)) 53 } 54 const want = int64(4) 55 if got, ok := state.ToInteger(-1); got != want || !ok { 56 t.Errorf("state.ToInteger(-1) = %d, %t; want %d, true", got, ok, want) 57 } 58 }) 59 60 t.Run("ReadError", func(t *testing.T) { 61 state := new(State) 62 defer func() { 63 if err := state.Close(); err != nil { 64 t.Error("Close:", err) 65 } 66 }() 67 68 const message = "bork" 69 r := io.MultiReader(strings.NewReader("return"), iotest.ErrReader(errors.New(message))) 70 err := state.Load(r, "=(reader)", "t") 71 if err == nil { 72 t.Error("state.Load(...) = <nil>; want error") 73 } else if got := err.Error(); !strings.Contains(got, message) { 74 t.Errorf("state.Load(...) = %v; want to contain %q", got, message) 75 } 76 if got, ok := state.ToString(-1); !strings.Contains(got, message) || !ok { 77 t.Errorf("state.ToString(-1) = %q, %t; want to contain %q", got, ok, message) 78 } 79 }) 80 } 81 82 func TestLoadString(t *testing.T) { 83 state := new(State) 84 defer func() { 85 if err := state.Close(); err != nil { 86 t.Error("Close:", err) 87 } 88 }() 89 90 const source = "return 2 + 2" 91 if err := state.LoadString(source, source, "t"); err != nil { 92 t.Fatal(err) 93 } 94 if err := state.Call(0, 1, 0); err != nil { 95 t.Fatal(err) 96 } 97 if !state.IsNumber(-1) { 98 t.Fatalf("top of stack is %v; want number", state.Type(-1)) 99 } 100 const want = int64(4) 101 if got, ok := state.ToInteger(-1); got != want || !ok { 102 t.Errorf("state.ToInteger(-1) = %d, %t; want %d, true", got, ok, want) 103 } 104 } 105 106 func TestDump(t *testing.T) { 107 state := new(State) 108 defer func() { 109 if err := state.Close(); err != nil { 110 t.Error("Close:", err) 111 } 112 }() 113 114 const source = "return 2 + 2" 115 if err := state.LoadString(source, source, "t"); err != nil { 116 t.Fatal(err) 117 } 118 compiledChunk := new(strings.Builder) 119 n, err := state.Dump(compiledChunk, false) 120 if wantN := int64(compiledChunk.Len()); n != wantN || err != nil { 121 t.Errorf("state.Dump(...) = %d, %v; want %d, <nil>", n, err, wantN) 122 } 123 state.Pop(1) 124 125 if err := state.LoadString(compiledChunk.String(), "=(load)", "b"); err != nil { 126 t.Fatal(err) 127 } 128 if err := state.Call(0, 1, 0); err != nil { 129 t.Fatal(err) 130 } 131 if !state.IsNumber(-1) { 132 t.Fatalf("top of stack is %v; want number", state.Type(-1)) 133 } 134 const want = int64(4) 135 if got, ok := state.ToInteger(-1); got != want || !ok { 136 t.Errorf("state.ToInteger(-1) = %d, %t; want %d, true", got, ok, want) 137 } 138 } 139 140 func TestFullUserdata(t *testing.T) { 141 state := new(State) 142 defer func() { 143 if err := state.Close(); err != nil { 144 t.Error("Close:", err) 145 } 146 }() 147 148 state.NewUserdataUV(4, 1) 149 if got, want := state.RawLen(-1), uint64(4); got != want { 150 t.Errorf("state.RawLen(-1) = %d; want %d", got, want) 151 } 152 var gotBlock [4]byte 153 if got, want := state.CopyUserdata(gotBlock[:], -1, 0), 4; got != want { 154 t.Errorf("CopyUserdata(...) = %d; want %d", got, want) 155 } else if want := ([4]byte{}); gotBlock != want { 156 t.Errorf("after init, block = %v; want %v", gotBlock, want) 157 } 158 state.SetUserdata(-1, 0, []byte{0xde, 0xad, 0xbe, 0xef}) 159 if got, want := state.CopyUserdata(gotBlock[:], -1, 0), 4; got != want { 160 t.Errorf("CopyUserdata(...) = %d; want %d", got, want) 161 } else if want := ([4]byte{0xde, 0xad, 0xbe, 0xef}); gotBlock != want { 162 t.Errorf("after init, block = %v; want %v", gotBlock, want) 163 } 164 165 const wantUserValue = 42 166 state.PushInteger(wantUserValue) 167 if !state.SetUserValue(-2, 1) { 168 t.Error("Userdata does not have value 1") 169 } 170 if got, want := state.UserValue(-1, 1), TypeNumber; got != want { 171 t.Errorf("user value 1 type = %v; want %v", got, want) 172 } 173 if got, ok := state.ToInteger(-1); got != wantUserValue || !ok { 174 value, err := ToString(state, -1) 175 if err != nil { 176 value = "<unknown value>" 177 } 178 t.Errorf("user value 1 = %s; want %d", value, wantUserValue) 179 } 180 state.Pop(1) 181 182 if got, want := state.UserValue(-1, 2), TypeNone; got != want { 183 t.Errorf("user value 2 type = %v; want %v", got, want) 184 } 185 if got, want := state.Top(), 2; got != want { 186 t.Errorf("after state.UserValue(-1, 2), state.Top() = %d; want %d", got, want) 187 } 188 if !state.IsNil(-1) { 189 value, err := ToString(state, -1) 190 if err != nil { 191 value = "<unknown value>" 192 } 193 t.Errorf("user value 2 = %s; want nil", value) 194 } 195 } 196 197 func TestLightUserdata(t *testing.T) { 198 state := new(State) 199 defer func() { 200 if err := state.Close(); err != nil { 201 t.Error("Close:", err) 202 } 203 }() 204 205 vals := []uintptr{0, 42} 206 for _, p := range vals { 207 state.PushLightUserdata(p) 208 } 209 210 if got, want := state.Top(), len(vals); got != want { 211 t.Fatalf("state.Top() = %d; want %d", got, want) 212 } 213 for i := 1; i <= len(vals); i++ { 214 if got, want := state.Type(i), TypeLightUserdata; got != want { 215 t.Errorf("state.Type(%d) = %v; want %v", i, got, want) 216 } 217 if !state.IsUserdata(i) { 218 t.Errorf("state.IsUserdata(%d) = false; want true", i) 219 } 220 if got, want := state.ToPointer(i), vals[i-1]; got != want { 221 t.Errorf("state.ToPointer(%d) = %#x; want %#x", i, got, want) 222 } 223 } 224 } 225 226 func TestPushClosure(t *testing.T) { 227 t.Run("NoUpvalues", func(t *testing.T) { 228 state := new(State) 229 defer func() { 230 if err := state.Close(); err != nil { 231 t.Error("Close:", err) 232 } 233 }() 234 235 const want = 42 236 state.PushClosure(0, func(l *State) (int, error) { 237 l.PushInteger(want) 238 return 1, nil 239 }) 240 if err := state.Call(0, 1, 0); err != nil { 241 t.Fatal(err) 242 } 243 if got, ok := state.ToInteger(-1); got != want || !ok { 244 value, err := ToString(state, -1) 245 if err != nil { 246 value = "<unknown value>" 247 } 248 t.Errorf("function returned %s; want %d", value, want) 249 } 250 }) 251 252 t.Run("Upvalues", func(t *testing.T) { 253 state := new(State) 254 defer func() { 255 if err := state.Close(); err != nil { 256 t.Error("Close:", err) 257 } 258 }() 259 260 const want = 42 261 state.PushInteger(want) 262 state.PushClosure(1, func(l *State) (int, error) { 263 l.PushValue(UpvalueIndex(1)) 264 return 1, nil 265 }) 266 if err := state.Call(0, 1, 0); err != nil { 267 t.Fatal(err) 268 } 269 if got, ok := state.ToInteger(-1); got != want || !ok { 270 value, err := ToString(state, -1) 271 if err != nil { 272 value = "<unknown value>" 273 } 274 t.Errorf("function returned %s; want %d", value, want) 275 } 276 }) 277 } 278 279 // TestStateRepresentation ensures that State has the same memory representation 280 // as lua54.State. 281 // This is critical for the correct functioning of [State.PushClosure], 282 // which avoids allocating a new closure by using a func(*State) (int, error) 283 // as a func(*lua54.State) (int, error). 284 func TestStateRepresentation(t *testing.T) { 285 if got, want := unsafe.Offsetof(State{}.state), uintptr(0); got != want { 286 t.Errorf("unsafe.Offsetof(State{}.state) = %d; want %d", got, want) 287 } 288 if got, want := unsafe.Sizeof(State{}), unsafe.Sizeof(lua54.State{}); got != want { 289 t.Errorf("unsafe.Sizeof(State{}) = %d; want %d", got, want) 290 } 291 if got, want := unsafe.Alignof(State{}), unsafe.Alignof(lua54.State{}); got%want != 0 { 292 t.Errorf("unsafe.Alignof(State{}) = %d; want %d", got, want) 293 } 294 } 295 296 func BenchmarkExec(b *testing.B) { 297 state := new(State) 298 defer func() { 299 if err := state.Close(); err != nil { 300 b.Error("Close:", err) 301 } 302 }() 303 304 const source = "return 2 + 2" 305 for i := 0; i < b.N; i++ { 306 if err := state.LoadString(source, source, "t"); err != nil { 307 b.Fatal(err) 308 } 309 if err := state.Call(0, 1, 0); err != nil { 310 b.Fatal(err) 311 } 312 state.Pop(1) 313 } 314 } 315 316 func BenchmarkPushClosure(b *testing.B) { 317 b.ReportAllocs() 318 319 state := new(State) 320 defer func() { 321 if err := state.Close(); err != nil { 322 b.Error("Close:", err) 323 } 324 }() 325 326 f := Function(func(l *State) (int, error) { return 0, nil }) 327 for i := 0; i < b.N; i++ { 328 state.PushClosure(0, f) 329 state.Pop(1) 330 } 331 } 332 333 func BenchmarkOpenLibraries(b *testing.B) { 334 b.ReportAllocs() 335 336 state := new(State) 337 defer func() { 338 if err := state.Close(); err != nil { 339 b.Error("Close:", err) 340 } 341 }() 342 343 for i := 0; i < b.N; i++ { 344 if err := OpenLibraries(state); err != nil { 345 b.Fatal(err) 346 } 347 } 348 }