summaryrefslogtreecommitdiffstats
path: root/src/tests/cache.c
blob: 667435d9a100d06b6f691fe423ccdde555accf93 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
#include "tests.h"

#include <libplacebo/cache.h>

// Returns "foo" for even keys, "bar" for odd
static pl_cache_obj lookup_foobar(void *priv, uint64_t key)
{
    return (pl_cache_obj) {
        .key = 0xFFFF, // test key sanity
        .data = (key & 1) ? "bar" : "foo",
        .size = 3,
    };
}

static void update_count(void *priv, pl_cache_obj obj)
{
    int *count = priv;
    *count += obj.size ? 1 : -1;
}

enum {
    KEY1 = 0x9c65575f419288f5,
    KEY2 = 0x92da969be9b88086,
    KEY3 = 0x7fcb62540b00bc8b,
    KEY4 = 0x46c60ec11af9dde3,
    KEY5 = 0xcb6760b98ece2477,
    KEY6 = 0xf37dc72b7f9e5c88,
    KEY7 = 0x30c18c962d82e5f5,
};

int main()
{
    pl_log log = pl_test_logger();
    pl_cache test = pl_cache_create(pl_cache_params(
        .log             = log,
        .max_object_size = 16,
        .max_total_size  = 32,
    ));

    pl_cache_obj obj1 = { .key  = KEY1, .data = "abc",  .size = 3 };
    pl_cache_obj obj2 = { .key  = KEY2, .data = "de",   .size = 2 };
    pl_cache_obj obj3 = { .key  = KEY3, .data = "xyzw", .size = 4 };

    REQUIRE(pl_cache_try_set(test, &obj1));
    REQUIRE(pl_cache_try_set(test, &obj2));
    REQUIRE(pl_cache_try_set(test, &obj3));
    REQUIRE_CMP(pl_cache_size(test), ==, 9, "zu");
    REQUIRE_CMP(pl_cache_objects(test), ==, 3, "d");
    REQUIRE(pl_cache_try_set(test, &obj2)); // delete KEY2
    REQUIRE_CMP(pl_cache_size(test), ==, 7, "zu");
    REQUIRE_CMP(pl_cache_objects(test), ==, 2, "d");

    REQUIRE(pl_cache_get(test, &obj1));
    REQUIRE(!pl_cache_get(test, &obj2));
    REQUIRE(pl_cache_get(test, &obj3));
    REQUIRE_CMP(pl_cache_size(test), ==, 0, "zu");
    REQUIRE_CMP(pl_cache_objects(test), ==, 0, "d");
    REQUIRE_MEMEQ(obj1.data, "abc", 3);
    REQUIRE_MEMEQ(obj3.data, "xyzw", 4);

    // Re-insert removed objects (in reversed order)
    REQUIRE(pl_cache_try_set(test, &obj3));
    REQUIRE(pl_cache_try_set(test, &obj1));
    REQUIRE_CMP(pl_cache_size(test), ==, 7, "zu");
    REQUIRE_CMP(pl_cache_objects(test), ==, 2, "d");

    uint8_t ref[72];
    memset(ref, 0xbe, sizeof(ref));
    uint8_t *refp = ref;

#define PAD_ALIGN(x) PL_ALIGN2(x, sizeof(uint32_t))
#define W(type, ...)                                    \
    do {                                                \
        size_t sz = sizeof((type){__VA_ARGS__});        \
        pl_assert(ref + sizeof(ref) - refp >= sz);      \
        memcpy(refp, &(type){__VA_ARGS__}, sz);         \
        refp += sz;                                     \
        size_t pad_sz = PAD_ALIGN(sz) - sz;             \
        pl_assert(ref + sizeof(ref) - refp >= pad_sz);  \
        memcpy(refp, &(char[PAD_ALIGN(1)]){0}, pad_sz); \
        refp += pad_sz;                                 \
    } while (0)

    W(char[], 'p', 'l', '_', 'c', 'a', 'c', 'h', 'e');  // cache magic
    W(uint32_t, 1);                                     // cache version
    W(uint32_t, 2);                                     // number of objects

    // object 3
    W(uint64_t, KEY3);                // key
    W(uint64_t, 4);                   // size
#ifdef PL_HAVE_XXHASH
    W(uint64_t, 0xd43612ef3fbee8be);  // hash
#else
    W(uint64_t, 0xec18884e5e471117);  // hash
#endif
    W(char[], 'x', 'y', 'z', 'w');    // data

    // object 1
    W(uint64_t, KEY1);                // key
    W(uint64_t, 3);                   // size
#ifdef PL_HAVE_XXHASH
    W(uint64_t, 0x78af5f94892f3950);  // hash
#else
    W(uint64_t, 0x3a204d408a2e2d77);  // hash
#endif
    W(char[], 'a', 'b', 'c');         // data

#undef W
#undef PAD_ALIGN

    uint8_t data[100];
    pl_static_assert(sizeof(data) >= sizeof(ref));
    REQUIRE_CMP(pl_cache_save(test, data, sizeof(data)), ==, sizeof(ref), "zu");
    REQUIRE_MEMEQ(data, ref, sizeof(ref));

    pl_cache test2 = pl_cache_create(pl_cache_params( .log = log ));
    REQUIRE_CMP(pl_cache_load(test2, data, sizeof(data)), ==, 2, "d");
    REQUIRE_CMP(pl_cache_size(test2), ==, 7, "zu");
    REQUIRE_CMP(pl_cache_save(test2, NULL, 0), ==, sizeof(ref), "zu");
    REQUIRE_CMP(pl_cache_save(test2, data, sizeof(data)), ==, sizeof(ref), "zu");
    REQUIRE_MEMEQ(data, ref, sizeof(ref));

    // Test loading invalid data
    REQUIRE_CMP(pl_cache_load(test2, ref, 0),   <, 0, "d"); // empty file
    REQUIRE_CMP(pl_cache_load(test2, ref, 5),   <, 0, "d"); // truncated header
    REQUIRE_CMP(pl_cache_load(test2, ref, 64), ==, 1, "d"); // truncated object data
    data[sizeof(ref) - 2] = 'X'; // corrupt data
    REQUIRE_CMP(pl_cache_load(test2, data, sizeof(ref)), ==, 1, "d"); // bad checksum
    pl_cache_destroy(&test2);

    // Inserting too large object should fail
    uint8_t zero[32] = {0};
    pl_cache_obj obj4 = { .key = KEY4, .data = zero, .size = 32 };
    REQUIRE(!pl_cache_try_set(test, &obj4));
    REQUIRE(!pl_cache_get(test, &obj4));
    REQUIRE_CMP(pl_cache_size(test), ==, 7, "zu");
    REQUIRE_CMP(pl_cache_objects(test), ==, 2, "d");

    // Inserting 16-byte object should succeed, and not purge old entries
    obj4 = (pl_cache_obj) { .key = KEY4, .data = zero, .size = 16 };
    REQUIRE(pl_cache_try_set(test, &obj4));
    REQUIRE_CMP(pl_cache_size(test), ==, 23, "zu");
    REQUIRE_CMP(pl_cache_objects(test), ==, 3, "d");
    REQUIRE(pl_cache_get(test, &obj1));
    REQUIRE(pl_cache_get(test, &obj3));
    REQUIRE(pl_cache_get(test, &obj4));
    pl_cache_set(test, &obj1);
    pl_cache_set(test, &obj3);
    pl_cache_set(test, &obj4);
    REQUIRE_CMP(pl_cache_size(test), ==, 23, "zu");
    REQUIRE_CMP(pl_cache_objects(test), ==, 3, "d");

    // Inserting another 10-byte object should purge entry KEY1
    pl_cache_obj obj5 = { .key = KEY5, .data = zero, .size = 10 };
    REQUIRE(pl_cache_try_set(test, &obj5));
    REQUIRE_CMP(pl_cache_size(test), ==, 30, "zu");
    REQUIRE_CMP(pl_cache_objects(test), ==, 3, "d");
    REQUIRE(!pl_cache_get(test, &obj1));
    REQUIRE(pl_cache_get(test, &obj3));
    REQUIRE(pl_cache_get(test, &obj4));
    REQUIRE(pl_cache_get(test, &obj5));
    pl_cache_set(test, &obj3);
    pl_cache_set(test, &obj4);
    pl_cache_set(test, &obj5);
    REQUIRE_CMP(pl_cache_size(test), ==, 30, "zu");
    REQUIRE_CMP(pl_cache_objects(test), ==, 3, "d");

    // Inserting final 6-byte object should purge entry KEY3
    pl_cache_obj obj6 = { .key = KEY6, .data = zero, .size = 6 };
    REQUIRE(pl_cache_try_set(test, &obj6));
    REQUIRE_CMP(pl_cache_size(test), ==, 32, "zu");
    REQUIRE_CMP(pl_cache_objects(test), ==, 3, "d");
    REQUIRE(!pl_cache_get(test, &obj3));
    REQUIRE(pl_cache_get(test, &obj4));
    REQUIRE(pl_cache_get(test, &obj5));
    REQUIRE(pl_cache_get(test, &obj6));
    REQUIRE_CMP(pl_cache_size(test), ==, 0, "zu");
    REQUIRE_CMP(pl_cache_objects(test), ==, 0, "d");
    pl_cache_obj_free(&obj4);
    pl_cache_obj_free(&obj5);
    pl_cache_obj_free(&obj6);

    // Test callback API
    int num_objects = 0;
    test2 = pl_cache_create(pl_cache_params(
        .get  = lookup_foobar,
        .set  = update_count,
        .priv = &num_objects,
    ));

    REQUIRE(pl_cache_get(test2, &obj1));
    REQUIRE_CMP(obj1.key, ==, KEY1, PRIu64);
    REQUIRE_CMP(obj1.size, ==, 3, "zu");
    REQUIRE_MEMEQ(obj1.data, "bar", 3);
    REQUIRE(pl_cache_get(test2, &obj2));
    REQUIRE_CMP(obj2.key, ==, KEY2, PRIu64);
    REQUIRE_CMP(obj2.size, ==, 3, "zu");
    REQUIRE_MEMEQ(obj2.data, "foo", 3);
    REQUIRE_CMP(pl_cache_objects(test2), ==, 0, "d");
    REQUIRE_CMP(num_objects, ==, 0, "d");
    REQUIRE(pl_cache_try_set(test2, &obj1));
    REQUIRE(pl_cache_try_set(test2, &obj2));
    REQUIRE(pl_cache_try_set(test2, &(pl_cache_obj) { .key = KEY7, .data = "abcde", .size = 5 }));
    REQUIRE_CMP(pl_cache_objects(test2), ==, 3, "d");
    REQUIRE_CMP(num_objects, ==, 3, "d");
    REQUIRE(pl_cache_try_set(test2, &obj1));
    REQUIRE(pl_cache_try_set(test2, &obj2));
    REQUIRE_CMP(pl_cache_objects(test2), ==, 1, "d");
    REQUIRE_CMP(num_objects, ==, 1, "d");
    pl_cache_destroy(&test2);

    pl_cache_destroy(&test);
    pl_log_destroy(&log);
    return 0;
}