1 package wasm
2
3 import (
4 "context"
5 "errors"
6 "fmt"
7 "sync"
8 "testing"
9 "time"
10
11 "github.com/tetratelabs/wazero/experimental/sys"
12 internalsys "github.com/tetratelabs/wazero/internal/sys"
13 "github.com/tetratelabs/wazero/internal/sysfs"
14 testfs "github.com/tetratelabs/wazero/internal/testing/fs"
15 "github.com/tetratelabs/wazero/internal/testing/hammer"
16 "github.com/tetratelabs/wazero/internal/testing/require"
17 )
18
19 func TestModuleInstance_String(t *testing.T) {
20 s := newStore()
21
22 tests := []struct {
23 name, moduleName, expected string
24 }{
25 {
26 name: "empty",
27 moduleName: "",
28 expected: "Module[]",
29 },
30 {
31 name: "not empty",
32 moduleName: "math",
33 expected: "Module[math]",
34 },
35 }
36
37 for _, tt := range tests {
38 tc := tt
39
40 t.Run(tc.name, func(t *testing.T) {
41
42 m, err := s.Instantiate(testCtx, &Module{}, tc.moduleName, nil, nil)
43 defer m.Close(testCtx)
44
45 require.NoError(t, err)
46 require.Equal(t, tc.expected, m.String())
47
48 if name := m.Name(); name != "" {
49 sm := s.Module(m.Name())
50 if sm != nil {
51 require.Equal(t, tc.expected, s.Module(m.Name()).String())
52 } else {
53 require.Zero(t, len(m.Name()))
54 }
55 }
56 })
57 }
58 }
59
60 func TestModuleInstance_Close(t *testing.T) {
61 s := newStore()
62
63 tests := []struct {
64 name string
65 closer func(context.Context, *ModuleInstance) error
66 expectedClosed uint64
67 }{
68 {
69 name: "Close()",
70 closer: func(ctx context.Context, m *ModuleInstance) error {
71 return m.Close(ctx)
72 },
73 expectedClosed: uint64(1),
74 },
75 {
76 name: "CloseWithExitCode(255)",
77 closer: func(ctx context.Context, m *ModuleInstance) error {
78 return m.CloseWithExitCode(ctx, 255)
79 },
80 expectedClosed: uint64(255)<<32 + 1,
81 },
82 }
83
84 for _, tt := range tests {
85 tc := tt
86 t.Run(fmt.Sprintf("%s calls ns.CloseWithExitCode(module.name))", tc.name), func(t *testing.T) {
87 for _, ctx := range []context.Context{nil, testCtx} {
88 moduleName := t.Name()
89 m, err := s.Instantiate(ctx, &Module{}, moduleName, nil, nil)
90 require.NoError(t, err)
91
92
93
94 require.Equal(t, s.Module(moduleName), m)
95
96
97 require.NoError(t, tc.closer(ctx, m))
98
99 require.Equal(t, tc.expectedClosed, m.Closed.Load())
100
101
102 require.True(t, m.IsClosed())
103
104
105 require.Nil(t, s.Module(moduleName))
106
107
108 require.NoError(t, tc.closer(ctx, m))
109 }
110 })
111 }
112
113 t.Run("calls Context.Close()", func(t *testing.T) {
114 testFS := &sysfs.AdaptFS{FS: testfs.FS{"foo": &testfs.File{}}}
115 sysCtx := internalsys.DefaultContext(testFS)
116 fsCtx := sysCtx.FS()
117
118 _, errno := fsCtx.OpenFile(testFS, "/foo", sys.O_RDONLY, 0)
119 require.EqualErrno(t, 0, errno)
120
121 m, err := s.Instantiate(testCtx, &Module{}, t.Name(), sysCtx, nil)
122 require.NoError(t, err)
123
124
125
126 _, ok := fsCtx.LookupFile(3)
127 require.True(t, ok, "sysCtx.openedFiles was empty")
128
129
130 hammer.NewHammer(t, 100, 10).Run(func(name string) {
131 require.NoError(t, m.Close(testCtx))
132
133 require.NoError(t, m.closeWithExitCode(testCtx, 0))
134 }, nil)
135 if t.Failed() {
136 return
137 }
138
139
140 _, ok = fsCtx.LookupFile(3)
141 require.False(t, ok, "expected no opened files")
142
143
144 require.NoError(t, m.Close(testCtx))
145 })
146
147 t.Run("error closing", func(t *testing.T) {
148
149 testFS := &sysfs.AdaptFS{FS: testfs.FS{"foo": &testfs.File{CloseErr: errors.New("error closing")}}}
150 sysCtx := internalsys.DefaultContext(testFS)
151 fsCtx := sysCtx.FS()
152
153 _, errno := fsCtx.OpenFile(testFS, "/foo", sys.O_RDONLY, 0)
154 require.EqualErrno(t, 0, errno)
155
156 m, err := s.Instantiate(testCtx, &Module{}, t.Name(), sysCtx, nil)
157 require.NoError(t, err)
158
159
160 require.EqualErrno(t, sys.EIO, m.Close(testCtx))
161
162
163 _, ok := fsCtx.LookupFile(3)
164 require.False(t, ok, "expected no opened files")
165 })
166 }
167
168 func TestModuleInstance_CallDynamic(t *testing.T) {
169 s := newStore()
170
171 tests := []struct {
172 name string
173 closer func(context.Context, *ModuleInstance) error
174 expectedClosed uint64
175 }{
176 {
177 name: "Close()",
178 closer: func(ctx context.Context, m *ModuleInstance) error {
179 return m.Close(ctx)
180 },
181 expectedClosed: uint64(1),
182 },
183 {
184 name: "CloseWithExitCode(255)",
185 closer: func(ctx context.Context, m *ModuleInstance) error {
186 return m.CloseWithExitCode(ctx, 255)
187 },
188 expectedClosed: uint64(255)<<32 + 1,
189 },
190 }
191
192 for _, tt := range tests {
193 tc := tt
194 t.Run(fmt.Sprintf("%s calls ns.CloseWithExitCode(module.name))", tc.name), func(t *testing.T) {
195 for _, ctx := range []context.Context{nil, testCtx} {
196 moduleName := t.Name()
197 m, err := s.Instantiate(ctx, &Module{}, moduleName, nil, nil)
198 require.NoError(t, err)
199
200
201
202 require.Equal(t, s.Module(moduleName), m)
203
204
205 require.NoError(t, tc.closer(ctx, m))
206
207 require.Equal(t, tc.expectedClosed, m.Closed.Load())
208
209
210 require.Nil(t, s.Module(moduleName))
211
212
213 require.NoError(t, tc.closer(ctx, m))
214 }
215 })
216 }
217
218 t.Run("calls Context.Close()", func(t *testing.T) {
219 testFS := &sysfs.AdaptFS{FS: testfs.FS{"foo": &testfs.File{}}}
220 sysCtx := internalsys.DefaultContext(testFS)
221 fsCtx := sysCtx.FS()
222
223 _, errno := fsCtx.OpenFile(testFS, "/foo", sys.O_RDONLY, 0)
224 require.EqualErrno(t, 0, errno)
225
226 m, err := s.Instantiate(testCtx, &Module{}, t.Name(), sysCtx, nil)
227 require.NoError(t, err)
228
229
230
231 _, ok := fsCtx.LookupFile(3)
232 require.True(t, ok, "sysCtx.openedFiles was empty")
233
234
235 require.NoError(t, m.Close(testCtx))
236
237
238 _, ok = fsCtx.LookupFile(3)
239 require.False(t, ok, "expected no opened files")
240
241
242 require.NoError(t, m.Close(testCtx))
243 })
244
245 t.Run("error closing", func(t *testing.T) {
246
247 testFS := &sysfs.AdaptFS{FS: testfs.FS{"foo": &testfs.File{CloseErr: errors.New("error closing")}}}
248 sysCtx := internalsys.DefaultContext(testFS)
249 fsCtx := sysCtx.FS()
250
251 path := "/foo"
252 _, errno := fsCtx.OpenFile(testFS, path, sys.O_RDONLY, 0)
253 require.EqualErrno(t, 0, errno)
254
255 m, err := s.Instantiate(testCtx, &Module{}, t.Name(), sysCtx, nil)
256 require.NoError(t, err)
257
258
259 require.EqualErrno(t, sys.EIO, m.Close(testCtx))
260
261
262 _, ok := fsCtx.LookupFile(3)
263 require.False(t, ok, "expected no opened files")
264 })
265 }
266
267 func TestModuleInstance_CloseModuleOnCanceledOrTimeout(t *testing.T) {
268 s := newStore()
269 t.Run("timeout", func(t *testing.T) {
270 cc := &ModuleInstance{ModuleName: "test", s: s, Sys: internalsys.DefaultContext(nil)}
271 const duration = time.Second
272 ctx, cancel := context.WithTimeout(context.Background(), duration)
273 defer cancel()
274 done := cc.CloseModuleOnCanceledOrTimeout(context.WithValue(ctx, struct{}{}, 1))
275 time.Sleep(duration * 2)
276 defer done()
277
278
279 require.Equal(t, exitCodeFlag(exitCodeFlagResourceNotClosed), cc.Closed.Load()&exitCodeFlagMask)
280 require.NotNil(t, cc.Sys)
281
282 err := cc.FailIfClosed()
283 require.EqualError(t, err, "module closed with context deadline exceeded")
284
285
286 require.Nil(t, cc.Sys)
287 })
288
289 t.Run("cancel", func(t *testing.T) {
290 cc := &ModuleInstance{ModuleName: "test", s: s, Sys: internalsys.DefaultContext(nil)}
291 ctx, cancel := context.WithCancel(context.Background())
292 done := cc.CloseModuleOnCanceledOrTimeout(context.WithValue(ctx, struct{}{}, 1))
293 cancel()
294
295 cancel()
296 cancel()
297 defer done()
298 time.Sleep(time.Second)
299
300
301 require.Equal(t, exitCodeFlag(exitCodeFlagResourceNotClosed), cc.Closed.Load()&exitCodeFlagMask)
302 require.NotNil(t, cc.Sys)
303
304 err := cc.FailIfClosed()
305 require.EqualError(t, err, "module closed with context canceled")
306
307
308 require.Nil(t, cc.Sys)
309 })
310
311 t.Run("timeout over cancel", func(t *testing.T) {
312 cc := &ModuleInstance{ModuleName: "test", s: s, Sys: internalsys.DefaultContext(nil)}
313 const duration = time.Second
314 ctx, cancel := context.WithCancel(context.Background())
315 defer cancel()
316
317 ctx, cancel = context.WithTimeout(ctx, duration)
318 defer cancel()
319 done := cc.CloseModuleOnCanceledOrTimeout(context.WithValue(ctx, struct{}{}, 1))
320 time.Sleep(duration * 2)
321 defer done()
322
323
324 require.Equal(t, exitCodeFlag(exitCodeFlagResourceNotClosed), cc.Closed.Load()&exitCodeFlagMask)
325 require.NotNil(t, cc.Sys)
326
327 err := cc.FailIfClosed()
328 require.EqualError(t, err, "module closed with context deadline exceeded")
329
330
331 require.Nil(t, cc.Sys)
332 })
333
334 t.Run("cancel over timeout", func(t *testing.T) {
335 cc := &ModuleInstance{ModuleName: "test", s: s, Sys: internalsys.DefaultContext(nil)}
336 ctx, cancel := context.WithCancel(context.Background())
337
338 var timeoutDone context.CancelFunc
339 ctx, timeoutDone = context.WithTimeout(ctx, time.Second*1000)
340 defer timeoutDone()
341
342 done := cc.CloseModuleOnCanceledOrTimeout(context.WithValue(ctx, struct{}{}, 1))
343 cancel()
344 defer done()
345
346 time.Sleep(time.Second)
347
348
349 require.Equal(t, exitCodeFlag(exitCodeFlagResourceNotClosed), cc.Closed.Load()&exitCodeFlagMask)
350 require.NotNil(t, cc.Sys)
351
352 err := cc.FailIfClosed()
353 require.EqualError(t, err, "module closed with context canceled")
354
355
356 require.Nil(t, cc.Sys)
357 })
358
359 t.Run("cancel works", func(t *testing.T) {
360 cc := &ModuleInstance{ModuleName: "test", s: s}
361 cancelChan := make(chan struct{})
362 var wg sync.WaitGroup
363 wg.Add(1)
364
365
366 go func() {
367 defer wg.Done()
368 cc.closeModuleOnCanceledOrTimeout(context.Background(), cancelChan)
369 }()
370 close(cancelChan)
371 wg.Wait()
372 })
373
374 t.Run("no close on all resources canceled", func(t *testing.T) {
375 cc := &ModuleInstance{ModuleName: "test", s: s}
376 cancelChan := make(chan struct{})
377 close(cancelChan)
378 ctx, cancel := context.WithCancel(context.Background())
379 cancel()
380
381 cc.closeModuleOnCanceledOrTimeout(ctx, cancelChan)
382
383 err := cc.FailIfClosed()
384 require.Nil(t, err)
385 })
386 }
387
388 func TestModuleInstance_CloseWithCtxErr(t *testing.T) {
389 s := newStore()
390
391 t.Run("context canceled", func(t *testing.T) {
392 cc := &ModuleInstance{ModuleName: "test", s: s}
393 ctx, cancel := context.WithCancel(context.Background())
394 cancel()
395
396 cc.CloseWithCtxErr(ctx)
397
398 err := cc.FailIfClosed()
399 require.EqualError(t, err, "module closed with context canceled")
400 })
401
402 t.Run("context timeout", func(t *testing.T) {
403 cc := &ModuleInstance{ModuleName: "test", s: s}
404 duration := time.Second
405 ctx, cancel := context.WithTimeout(context.Background(), duration)
406 defer cancel()
407
408 time.Sleep(duration * 2)
409
410 cc.CloseWithCtxErr(ctx)
411
412 err := cc.FailIfClosed()
413 require.EqualError(t, err, "module closed with context deadline exceeded")
414 })
415
416 t.Run("no error", func(t *testing.T) {
417 cc := &ModuleInstance{ModuleName: "test", s: s}
418
419 cc.CloseWithCtxErr(context.Background())
420
421 err := cc.FailIfClosed()
422 require.Nil(t, err)
423 })
424 }
425
426 type mockCloser struct{ called int }
427
428 func (m *mockCloser) Close(context.Context) error {
429 m.called++
430 return nil
431 }
432
433 func TestModuleInstance_ensureResourcesClosed(t *testing.T) {
434 closer := &mockCloser{}
435
436 for _, tc := range []struct {
437 name string
438 m *ModuleInstance
439 }{
440 {m: &ModuleInstance{CodeCloser: closer}},
441 {m: &ModuleInstance{Sys: internalsys.DefaultContext(nil)}},
442 {m: &ModuleInstance{Sys: internalsys.DefaultContext(nil), CodeCloser: closer}},
443 } {
444 err := tc.m.ensureResourcesClosed(context.Background())
445 require.NoError(t, err)
446 require.Nil(t, tc.m.Sys)
447 require.Nil(t, tc.m.CodeCloser)
448
449
450 err = tc.m.ensureResourcesClosed(context.Background())
451 require.NoError(t, err)
452 }
453 require.Equal(t, 2, closer.called)
454 }
455
View as plain text