cache_test.c revision 7ec681f3
1/*
2 * Copyright © 2015 Intel Corporation
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
22 */
23
24/* A collection of unit tests for cache.c */
25
26#include <stdio.h>
27#include <stdlib.h>
28#include <stdbool.h>
29#include <string.h>
30#include <ftw.h>
31#include <errno.h>
32#include <stdarg.h>
33#include <inttypes.h>
34#include <limits.h>
35#include <time.h>
36#include <unistd.h>
37
38#include "util/mesa-sha1.h"
39#include "util/disk_cache.h"
40
41bool error = false;
42
43#ifdef ENABLE_SHADER_CACHE
44
45static void
46expect_true(bool result, const char *test)
47{
48   if (!result) {
49      fprintf(stderr, "Error: Test '%s' failed: Expected=true"
50              ", Actual=false\n", test);
51      error = true;
52   }
53}
54static void
55expect_false(bool result, const char *test)
56{
57   if (result) {
58      fprintf(stderr, "Error: Test '%s' failed: Expected=false"
59              ", Actual=true\n", test);
60      error = true;
61   }
62}
63
64static void
65expect_equal(uint64_t actual, uint64_t expected, const char *test)
66{
67   if (actual != expected) {
68      fprintf(stderr, "Error: Test '%s' failed: Expected=%" PRIu64
69              ", Actual=%" PRIu64 "\n",
70              test, expected, actual);
71      error = true;
72   }
73}
74
75static void
76expect_null(void *ptr, const char *test)
77{
78   if (ptr != NULL) {
79      fprintf(stderr, "Error: Test '%s' failed: Result=%p, but expected NULL.\n",
80              test, ptr);
81      error = true;
82   }
83}
84
85static void
86expect_non_null(void *ptr, const char *test)
87{
88   if (ptr == NULL) {
89      fprintf(stderr, "Error: Test '%s' failed: Result=NULL, but expected something else.\n",
90              test);
91      error = true;
92   }
93}
94
95static void
96expect_equal_str(const char *actual, const char *expected, const char *test)
97{
98   if (strcmp(actual, expected)) {
99      fprintf(stderr, "Error: Test '%s' failed:\n\t"
100              "Expected=\"%s\", Actual=\"%s\"\n",
101              test, expected, actual);
102      error = true;
103   }
104}
105
106/* Callback for nftw used in rmrf_local below.
107 */
108static int
109remove_entry(const char *path,
110             const struct stat *sb,
111             int typeflag,
112             struct FTW *ftwbuf)
113{
114   int err = remove(path);
115
116   if (err)
117      fprintf(stderr, "Error removing %s: %s\n", path, strerror(errno));
118
119   return err;
120}
121
122/* Recursively remove a directory.
123 *
124 * This is equivalent to "rm -rf <dir>" with one bit of protection
125 * that the directory name must begin with "." to ensure we don't
126 * wander around deleting more than intended.
127 *
128 * Returns 0 on success, -1 on any error.
129 */
130static int
131rmrf_local(const char *path)
132{
133   if (path == NULL || *path == '\0' || *path != '.')
134      return -1;
135
136   return nftw(path, remove_entry, 64, FTW_DEPTH | FTW_PHYS);
137}
138
139static void
140check_directories_created(const char *cache_dir)
141{
142   bool sub_dirs_created = false;
143
144   char buf[PATH_MAX];
145   if (getcwd(buf, PATH_MAX)) {
146      char *full_path = NULL;
147      if (asprintf(&full_path, "%s%s", buf, ++cache_dir) != -1 ) {
148         struct stat sb;
149         if (stat(full_path, &sb) != -1 && S_ISDIR(sb.st_mode))
150            sub_dirs_created = true;
151
152         free(full_path);
153      }
154   }
155
156   expect_true(sub_dirs_created, "create sub dirs");
157}
158
159static bool
160does_cache_contain(struct disk_cache *cache, const cache_key key)
161{
162   void *result;
163
164   result = disk_cache_get(cache, key, NULL);
165
166   if (result) {
167      free(result);
168      return true;
169   }
170
171   return false;
172}
173
174static bool
175cache_exists(struct disk_cache *cache)
176{
177   uint8_t key[20];
178   char data[] = "some test data";
179
180   if (!cache)
181      return NULL;
182
183   disk_cache_compute_key(cache, data, sizeof(data), key);
184   disk_cache_put(cache, key, data, sizeof(data), NULL);
185   disk_cache_wait_for_idle(cache);
186   void *result = disk_cache_get(cache, key, NULL);
187
188   free(result);
189   return result != NULL;
190}
191
192#define CACHE_TEST_TMP "./cache-test-tmp"
193
194static void
195test_disk_cache_create(const char *cache_dir_name)
196{
197   struct disk_cache *cache;
198   int err;
199
200   /* Before doing anything else, ensure that with
201    * MESA_GLSL_CACHE_DISABLE set to true, that disk_cache_create returns NULL.
202    */
203   setenv("MESA_GLSL_CACHE_DISABLE", "true", 1);
204   cache = disk_cache_create("test", "make_check", 0);
205   expect_null(cache, "disk_cache_create with MESA_GLSL_CACHE_DISABLE set");
206
207   unsetenv("MESA_GLSL_CACHE_DISABLE");
208
209#ifdef SHADER_CACHE_DISABLE_BY_DEFAULT
210   /* With SHADER_CACHE_DISABLE_BY_DEFAULT, ensure that with
211    * MESA_GLSL_CACHE_DISABLE set to nothing, disk_cache_create returns NULL.
212    */
213   unsetenv("MESA_GLSL_CACHE_DISABLE");
214   cache = disk_cache_create("test", "make_check", 0);
215   expect_null(cache, "disk_cache_create with MESA_GLSL_CACHE_DISABLE unset "
216               " and SHADER_CACHE_DISABLE_BY_DEFAULT build option");
217
218   /* For remaining tests, ensure that the cache is enabled. */
219   setenv("MESA_GLSL_CACHE_DISABLE", "false", 1);
220#endif /* SHADER_CACHE_DISABLE_BY_DEFAULT */
221
222   /* For the first real disk_cache_create() clear these environment
223    * variables to test creation of cache in home directory.
224    */
225   unsetenv("MESA_GLSL_CACHE_DIR");
226   unsetenv("XDG_CACHE_HOME");
227
228   cache = disk_cache_create("test", "make_check", 0);
229   expect_non_null(cache, "disk_cache_create with no environment variables");
230
231   disk_cache_destroy(cache);
232
233#ifdef ANDROID
234   /* Android doesn't try writing to disk (just calls the cache callbacks), so
235    * the directory tests below don't apply.
236    */
237   exit(error ? 1 : 0);
238#endif
239
240   /* Test with XDG_CACHE_HOME set */
241   setenv("XDG_CACHE_HOME", CACHE_TEST_TMP "/xdg-cache-home", 1);
242   cache = disk_cache_create("test", "make_check", 0);
243   expect_false(cache_exists(cache), "disk_cache_create with XDG_CACHE_HOME set "
244                "with a non-existing parent directory");
245
246   err = mkdir(CACHE_TEST_TMP, 0755);
247   if (err != 0) {
248      fprintf(stderr, "Error creating %s: %s\n", CACHE_TEST_TMP, strerror(errno));
249      error = true;
250      return;
251   }
252   disk_cache_destroy(cache);
253
254   cache = disk_cache_create("test", "make_check", 0);
255   expect_true(cache_exists(cache), "disk_cache_create with XDG_CACHE_HOME "
256               "set");
257
258   char *path;
259   asprintf(&path, "%s%s", CACHE_TEST_TMP "/xdg-cache-home/", cache_dir_name);
260   check_directories_created(path);
261   free(path);
262
263   disk_cache_destroy(cache);
264
265   /* Test with MESA_GLSL_CACHE_DIR set */
266   err = rmrf_local(CACHE_TEST_TMP);
267   expect_equal(err, 0, "Removing " CACHE_TEST_TMP);
268
269   setenv("MESA_GLSL_CACHE_DIR", CACHE_TEST_TMP "/mesa-glsl-cache-dir", 1);
270   cache = disk_cache_create("test", "make_check", 0);
271   expect_false(cache_exists(cache), "disk_cache_create with MESA_GLSL_CACHE_DIR"
272                " set with a non-existing parent directory");
273
274   err = mkdir(CACHE_TEST_TMP, 0755);
275   if (err != 0) {
276      fprintf(stderr, "Error creating %s: %s\n", CACHE_TEST_TMP, strerror(errno));
277      error = true;
278      return;
279   }
280   disk_cache_destroy(cache);
281
282   cache = disk_cache_create("test", "make_check", 0);
283   expect_true(cache_exists(cache), "disk_cache_create with "
284               "MESA_GLSL_CACHE_DIR set");
285
286   asprintf(&path, "%s%s", CACHE_TEST_TMP "/mesa-glsl-cache-dir/",
287            cache_dir_name);
288   check_directories_created(path);
289   free(path);
290
291   disk_cache_destroy(cache);
292}
293
294static void
295test_put_and_get(bool test_cache_size_limit)
296{
297   struct disk_cache *cache;
298   char blob[] = "This is a blob of thirty-seven bytes";
299   uint8_t blob_key[20];
300   char string[] = "While this string has thirty-four";
301   uint8_t string_key[20];
302   char *result;
303   size_t size;
304   uint8_t *one_KB, *one_MB;
305   uint8_t one_KB_key[20], one_MB_key[20];
306   int count;
307
308#ifdef SHADER_CACHE_DISABLE_BY_DEFAULT
309   setenv("MESA_GLSL_CACHE_DISABLE", "false", 1);
310#endif /* SHADER_CACHE_DISABLE_BY_DEFAULT */
311
312   cache = disk_cache_create("test", "make_check", 0);
313
314   disk_cache_compute_key(cache, blob, sizeof(blob), blob_key);
315
316   /* Ensure that disk_cache_get returns nothing before anything is added. */
317   result = disk_cache_get(cache, blob_key, &size);
318   expect_null(result, "disk_cache_get with non-existent item (pointer)");
319   expect_equal(size, 0, "disk_cache_get with non-existent item (size)");
320
321   /* Simple test of put and get. */
322   disk_cache_put(cache, blob_key, blob, sizeof(blob), NULL);
323
324   /* disk_cache_put() hands things off to a thread so wait for it. */
325   disk_cache_wait_for_idle(cache);
326
327   result = disk_cache_get(cache, blob_key, &size);
328   expect_equal_str(blob, result, "disk_cache_get of existing item (pointer)");
329   expect_equal(size, sizeof(blob), "disk_cache_get of existing item (size)");
330
331   free(result);
332
333   /* Test put and get of a second item. */
334   disk_cache_compute_key(cache, string, sizeof(string), string_key);
335   disk_cache_put(cache, string_key, string, sizeof(string), NULL);
336
337   /* disk_cache_put() hands things off to a thread so wait for it. */
338   disk_cache_wait_for_idle(cache);
339
340   result = disk_cache_get(cache, string_key, &size);
341   expect_equal_str(result, string, "2nd disk_cache_get of existing item (pointer)");
342   expect_equal(size, sizeof(string), "2nd disk_cache_get of existing item (size)");
343
344   free(result);
345
346   /* Set the cache size to 1KB and add a 1KB item to force an eviction. */
347   disk_cache_destroy(cache);
348
349   if (!test_cache_size_limit)
350      return;
351
352   setenv("MESA_GLSL_CACHE_MAX_SIZE", "1K", 1);
353   cache = disk_cache_create("test", "make_check", 0);
354
355   one_KB = calloc(1, 1024);
356
357   /* Obviously the SHA-1 hash of 1024 zero bytes isn't particularly
358    * interesting. But we do have want to take some special care with
359    * the hash we use here. The issue is that in this artificial case,
360    * (with only three files in the cache), the probability is good
361    * that each of the three files will end up in their own
362    * directory. Then, if the directory containing the .tmp file for
363    * the new item being added for disk_cache_put() is the chosen victim
364    * directory for eviction, then no suitable file will be found and
365    * nothing will be evicted.
366    *
367    * That's actually expected given how the eviction code is
368    * implemented, (which expects to only evict once things are more
369    * interestingly full than that).
370    *
371    * For this test, we force this signature to land in the same
372    * directory as the original blob first written to the cache.
373    */
374   disk_cache_compute_key(cache, one_KB, 1024, one_KB_key);
375   one_KB_key[0] = blob_key[0];
376
377   disk_cache_put(cache, one_KB_key, one_KB, 1024, NULL);
378
379   free(one_KB);
380
381   /* disk_cache_put() hands things off to a thread so wait for it. */
382   disk_cache_wait_for_idle(cache);
383
384   result = disk_cache_get(cache, one_KB_key, &size);
385   expect_non_null(result, "3rd disk_cache_get of existing item (pointer)");
386   expect_equal(size, 1024, "3rd disk_cache_get of existing item (size)");
387
388   free(result);
389
390   /* Ensure eviction happened by checking that both of the previous
391    * cache itesm were evicted.
392    */
393   bool contains_1KB_file = false;
394   count = 0;
395   if (does_cache_contain(cache, blob_key))
396       count++;
397
398   if (does_cache_contain(cache, string_key))
399       count++;
400
401   if (does_cache_contain(cache, one_KB_key)) {
402      count++;
403      contains_1KB_file = true;
404   }
405
406   expect_true(contains_1KB_file,
407               "disk_cache_put eviction last file == MAX_SIZE (1KB)");
408   expect_equal(count, 1, "disk_cache_put eviction with MAX_SIZE=1K");
409
410   /* Now increase the size to 1M, add back both items, and ensure all
411    * three that have been added are available via disk_cache_get.
412    */
413   disk_cache_destroy(cache);
414
415   setenv("MESA_GLSL_CACHE_MAX_SIZE", "1M", 1);
416   cache = disk_cache_create("test", "make_check", 0);
417
418   disk_cache_put(cache, blob_key, blob, sizeof(blob), NULL);
419   disk_cache_put(cache, string_key, string, sizeof(string), NULL);
420
421   /* disk_cache_put() hands things off to a thread so wait for it. */
422   disk_cache_wait_for_idle(cache);
423
424   count = 0;
425   if (does_cache_contain(cache, blob_key))
426       count++;
427
428   if (does_cache_contain(cache, string_key))
429       count++;
430
431   if (does_cache_contain(cache, one_KB_key))
432       count++;
433
434   expect_equal(count, 3, "no eviction before overflow with MAX_SIZE=1M");
435
436   /* Finally, check eviction again after adding an object of size 1M. */
437   one_MB = calloc(1024, 1024);
438
439   disk_cache_compute_key(cache, one_MB, 1024 * 1024, one_MB_key);
440   one_MB_key[0] = blob_key[0];
441
442   disk_cache_put(cache, one_MB_key, one_MB, 1024 * 1024, NULL);
443
444   free(one_MB);
445
446   /* disk_cache_put() hands things off to a thread so wait for it. */
447   disk_cache_wait_for_idle(cache);
448
449   bool contains_1MB_file = false;
450   count = 0;
451   if (does_cache_contain(cache, blob_key))
452       count++;
453
454   if (does_cache_contain(cache, string_key))
455       count++;
456
457   if (does_cache_contain(cache, one_KB_key))
458       count++;
459
460   if (does_cache_contain(cache, one_MB_key)) {
461      count++;
462      contains_1MB_file = true;
463   }
464
465   expect_true(contains_1MB_file,
466               "disk_cache_put eviction last file == MAX_SIZE (1MB)");
467   expect_equal(count, 1, "eviction after overflow with MAX_SIZE=1M");
468
469   disk_cache_destroy(cache);
470}
471
472static void
473test_put_key_and_get_key(void)
474{
475   struct disk_cache *cache;
476   bool result;
477
478   uint8_t key_a[20] = {  0,  1,  2,  3,  4,  5,  6,  7,  8,  9,
479                         10, 11, 12, 13, 14, 15, 16, 17, 18, 19};
480   uint8_t key_b[20] = { 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
481                         30, 33, 32, 33, 34, 35, 36, 37, 38, 39};
482   uint8_t key_a_collide[20] =
483                        { 0,  1, 42, 43, 44, 45, 46, 47, 48, 49,
484                         50, 55, 52, 53, 54, 55, 56, 57, 58, 59};
485
486#ifdef SHADER_CACHE_DISABLE_BY_DEFAULT
487   setenv("MESA_GLSL_CACHE_DISABLE", "false", 1);
488#endif /* SHADER_CACHE_DISABLE_BY_DEFAULT */
489
490   cache = disk_cache_create("test", "make_check", 0);
491
492   /* First test that disk_cache_has_key returns false before disk_cache_put_key */
493   result = disk_cache_has_key(cache, key_a);
494   expect_equal(result, 0, "disk_cache_has_key before key added");
495
496   /* Then a couple of tests of disk_cache_put_key followed by disk_cache_has_key */
497   disk_cache_put_key(cache, key_a);
498   result = disk_cache_has_key(cache, key_a);
499   expect_equal(result, 1, "disk_cache_has_key after key added");
500
501   disk_cache_put_key(cache, key_b);
502   result = disk_cache_has_key(cache, key_b);
503   expect_equal(result, 1, "2nd disk_cache_has_key after key added");
504
505   /* Test that a key with the same two bytes as an existing key
506    * forces an eviction.
507    */
508   disk_cache_put_key(cache, key_a_collide);
509   result = disk_cache_has_key(cache, key_a_collide);
510   expect_equal(result, 1, "put_key of a colliding key lands in the cache");
511
512   result = disk_cache_has_key(cache, key_a);
513   expect_equal(result, 0, "put_key of a colliding key evicts from the cache");
514
515   /* And finally test that we can re-add the original key to re-evict
516    * the colliding key.
517    */
518   disk_cache_put_key(cache, key_a);
519   result = disk_cache_has_key(cache, key_a);
520   expect_equal(result, 1, "put_key of original key lands again");
521
522   result = disk_cache_has_key(cache, key_a_collide);
523   expect_equal(result, 0, "put_key of orginal key evicts the colliding key");
524
525   disk_cache_destroy(cache);
526}
527
528/* To make sure we are not just using the inmemory cache index for the single
529 * file cache we test adding and retriving cache items between two different
530 * cache instances.
531 */
532static void
533test_put_and_get_between_instances()
534{
535   char blob[] = "This is a blob of thirty-seven bytes";
536   uint8_t blob_key[20];
537   char string[] = "While this string has thirty-four";
538   uint8_t string_key[20];
539   char *result;
540   size_t size;
541
542#ifdef SHADER_CACHE_DISABLE_BY_DEFAULT
543   setenv("MESA_GLSL_CACHE_DISABLE", "false", 1);
544#endif /* SHADER_CACHE_DISABLE_BY_DEFAULT */
545
546   struct disk_cache *cache1 = disk_cache_create("test_between_instances",
547                                                 "make_check", 0);
548   struct disk_cache *cache2 = disk_cache_create("test_between_instances",
549                                                 "make_check", 0);
550
551   disk_cache_compute_key(cache1, blob, sizeof(blob), blob_key);
552
553   /* Ensure that disk_cache_get returns nothing before anything is added. */
554   result = disk_cache_get(cache1, blob_key, &size);
555   expect_null(result, "disk_cache_get(cache1) with non-existent item (pointer)");
556   expect_equal(size, 0, "disk_cache_get(cach1) with non-existent item (size)");
557
558   result = disk_cache_get(cache2, blob_key, &size);
559   expect_null(result, "disk_cache_get(cache2) with non-existent item (pointer)");
560   expect_equal(size, 0, "disk_cache_get(cache2) with non-existent item (size)");
561
562   /* Simple test of put and get. */
563   disk_cache_put(cache1, blob_key, blob, sizeof(blob), NULL);
564
565   /* disk_cache_put() hands things off to a thread so wait for it. */
566   disk_cache_wait_for_idle(cache1);
567
568   result = disk_cache_get(cache2, blob_key, &size);
569   expect_equal_str(blob, result, "disk_cache_get(cache2) of existing item (pointer)");
570   expect_equal(size, sizeof(blob), "disk_cache_get of(cache2) existing item (size)");
571
572   free(result);
573
574   /* Test put and get of a second item, via the opposite instances */
575   disk_cache_compute_key(cache2, string, sizeof(string), string_key);
576   disk_cache_put(cache2, string_key, string, sizeof(string), NULL);
577
578   /* disk_cache_put() hands things off to a thread so wait for it. */
579   disk_cache_wait_for_idle(cache2);
580
581   result = disk_cache_get(cache1, string_key, &size);
582   expect_equal_str(result, string, "2nd disk_cache_get(cache1) of existing item (pointer)");
583   expect_equal(size, sizeof(string), "2nd disk_cache_get(cache1) of existing item (size)");
584
585   free(result);
586
587   disk_cache_destroy(cache1);
588   disk_cache_destroy(cache2);
589}
590#endif /* ENABLE_SHADER_CACHE */
591
592static void
593test_multi_file_cache(void)
594{
595   int err;
596
597   printf("Test multi file disk cache - Start\n");
598
599   test_disk_cache_create(CACHE_DIR_NAME);
600
601   test_put_and_get(true);
602
603   test_put_key_and_get_key();
604
605   printf("Test multi file disk cache - End\n");
606
607   err = rmrf_local(CACHE_TEST_TMP);
608   expect_equal(err, 0, "Removing " CACHE_TEST_TMP " again");
609}
610
611static void
612test_single_file_cache(void)
613{
614   int err;
615
616   printf("Test single file disk cache - Start\n");
617
618   setenv("MESA_DISK_CACHE_SINGLE_FILE", "true", 1);
619
620   test_disk_cache_create(CACHE_DIR_NAME_SF);
621
622   /* We skip testing cache size limit as the single file cache currently
623    * doesn't have any functionality to enforce cache size limits.
624    */
625   test_put_and_get(false);
626
627   test_put_key_and_get_key();
628
629   test_put_and_get_between_instances();
630
631   setenv("MESA_DISK_CACHE_SINGLE_FILE", "false", 1);
632
633   printf("Test single file disk cache - End\n");
634
635   err = rmrf_local(CACHE_TEST_TMP);
636   expect_equal(err, 0, "Removing " CACHE_TEST_TMP " again");
637}
638
639int
640main(void)
641{
642#ifdef ENABLE_SHADER_CACHE
643
644   test_multi_file_cache();
645
646   test_single_file_cache();
647
648#endif /* ENABLE_SHADER_CACHE */
649
650   return error ? 1 : 0;
651}
652