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  }