xref: /aosp_15_r20/external/crosvm/rutabaga_gfx/ffi/src/tests/rutabaga_test.c (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1 /*
2  * Copyright 2021 The ChromiumOS Authors
3  * Use of this source code is governed by a BSD-style license that can be
4  * found in the LICENSE file.
5  */
6 
7 #define _GNU_SOURCE
8 #include <assert.h>
9 #include <errno.h>
10 #include <ftw.h>
11 #include <stdbool.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <unistd.h>
16 
17 #include <rutabaga_gfx/rutabaga_gfx_ffi.h>
18 
19 #include "virtgpu_cross_domain_protocol.h"
20 
21 #define CHECK_RESULT(result)                                                                       \
22     do {                                                                                           \
23         if (result) {                                                                              \
24             printf("CHECK_RESULT failed in %s() %s:%d\n", __func__, __FILE__, __LINE__);           \
25             return result;                                                                         \
26         }                                                                                          \
27     } while (0)
28 
29 #define CHECK(cond)                                                                                \
30     do {                                                                                           \
31         if (!(cond)) {                                                                             \
32             printf("CHECK failed in %s() %s:%d\n", __func__, __FILE__, __LINE__);                  \
33             return -EINVAL;                                                                        \
34         }                                                                                          \
35     } while (0)
36 
37 #define DEFAULT_BUFFER_SIZE 4096
38 #define WIDTH 512
39 #define HEIGHT 512
40 #define NUM_ITERATIONS 4
41 
42 #define GBM_BO_USE_LINEAR (1 << 4)
43 #define GBM_BO_USE_SCANOUT (1 << 5)
44 #define fourcc_code(a, b, c, d)                                                                    \
45     ((uint32_t)(a) | ((uint32_t)(b) << 8) | ((uint32_t)(c) << 16) | ((uint32_t)(d) << 24))
46 #define DRM_FORMAT_XRGB8888 fourcc_code('X', 'R', '2', '4');
47 
48 #define PIPE_TEXTURE_2D 2
49 #define PIPE_BIND_RENDER_TARGET 2
50 #define VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM 1
51 
52 static int s_resource_id = 1;
53 static int s_fence_id = 1;
54 
55 #if defined(__linux__)
56 static char *s_wayland_path = "/run/user/1000/wayland-0";
57 #elif defined(__Fuchsia__)
58 #endif
59 
60 struct rutabaga_test {
61     struct rutabaga *rutabaga;
62     uint32_t ctx_id;
63     uint64_t value;
64     uint32_t query_ring_id;
65     uint32_t channel_ring_id;
66     struct iovec *query_iovecs;
67     struct iovec *channel_iovecs;
68 };
69 
rutabaga_test_write_fence(uint64_t user_data,const struct rutabaga_fence * fence)70 static void rutabaga_test_write_fence(uint64_t user_data, const struct rutabaga_fence *fence)
71 {
72     struct rutabaga_test *test = (void *)(uintptr_t)user_data;
73     test->value = fence->fence_id;
74 }
75 
rutabaga_test_debug_cb(uint64_t user_data,const struct rutabaga_debug * debug)76 static void rutabaga_test_debug_cb(uint64_t user_data, const struct rutabaga_debug *debug)
77 {
78     if (debug->message) {
79         printf("The debug message is %s\n", debug->message);
80     }
81 }
82 
test_capset_mask_calculation(void)83 static int test_capset_mask_calculation(void)
84 {
85     int result;
86     uint64_t capset_mask;
87 
88     result = rutabaga_calculate_capset_mask("cross-domain:gfxstream-vulkan", &capset_mask);
89     CHECK_RESULT(result);
90     CHECK(capset_mask ==
91           ((1 << RUTABAGA_CAPSET_CROSS_DOMAIN) | (1 << RUTABAGA_CAPSET_GFXSTREAM_VULKAN)));
92 
93     result = rutabaga_calculate_capset_mask(":gfxstream-vulkan", &capset_mask);
94     CHECK_RESULT(result);
95     CHECK(capset_mask == (1 << RUTABAGA_CAPSET_GFXSTREAM_VULKAN));
96 
97     result = rutabaga_calculate_capset_mask("cross-domain:", &capset_mask);
98     CHECK_RESULT(result);
99     CHECK(capset_mask == (1 << RUTABAGA_CAPSET_CROSS_DOMAIN));
100 
101     result = rutabaga_calculate_capset_mask("cross-domain", &capset_mask);
102     CHECK_RESULT(result);
103     CHECK(capset_mask == (1 << RUTABAGA_CAPSET_CROSS_DOMAIN));
104 
105     result = rutabaga_calculate_capset_mask(":", &capset_mask);
106     CHECK_RESULT(result);
107     CHECK(capset_mask == 0);
108 
109     result = rutabaga_calculate_capset_mask("fake", &capset_mask);
110     CHECK_RESULT(result);
111     CHECK(capset_mask == 0);
112 
113     result = rutabaga_calculate_capset_mask("", &capset_mask);
114     CHECK_RESULT(result);
115     CHECK(capset_mask == 0);
116 
117     result = rutabaga_calculate_capset_mask(NULL, &capset_mask);
118     CHECK(result != 0);
119 
120     return 0;
121 }
122 
test_rutabaga_init(struct rutabaga_test * test,uint64_t capset_mask)123 static int test_rutabaga_init(struct rutabaga_test *test, uint64_t capset_mask)
124 {
125     int result;
126     struct rutabaga_builder builder = { 0 };
127     struct rutabaga_channels channels = { 0 };
128 
129     builder.fence_cb = rutabaga_test_write_fence;
130     builder.debug_cb = rutabaga_test_debug_cb;
131     builder.capset_mask = capset_mask;
132     builder.wsi = RUTABAGA_WSI_SURFACELESS;
133     if (capset_mask & (1 << RUTABAGA_CAPSET_CROSS_DOMAIN)) {
134         builder.user_data = (uint64_t)(uintptr_t *)(void *)test;
135         channels.channels = (struct rutabaga_channel *)calloc(1, sizeof(struct rutabaga_channel));
136         channels.num_channels = 1;
137 
138         channels.channels[0].channel_name = s_wayland_path;
139         channels.channels[0].channel_type = RUTABAGA_CHANNEL_TYPE_WAYLAND;
140 
141         builder.channels = &channels;
142     }
143 
144     result = rutabaga_init(&builder, &test->rutabaga);
145 
146     if (capset_mask & (1 << RUTABAGA_CAPSET_CROSS_DOMAIN))
147         free(channels.channels);
148 
149     CHECK_RESULT(result);
150     return 0;
151 }
152 
test_create_context(struct rutabaga_test * test,const char * context_name)153 static int test_create_context(struct rutabaga_test *test, const char *context_name)
154 {
155     int result;
156     uint32_t num_capsets;
157     uint32_t capset_id, capset_version, capset_size;
158     bool found_cross_domain = false;
159     struct CrossDomainCapabilities *capset;
160 
161     result = rutabaga_get_num_capsets(test->rutabaga, &num_capsets);
162     CHECK_RESULT(result);
163     CHECK(num_capsets == 1);
164 
165     for (uint32_t i = 0; i < num_capsets; i++) {
166         result =
167             rutabaga_get_capset_info(test->rutabaga, i, &capset_id, &capset_version, &capset_size);
168         CHECK_RESULT(result);
169         if (capset_id == RUTABAGA_CAPSET_CROSS_DOMAIN) {
170             found_cross_domain = true;
171             CHECK(capset_size == (uint32_t)sizeof(struct CrossDomainCapabilities));
172         }
173     }
174 
175     CHECK(found_cross_domain);
176     CHECK_RESULT(result);
177 
178     capset = (struct CrossDomainCapabilities *)calloc(1, capset_size);
179     result = rutabaga_get_capset(test->rutabaga, RUTABAGA_CAPSET_CROSS_DOMAIN, 0, (uint8_t *)capset,
180                                  capset_size);
181     CHECK_RESULT(result);
182 
183     CHECK(capset->version == 1);
184     free(capset);
185 
186     size_t context_name_len = 0;
187     if (context_name)
188         context_name_len = strlen(context_name);
189 
190     result = rutabaga_context_create(test->rutabaga, test->ctx_id, RUTABAGA_CAPSET_CROSS_DOMAIN,
191                                      context_name, context_name_len);
192     CHECK_RESULT(result);
193 
194     return 0;
195 }
196 
test_init_context(struct rutabaga_test * test)197 static int test_init_context(struct rutabaga_test *test)
198 {
199     int result;
200     struct rutabaga_create_blob rc_blob = { 0 };
201     struct rutabaga_iovecs vecs = { 0 };
202     struct rutabaga_command cmd = { 0 };
203     struct CrossDomainInit cmd_init = { { 0 } };
204 
205     struct iovec *query_iovecs = (struct iovec *)calloc(1, sizeof(struct iovec));
206     query_iovecs[0].iov_base = calloc(1, DEFAULT_BUFFER_SIZE);
207     query_iovecs[0].iov_len = DEFAULT_BUFFER_SIZE;
208 
209     test->query_iovecs = query_iovecs;
210     rc_blob.blob_mem = RUTABAGA_BLOB_MEM_GUEST;
211     rc_blob.blob_flags = RUTABAGA_BLOB_FLAG_USE_MAPPABLE;
212     rc_blob.size = DEFAULT_BUFFER_SIZE;
213 
214     vecs.iovecs = query_iovecs;
215     vecs.num_iovecs = 1;
216 
217     result = rutabaga_resource_create_blob(test->rutabaga, 0, test->query_ring_id, &rc_blob, &vecs,
218                                            NULL);
219     CHECK_RESULT(result);
220 
221     result = rutabaga_context_attach_resource(test->rutabaga, test->ctx_id, test->query_ring_id);
222     CHECK_RESULT(result);
223 
224     struct iovec *channel_iovecs = (struct iovec *)calloc(1, sizeof(struct iovec));
225     channel_iovecs[0].iov_base = calloc(1, DEFAULT_BUFFER_SIZE);
226     channel_iovecs[0].iov_len = DEFAULT_BUFFER_SIZE;
227 
228     test->channel_iovecs = channel_iovecs;
229     rc_blob.blob_mem = RUTABAGA_BLOB_MEM_GUEST;
230     rc_blob.blob_flags = RUTABAGA_BLOB_FLAG_USE_MAPPABLE;
231     rc_blob.size = DEFAULT_BUFFER_SIZE;
232 
233     vecs.iovecs = channel_iovecs;
234     vecs.num_iovecs = 1;
235 
236     result = rutabaga_resource_create_blob(test->rutabaga, 0, test->channel_ring_id, &rc_blob,
237                                            &vecs, NULL);
238     CHECK_RESULT(result);
239 
240     result = rutabaga_context_attach_resource(test->rutabaga, test->ctx_id, test->channel_ring_id);
241     CHECK_RESULT(result);
242 
243     cmd_init.hdr.cmd = CROSS_DOMAIN_CMD_INIT;
244     cmd_init.hdr.cmd_size = sizeof(struct CrossDomainInit);
245     cmd_init.query_ring_id = test->query_ring_id;
246     cmd_init.channel_ring_id = test->channel_ring_id;
247     cmd_init.channel_type = CROSS_DOMAIN_CHANNEL_TYPE_WAYLAND;
248 
249     cmd.ctx_id = test->ctx_id;
250     cmd.cmd = (uint8_t *)&cmd_init;
251     cmd.cmd_size = cmd_init.hdr.cmd_size;
252 
253     result = rutabaga_submit_command(test->rutabaga, &cmd);
254     CHECK_RESULT(result);
255     return 0;
256 }
257 
test_command_submission(struct rutabaga_test * test)258 static int test_command_submission(struct rutabaga_test *test)
259 {
260     int result;
261     struct CrossDomainGetImageRequirements cmd_get_reqs = { 0 };
262     struct CrossDomainImageRequirements *image_reqs = (void *)test->query_iovecs[0].iov_base;
263     struct rutabaga_create_blob rc_blob = { 0 };
264     struct rutabaga_fence fence;
265     struct rutabaga_handle handle = { 0 };
266     struct rutabaga_command cmd = { 0 };
267     uint32_t map_info;
268 
269     fence.flags = RUTABAGA_FLAG_FENCE | RUTABAGA_FLAG_INFO_RING_IDX;
270     fence.ctx_id = test->ctx_id;
271     fence.ring_idx = 0;
272 
273     cmd_get_reqs.hdr.cmd = CROSS_DOMAIN_CMD_GET_IMAGE_REQUIREMENTS;
274     cmd_get_reqs.hdr.cmd_size = sizeof(struct CrossDomainGetImageRequirements);
275 
276     for (uint32_t i = 0; i < NUM_ITERATIONS; i++) {
277         for (uint32_t j = 0; j < NUM_ITERATIONS; j++) {
278             fence.fence_id = s_fence_id;
279             map_info = 0;
280 
281             cmd_get_reqs.width = WIDTH * i;
282             cmd_get_reqs.height = HEIGHT * j;
283             cmd_get_reqs.drm_format = DRM_FORMAT_XRGB8888;
284 
285             cmd_get_reqs.flags = GBM_BO_USE_LINEAR | GBM_BO_USE_SCANOUT;
286 
287             cmd.ctx_id = test->ctx_id;
288             cmd.cmd = (uint8_t *)&cmd_get_reqs;
289             cmd.cmd_size = cmd_get_reqs.hdr.cmd_size;
290 
291             result = rutabaga_submit_command(test->rutabaga, &cmd);
292 
293             CHECK(test->value < fence.fence_id);
294             result = rutabaga_create_fence(test->rutabaga, &fence);
295 
296             CHECK_RESULT(result);
297             for (;;) {
298                 if (fence.fence_id == test->value)
299                     break;
300             }
301 
302             CHECK(image_reqs->strides[0] >= cmd_get_reqs.width * 4);
303             CHECK(image_reqs->size >= (cmd_get_reqs.width * 4) * cmd_get_reqs.height);
304 
305             rc_blob.blob_mem = RUTABAGA_BLOB_MEM_HOST3D;
306             rc_blob.blob_flags = RUTABAGA_BLOB_FLAG_USE_MAPPABLE | RUTABAGA_BLOB_FLAG_USE_SHAREABLE;
307             rc_blob.blob_id = image_reqs->blob_id;
308             rc_blob.size = image_reqs->size;
309 
310             result = rutabaga_resource_create_blob(test->rutabaga, test->ctx_id, s_resource_id,
311                                                    &rc_blob, NULL, NULL);
312             CHECK_RESULT(result);
313 
314             result = rutabaga_context_attach_resource(test->rutabaga, test->ctx_id, s_resource_id);
315             CHECK_RESULT(result);
316 
317             result = rutabaga_resource_map_info(test->rutabaga, s_resource_id, &map_info);
318             CHECK_RESULT(result);
319             CHECK(map_info > 0);
320 
321             result = rutabaga_resource_export_blob(test->rutabaga, s_resource_id, &handle);
322             CHECK_RESULT(result);
323             CHECK(handle.os_handle >= 0);
324 
325             result = close(handle.os_handle);
326             CHECK_RESULT(result);
327 
328             result = rutabaga_context_detach_resource(test->rutabaga, test->ctx_id, s_resource_id);
329             CHECK_RESULT(result);
330 
331             result = rutabaga_resource_unref(test->rutabaga, s_resource_id);
332             CHECK_RESULT(result);
333 
334             s_resource_id++;
335             s_fence_id++;
336         }
337     }
338 
339     return 0;
340 }
341 
test_context_finish(struct rutabaga_test * test)342 static int test_context_finish(struct rutabaga_test *test)
343 {
344     int result;
345 
346     result = rutabaga_context_detach_resource(test->rutabaga, test->ctx_id, test->query_ring_id);
347     CHECK_RESULT(result);
348 
349     result = rutabaga_resource_unref(test->rutabaga, test->query_ring_id);
350     CHECK_RESULT(result);
351 
352     free(test->query_iovecs[0].iov_base);
353 
354     result = rutabaga_context_detach_resource(test->rutabaga, test->ctx_id, test->channel_ring_id);
355     CHECK_RESULT(result);
356 
357     result = rutabaga_resource_unref(test->rutabaga, test->channel_ring_id);
358     CHECK_RESULT(result);
359 
360     free(test->channel_iovecs[0].iov_base);
361 
362     result = rutabaga_context_destroy(test->rutabaga, test->ctx_id);
363     CHECK_RESULT(result);
364 
365     return 0;
366 }
367 
test_rutabaga_2d(struct rutabaga_test * test)368 static int test_rutabaga_2d(struct rutabaga_test *test)
369 {
370     struct rutabaga_create_3d rc_3d = { 0 };
371     struct rutabaga_transfer transfer = { 0 };
372     int result;
373     uint32_t resource_id = s_resource_id++;
374 
375     struct rutabaga_iovecs vecs = { 0 };
376     struct iovec *iovecs = (struct iovec *)calloc(1, sizeof(struct iovec));
377     uint8_t *test_data;
378     struct iovec result_iovec;
379 
380     iovecs[0].iov_base = calloc(1, DEFAULT_BUFFER_SIZE);
381     iovecs[0].iov_len = DEFAULT_BUFFER_SIZE;
382     result_iovec.iov_base = calloc(1, DEFAULT_BUFFER_SIZE);
383     result_iovec.iov_len = DEFAULT_BUFFER_SIZE;
384     test_data = (uint8_t *)result_iovec.iov_base;
385 
386     vecs.iovecs = iovecs;
387     vecs.num_iovecs = 1;
388 
389     rc_3d.target = PIPE_TEXTURE_2D;
390     rc_3d.bind = PIPE_BIND_RENDER_TARGET;
391     rc_3d.format = VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM;
392     rc_3d.width = DEFAULT_BUFFER_SIZE / 16;
393     rc_3d.height = 4;
394 
395     transfer.w = DEFAULT_BUFFER_SIZE / 16;
396     transfer.h = 4;
397     transfer.d = 1;
398 
399     result = rutabaga_resource_create_3d(test->rutabaga, resource_id, &rc_3d);
400     CHECK_RESULT(result);
401 
402     result = rutabaga_resource_attach_backing(test->rutabaga, resource_id, &vecs);
403     CHECK_RESULT(result);
404 
405     memset(iovecs[0].iov_base, 8, DEFAULT_BUFFER_SIZE);
406 
407     result =
408         rutabaga_resource_transfer_read(test->rutabaga, 0, resource_id, &transfer, &result_iovec);
409     CHECK_RESULT(result);
410 
411     CHECK(test_data[0] == 0);
412 
413     result = rutabaga_resource_transfer_write(test->rutabaga, 0, resource_id, &transfer);
414     CHECK_RESULT(result);
415 
416     result =
417         rutabaga_resource_transfer_read(test->rutabaga, 0, resource_id, &transfer, &result_iovec);
418     CHECK_RESULT(result);
419 
420     CHECK(test_data[0] == 8);
421 
422     result = rutabaga_resource_detach_backing(test->rutabaga, resource_id);
423     CHECK_RESULT(result);
424 
425     result = rutabaga_resource_unref(test->rutabaga, resource_id);
426     CHECK_RESULT(result);
427 
428     free(iovecs[0].iov_base);
429     free(iovecs);
430     free(test_data);
431     return 0;
432 }
433 
434 struct cb_data {
435     uint8_t buf[2000];
436     size_t len;
437 };
438 
snapshot_cb(uint64_t user_data,const uint8_t * data,size_t len)439 static void snapshot_cb(uint64_t user_data, const uint8_t* data, size_t len) {
440     struct cb_data* cb_data = (struct cb_data*)user_data;
441     if (cb_data->len + len > 2000) {
442       // Silently truncate. Checked for by the test.
443       len = 2000 - cb_data->len;
444     }
445     memcpy(cb_data->buf + cb_data->len, data, len);
446     cb_data->len += len;
447 }
448 
test_rutabaga_finish(struct rutabaga_test * test)449 static int test_rutabaga_finish(struct rutabaga_test *test)
450 {
451     int result;
452 
453     result = rutabaga_finish(&test->rutabaga);
454     CHECK_RESULT(result);
455     CHECK(test->rutabaga == NULL);
456     return 0;
457 }
458 
test_rutabaga_2d_snapshot(struct rutabaga_test * test,const char * dir)459 static int test_rutabaga_2d_snapshot(struct rutabaga_test *test, const char* dir)
460 {
461     struct rutabaga_create_3d rc_3d = { 0 };
462     struct rutabaga_transfer transfer = { 0 };
463     int result;
464     uint32_t resource_id = s_resource_id++;
465 
466     result = test_rutabaga_init(test, 0);
467     CHECK_RESULT(result);
468 
469     struct rutabaga_iovecs vecs = { 0 };
470     struct iovec *iovecs = (struct iovec *)calloc(1, sizeof(struct iovec));
471     uint8_t *test_data;
472     struct iovec result_iovec;
473 
474     iovecs[0].iov_base = calloc(1, DEFAULT_BUFFER_SIZE);
475     iovecs[0].iov_len = DEFAULT_BUFFER_SIZE;
476     result_iovec.iov_base = calloc(1, DEFAULT_BUFFER_SIZE);
477     result_iovec.iov_len = DEFAULT_BUFFER_SIZE;
478     test_data = (uint8_t *)result_iovec.iov_base;
479 
480     vecs.iovecs = iovecs;
481     vecs.num_iovecs = 1;
482 
483     rc_3d.target = PIPE_TEXTURE_2D;
484     rc_3d.bind = PIPE_BIND_RENDER_TARGET;
485     rc_3d.format = VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM;
486     rc_3d.width = DEFAULT_BUFFER_SIZE / 16;
487     rc_3d.height = 4;
488 
489     transfer.w = DEFAULT_BUFFER_SIZE / 16;
490     transfer.h = 4;
491     transfer.d = 1;
492 
493     result = rutabaga_resource_create_3d(test->rutabaga, resource_id, &rc_3d);
494     CHECK_RESULT(result);
495 
496     result = rutabaga_resource_attach_backing(test->rutabaga, resource_id, &vecs);
497     CHECK_RESULT(result);
498 
499     struct cb_data cb_data;
500     memset(&cb_data, 0, sizeof(struct cb_data));
501 
502     result = rutabaga_snapshot(test->rutabaga, dir);
503     CHECK_RESULT(result);
504     // If the buffer is filled, assume it overflow. If that happens, just make
505     // the test's buffer bigger (or make the snapshot smaller).
506     CHECK(cb_data.len < 2000);
507 
508     result = rutabaga_resource_unref(test->rutabaga, resource_id);
509     CHECK_RESULT(result);
510 
511     free(iovecs[0].iov_base);
512     free(iovecs);
513     free(test_data);
514 
515     // Teardown and re-init. Restore is only supported from a fresh init.
516     result = test_rutabaga_finish(test);
517     CHECK_RESULT(result);
518 
519     result = test_rutabaga_init(test, 0);
520     CHECK_RESULT(result);
521 
522     result = rutabaga_restore(test->rutabaga, dir);
523     CHECK_RESULT(result);
524 
525     result = test_rutabaga_finish(test);
526     CHECK_RESULT(result);
527 
528     return 0;
529 }
530 
ftw_cb(const char * fpath,const struct stat *,int,struct FTW *)531 int ftw_cb(const char *fpath, const struct stat *, int, struct FTW *) {
532   return remove(fpath);
533 }
534 
recursive_rm(const char * dir)535 int recursive_rm(const char* dir) {
536     CHECK(nftw(dir, ftw_cb, 64, FTW_DEPTH | FTW_PHYS) == 0);
537 }
538 
main(int argc,char * argv[])539 int main(int argc, char *argv[])
540 {
541     struct rutabaga_test test = { 0 };
542     test.ctx_id = 1;
543     test.query_ring_id = s_resource_id++;
544     test.channel_ring_id = s_resource_id++;
545 
546     int result;
547 
548     const char *context_names[] = {
549         NULL,
550         "test_context",
551     };
552     const uint32_t num_context_names = 2;
553 
554     for (uint32_t i = 0; i < num_context_names; i++) {
555         continue;
556         const char *context_name = context_names[i];
557         for (uint32_t j = 0; j < NUM_ITERATIONS; j++) {
558             result = test_capset_mask_calculation();
559             CHECK_RESULT(result);
560 
561             result = test_rutabaga_init(&test, 1 << RUTABAGA_CAPSET_CROSS_DOMAIN);
562             CHECK_RESULT(result);
563 
564             result |= test_create_context(&test, context_name);
565             CHECK_RESULT(result);
566 
567             result |= test_init_context(&test);
568             CHECK_RESULT(result);
569 
570             result |= test_command_submission(&test);
571             CHECK_RESULT(result);
572 
573             result |= test_context_finish(&test);
574             CHECK_RESULT(result);
575 
576             result |= test_rutabaga_finish(&test);
577             CHECK_RESULT(result);
578         }
579     }
580 
581     for (uint32_t i = 0; i < NUM_ITERATIONS; i++) {
582         result = test_rutabaga_init(&test, 0);
583         CHECK_RESULT(result);
584 
585         result |= test_rutabaga_2d(&test);
586         CHECK_RESULT(result);
587 
588         result |= test_rutabaga_finish(&test);
589         CHECK_RESULT(result);
590     }
591 
592     for (uint32_t i = 0; i < NUM_ITERATIONS; i++) {
593         char template[] = "/tmp/rutabaga_test_snapshot.XXXXXX";
594         const char *dir = mkdtemp(template);
595         CHECK(dir);
596         result = test_rutabaga_2d_snapshot(&test, dir);
597         CHECK(recursive_rm(dir) == 0);
598         CHECK_RESULT(result);
599         break;
600     }
601 
602     printf("[  PASSED  ] rutabaga_test success\n");
603     return 0;
604 }
605