github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/internal/engine/wazevo/engine_cache_test.go (about)

     1  package wazevo
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/sha256"
     6  	"io"
     7  	"testing"
     8  
     9  	"github.com/wasilibs/wazerox/internal/testing/require"
    10  	"github.com/wasilibs/wazerox/internal/u32"
    11  	"github.com/wasilibs/wazerox/internal/u64"
    12  	"github.com/wasilibs/wazerox/internal/wasm"
    13  )
    14  
    15  var testVersion = "0.0.1"
    16  
    17  func TestSerializeCompiledModule(t *testing.T) {
    18  	tests := []struct {
    19  		in  *compiledModule
    20  		exp []byte
    21  	}{
    22  		{
    23  			in: &compiledModule{
    24  				executables:     &executables{executable: []byte{1, 2, 3, 4, 5}},
    25  				functionOffsets: []int{0},
    26  			},
    27  			exp: concat(
    28  				magic,
    29  				[]byte{byte(len(testVersion))},
    30  				[]byte(testVersion),
    31  				u32.LeBytes(1),        // number of functions.
    32  				u64.LeBytes(0),        // offset.
    33  				u64.LeBytes(5),        // length of code.
    34  				[]byte{1, 2, 3, 4, 5}, // code.
    35  				[]byte{0},             // no source map.
    36  			),
    37  		},
    38  		{
    39  			in: &compiledModule{
    40  				executables:     &executables{executable: []byte{1, 2, 3, 4, 5}},
    41  				functionOffsets: []int{0},
    42  			},
    43  			exp: concat(
    44  				magic,
    45  				[]byte{byte(len(testVersion))},
    46  				[]byte(testVersion),
    47  				u32.LeBytes(1),        // number of functions.
    48  				u64.LeBytes(0),        // offset.
    49  				u64.LeBytes(5),        // length of code.
    50  				[]byte{1, 2, 3, 4, 5}, // code.
    51  				[]byte{0},             // no source map.
    52  			),
    53  		},
    54  		{
    55  			in: &compiledModule{
    56  				executables:     &executables{executable: []byte{1, 2, 3, 4, 5, 1, 2, 3}},
    57  				functionOffsets: []int{0, 5},
    58  			},
    59  			exp: concat(
    60  				magic,
    61  				[]byte{byte(len(testVersion))},
    62  				[]byte(testVersion),
    63  				u32.LeBytes(2), // number of functions.
    64  				// Function index = 0.
    65  				u64.LeBytes(0), // offset.
    66  				// Function index = 1.
    67  				u64.LeBytes(5), // offset.
    68  				// Executable.
    69  				u64.LeBytes(8),                 // length of code.
    70  				[]byte{1, 2, 3, 4, 5, 1, 2, 3}, // code.
    71  				[]byte{0},                      // no source map.
    72  			),
    73  		},
    74  	}
    75  
    76  	for i, tc := range tests {
    77  		actual, err := io.ReadAll(serializeCompiledModule(testVersion, tc.in))
    78  		require.NoError(t, err, i)
    79  		require.Equal(t, tc.exp, actual, i)
    80  	}
    81  }
    82  
    83  func concat(ins ...[]byte) (ret []byte) {
    84  	for _, in := range ins {
    85  		ret = append(ret, in...)
    86  	}
    87  	return
    88  }
    89  
    90  func TestDeserializeCompiledModule(t *testing.T) {
    91  	tests := []struct {
    92  		name                  string
    93  		in                    []byte
    94  		importedFunctionCount uint32
    95  		expCompiledModule     *compiledModule
    96  		expStaleCache         bool
    97  		expErr                string
    98  	}{
    99  		{
   100  			name:   "invalid header",
   101  			in:     []byte{1},
   102  			expErr: "compilationcache: invalid header length: 1",
   103  		},
   104  		{
   105  			name: "invalid magic",
   106  			in: concat(
   107  				[]byte{'a', 'b', 'c', 'd', 'e', 'f'},
   108  				[]byte{byte(len(testVersion))},
   109  				[]byte(testVersion),
   110  				u32.LeBytes(1), // number of functions.
   111  			),
   112  			expErr: "compilationcache: invalid magic number: got WAZEVO but want abcdef",
   113  		},
   114  		{
   115  			name: "version mismatch",
   116  			in: concat(
   117  				magic,
   118  				[]byte{byte(len("1233123.1.1"))},
   119  				[]byte("1233123.1.1"),
   120  				u32.LeBytes(1), // number of functions.
   121  			),
   122  			expStaleCache: true,
   123  		},
   124  		{
   125  			name: "version mismatch",
   126  			in: concat(
   127  				magic,
   128  				[]byte{byte(len("0.0.0"))},
   129  				[]byte("0.0.0"),
   130  				u32.LeBytes(1), // number of functions.
   131  			),
   132  			expStaleCache: true,
   133  		},
   134  		{
   135  			name: "one function",
   136  			in: concat(
   137  				magic,
   138  				[]byte{byte(len(testVersion))},
   139  				[]byte(testVersion),
   140  				u32.LeBytes(1), // number of functions.
   141  				u64.LeBytes(0), // offset.
   142  				// Executable.
   143  				u64.LeBytes(5),        // size.
   144  				[]byte{1, 2, 3, 4, 5}, // machine code.
   145  				[]byte{0},             // no source map.
   146  			),
   147  			expCompiledModule: &compiledModule{
   148  				executables:     &executables{executable: []byte{1, 2, 3, 4, 5}},
   149  				functionOffsets: []int{0},
   150  			},
   151  			expStaleCache: false,
   152  			expErr:        "",
   153  		},
   154  		{
   155  			name: "two functions",
   156  			in: concat(
   157  				magic,
   158  				[]byte{byte(len(testVersion))},
   159  				[]byte(testVersion),
   160  				u32.LeBytes(2), // number of functions.
   161  				// Function index = 0.
   162  				u64.LeBytes(0), // offset.
   163  				// Function index = 1.
   164  				u64.LeBytes(7), // offset.
   165  				// Executable.
   166  				u64.LeBytes(10),                       // size.
   167  				[]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, // machine code.
   168  				[]byte{0},                             // no source map.
   169  			),
   170  			importedFunctionCount: 1,
   171  			expCompiledModule: &compiledModule{
   172  				executables:     &executables{executable: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}},
   173  				functionOffsets: []int{0, 7},
   174  			},
   175  			expStaleCache: false,
   176  			expErr:        "",
   177  		},
   178  		{
   179  			name: "reading executable offset",
   180  			in: concat(
   181  				[]byte(magic),
   182  				[]byte{byte(len(testVersion))},
   183  				[]byte(testVersion),
   184  				u32.LeBytes(2), // number of functions.
   185  				// Function index = 0.
   186  				u64.LeBytes(5), // offset.
   187  				// Function index = 1.
   188  			),
   189  			expErr: "compilationcache: error reading func[1] executable offset: EOF",
   190  		},
   191  		{
   192  			name: "mmapping",
   193  			in: concat(
   194  				[]byte(magic),
   195  				[]byte{byte(len(testVersion))},
   196  				[]byte(testVersion),
   197  				u32.LeBytes(2), // number of functions.
   198  				// Function index = 0.
   199  				u64.LeBytes(0), // offset.
   200  				// Function index = 1.
   201  				u64.LeBytes(5), // offset.
   202  				// Executable.
   203  				u64.LeBytes(5), // size of the executable.
   204  				// Lack of machine code here.
   205  			),
   206  			expErr: "compilationcache: error reading executable (len=5): EOF",
   207  		},
   208  		{
   209  			name: "no source map presence",
   210  			in: concat(
   211  				magic,
   212  				[]byte{byte(len(testVersion))},
   213  				[]byte(testVersion),
   214  				u32.LeBytes(1), // number of functions.
   215  				u64.LeBytes(0), // offset.
   216  				// Executable.
   217  				u64.LeBytes(5),        // size.
   218  				[]byte{1, 2, 3, 4, 5}, // machine code.
   219  			),
   220  			expCompiledModule: &compiledModule{
   221  				executables:     &executables{executable: []byte{1, 2, 3, 4, 5}},
   222  				functionOffsets: []int{0},
   223  			},
   224  			expStaleCache: false,
   225  			expErr:        "compilationcache: error reading source map presence: EOF",
   226  		},
   227  	}
   228  
   229  	for _, tc := range tests {
   230  		tc := tc
   231  		t.Run(tc.name, func(t *testing.T) {
   232  			cm, staleCache, err := deserializeCompiledModule(testVersion, io.NopCloser(bytes.NewReader(tc.in)))
   233  
   234  			if tc.expErr != "" {
   235  				require.EqualError(t, err, tc.expErr)
   236  			} else {
   237  				require.NoError(t, err)
   238  				require.Equal(t, tc.expCompiledModule, cm)
   239  			}
   240  
   241  			require.Equal(t, tc.expStaleCache, staleCache)
   242  		})
   243  	}
   244  }
   245  
   246  func Test_fileCacheKey(t *testing.T) {
   247  	s := sha256.New()
   248  	s.Write([]byte("hello world"))
   249  	m := &wasm.Module{}
   250  	s.Sum(m.ID[:0])
   251  	original := m.ID
   252  	result := fileCacheKey(m)
   253  	require.Equal(t, original, m.ID)
   254  	require.NotEqual(t, original, result)
   255  }