github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/internal/engine/wazevo/engine_cache_test.go (about)

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