Path: blob/main/sys/contrib/openzfs/tests/unit/test_zap.c
289174 views
// SPDX-License-Identifier: CDDL-1.01/*2* This file and its contents are supplied under the terms of the3* Common Development and Distribution License ("CDDL"), version 1.0.4* You may only use this file in accordance with the terms of version5* 1.0 of the CDDL.6*7* A full copy of the text of the CDDL should have accompanied this8* source. A copy of the CDDL is also available via the Internet at9* http://www.illumos.org/license/CDDL.10*/1112/*13* Copyright (c) 2026, TrueNAS.14*/1516#include <stdbool.h>1718#include <sys/zap.h>19#include <sys/btree.h>20typedef struct spa spa_t; /* forward decl for zap_impl.h */21#include <sys/zap_impl.h>2223#include "mock_dmu.h"24#include "unit.h"2526/* ========== */2728/*29* Normally defined and initialised in arc.c. We define and initialise it30* ourselves here so this mock can be linked without arc.c.31*/32uint64_t zfs_crc64_table[256];3334static void35mock_crc64_init(void)36{37for (int i = 0; i < 256; i++) {38uint64_t ct = i;39for (int j = 8; j > 0; j--)40ct = (ct >> 1) ^ (-(ct & 1) & ZFS_CRC64_POLY);41zfs_crc64_table[i] = ct;42}43}4445/* Misc utility functions. */4647#define rd64(ptr, off) (*(uint64_t *)((const char *)(ptr) + (off)))4849/* ========== */5051/* ZAP-specific mocks and other test helpers. */5253/* Create a microzap backed by a mock dnode. */54static dnode_t *55mock_zap_create_microzap(void) {56/*57* We use DMU_OTN_ZAP_DATA so that DMU_OT_BYTESWAP() returns58* DMU_BSWAP_ZAP without consulting dmu_ot[], which is not currently59* provided in the mock.60*/61mock_dnode_t *mdn = mock_dnode_create(512, DMU_OTN_ZAP_DATA);62dnode_t *dn = (dnode_t *)mdn;63dmu_tx_t *tx = (dmu_tx_t *)mock_tx_create();64mzap_create_impl(dn, 0, 0, tx);65mock_tx_destroy((mock_dmu_tx_t *)tx);66return (dn);67}6869/* Create a fatzap backed by a mock dnode. */70static dnode_t *71mock_zap_create_fatzap(void)72{73/*74* We can only create microzaps directly. They only take u64s as a75* value, so we add a u16 to trigger an upgrade to fatzap.76*/77dnode_t *dn = mock_zap_create_microzap();78dmu_tx_t *tx = (dmu_tx_t *)mock_tx_create();79uint16_t upgrade = 0;80zap_add_by_dnode(dn, "_upgrade", sizeof (uint16_t), 1, &upgrade, tx);81zap_remove_by_dnode(dn, "_upgrade", tx);82mock_tx_destroy((mock_dmu_tx_t *)tx);83return (dn);84}8586static bool87mock_zap_is_microzap(dnode_t *dn)88{89/* check block 0 has a microzap header */90const void *blk = mock_dnode_block_data((mock_dnode_t *)dn, 0);91return (rd64(blk, 0) == ZBT_MICRO);92}9394static bool95mock_zap_is_fatzap(dnode_t *dn)96{97/* check block 0 has a fatzap header */98const void *blk = mock_dnode_block_data((mock_dnode_t *)dn, 0);99return (rd64(blk, 0) == ZBT_HEADER && rd64(blk, 8) == ZAP_MAGIC);100}101102static void103mock_zap_destroy(dnode_t *dn)104{105mock_dnode_t *mdn = (mock_dnode_t *)dn;106unit_eq(mock_dnode_refcount(mdn), 1);107mock_dnode_destroy(mdn);108}109110/* Create a ZAP of the type named in the given test params. */111static dnode_t *112mock_zap_create_params(const MunitParameter params[], const char *key) {113const char *type = munit_parameters_get(params, key);114if (type == NULL)115munit_error("mock_zap_create_params: missing type param");116else if (strcmp(type, "micro") == 0)117return (mock_zap_create_microzap());118else if (strcmp(type, "fat") == 0)119return (mock_zap_create_fatzap());120else121munit_errorf("mock_zap_create_params: invalid type '%s'", type);122__builtin_unreachable();123}124125/*126* Confirm the stored ZAP is of the type named in the given test params. This127* is useful for sanity checks within tests that a ZAP wasn't unexpectedly128* upgraded during the test.129*/130static bool131mock_zap_is_params(dnode_t *dn, const MunitParameter params[],132const char *key)133{134const char *type = munit_parameters_get(params, key);135if (type == NULL)136munit_error("mock_zap_is_params: missing type param");137else if (strcmp(type, "micro") == 0)138return (mock_zap_is_microzap(dn));139else if (strcmp(type, "fat") == 0)140return (mock_zap_is_fatzap(dn));141else142munit_errorf("mock_zap_is_params: invalid type '%s'", type);143__builtin_unreachable();144}145146/* ========== */147148/*149* Sanity checks for mock ZAPs. Ensures that the mock_zap_create_* functions150* really do create the right kind of ZAPs, since many of the tests need to151* run against both kinds to confirm that they all work the same way.152*/153static MunitResult154test_mock_microzap_sanity(const MunitParameter params[], void *data)155{156(void) params, (void) data;157158dnode_t *dn = mock_zap_create_microzap();159unit_true(mock_zap_is_microzap(dn));160mock_zap_destroy(dn);161162return (MUNIT_OK);163}164165static MunitResult166test_mock_fatzap_sanity(const MunitParameter params[], void *data)167{168(void) params, (void) data;169170dnode_t *dn = mock_zap_create_fatzap();171unit_true(mock_zap_is_fatzap(dn));172mock_zap_destroy(dn);173174return (MUNIT_OK);175}176177/* ========== */178179/*180* A simple add, lookup and remove test. Confirms basic operation. These are181* tested together simply because all other tests rely on these primitives.182*/183static MunitResult184test_zap_basic(const MunitParameter params[], void *data)185{186(void) data;187188dnode_t *dn = mock_zap_create_params(params, "type");189dmu_tx_t *tx = (dmu_tx_t *)mock_tx_create();190191/* Insert a few entries. */192uint64_t val42 = 42;193uint64_t val99 = 99;194uint64_t val0 = 0;195196unit_ok(zap_add_by_dnode(dn, "hello",197sizeof (uint64_t), 1, &val42, tx));198unit_ok(zap_add_by_dnode(dn, "world",199sizeof (uint64_t), 1, &val99, tx));200unit_ok(zap_add_by_dnode(dn, "zero",201sizeof (uint64_t), 1, &val0, tx));202203/* Lookup each entry. */204uint64_t result = 0;205unit_ok(zap_lookup_by_dnode(dn, "hello",206sizeof (uint64_t), 1, &result));207unit_eq(result, 42);208209unit_ok(zap_lookup_by_dnode(dn, "world",210sizeof (uint64_t), 1, &result));211unit_eq(result, 99);212213unit_ok(zap_lookup_by_dnode(dn, "zero",214sizeof (uint64_t), 1, &result));215unit_eq(result, 0);216217/* Non-existent key should return ENOENT. */218unit_err(zap_lookup_by_dnode(dn, "nope",219sizeof (uint64_t), 1, &result), ENOENT);220221/* Removing an entry should make it impossible to look up. */222unit_ok(zap_remove_by_dnode(dn, "world", tx));223unit_err(zap_lookup_by_dnode(dn, "world",224sizeof (uint64_t), 1, &result), ENOENT);225226mock_tx_destroy((mock_dmu_tx_t *)tx);227unit_true(mock_zap_is_params(dn, params, "type"));228mock_zap_destroy(dn);229230return (MUNIT_OK);231}232233/* ========== */234235/*236* "Core" ZAP API tests. Covers the most basic functionality upon which which237* everything else is built.238*239* Note that to avoid microzap upgrade here, we only short keys and240* single-uint64 values.241*/242243/* zap_add: add new items. */244static MunitResult245test_zap_add(const MunitParameter params[], void *data)246{247(void) data;248249dnode_t *dn = mock_zap_create_params(params, "type");250dmu_tx_t *tx = (dmu_tx_t *)mock_tx_create();251252/* A key added can be found by that name. */253uint64_t va = 1, var = 0;254unit_ok(zap_add_by_dnode(dn, "a", sizeof (uint64_t), 1, &va, tx));255unit_ok(zap_lookup_by_dnode(dn, "a", sizeof (uint64_t), 1, &var));256unit_eq(var, 1);257258/* Another key added can be found by that name. */259uint64_t vb = 2, vbr = 0;260unit_ok(zap_add_by_dnode(dn, "b", sizeof (uint64_t), 1, &vb, tx));261unit_ok(zap_lookup_by_dnode(dn, "b", sizeof (uint64_t), 1, &vbr));262unit_eq(vbr, 2);263264/* The first key is still findable with the right value. */265var = 0;266unit_ok(zap_lookup_by_dnode(dn, "a", sizeof (uint64_t), 1, &var));267unit_eq(var, 1);268269/* Adding the key again fails. */270unit_err(zap_add_by_dnode(dn, "a",271sizeof (uint64_t), 1, &va, tx), EEXIST);272273/* Adding the key with a different value still fails. */274va = 2;275unit_err(zap_add_by_dnode(dn, "a",276sizeof (uint64_t), 1, &va, tx), EEXIST);277278/* And is still findable with the original value. */279var = 0;280unit_ok(zap_lookup_by_dnode(dn, "a", sizeof (uint64_t), 1, &var));281unit_eq(var, 1);282283mock_tx_destroy((mock_dmu_tx_t *)tx);284unit_true(mock_zap_is_params(dn, params, "type"));285mock_zap_destroy(dn);286287return (MUNIT_OK);288}289290/* zap_update: add new or replace existing items. */291static MunitResult292test_zap_update(const MunitParameter params[], void *data)293{294(void) data;295296dnode_t *dn = mock_zap_create_params(params, "type");297dmu_tx_t *tx = (dmu_tx_t *)mock_tx_create();298299/* Update on a non-existent key inserts it. */300uint64_t va = 1, var = 0;301unit_ok(zap_update_by_dnode(dn, "a", sizeof (uint64_t), 1, &va, tx));302unit_ok(zap_lookup_by_dnode(dn, "a", sizeof (uint64_t), 1, &var));303unit_eq(var, 1);304305/* Update on an existing key replaces it without error. */306va = 2;307unit_ok(zap_update_by_dnode(dn, "a", sizeof (uint64_t), 1, &va, tx));308unit_ok(zap_lookup_by_dnode(dn, "a", sizeof (uint64_t), 1, &var));309unit_eq(var, 2);310311/* Count should still be 1 (no duplicate was created). */312uint64_t count = 0;313unit_ok(zap_count_by_dnode(dn, &count));314unit_eq(count, 1);315316mock_tx_destroy((mock_dmu_tx_t *)tx);317unit_true(mock_zap_is_params(dn, params, "type"));318mock_zap_destroy(dn);319320return (MUNIT_OK);321}322323/* zap_remove: remove existing items. */324static MunitResult325test_zap_remove(const MunitParameter params[], void *data)326{327(void) data;328329dnode_t *dn = mock_zap_create_params(params, "type");330dmu_tx_t *tx = (dmu_tx_t *)mock_tx_create();331332/* Removing a non-existing key fails. */333unit_err(zap_remove_by_dnode(dn, "a", tx), ENOENT);334335/* Adding two keys. */336uint64_t va = 1, vb = 2;337unit_ok(zap_add_by_dnode(dn, "a", sizeof (uint64_t), 1, &va, tx));338unit_ok(zap_add_by_dnode(dn, "b", sizeof (uint64_t), 1, &vb, tx));339340/* Remove an existing key succeeds. */341unit_ok(zap_remove_by_dnode(dn, "a", tx));342343/* After removing, looking up removed key fails. */344uint64_t var = 0;345unit_err(346zap_lookup_by_dnode(dn, "a", sizeof (uint64_t), 1, &var), ENOENT);347348/* Looking up the other key succeeds, and has the correct value. */349uint64_t vbr = 0;350unit_ok(zap_lookup_by_dnode(dn, "b", sizeof (uint64_t), 1, &vbr));351unit_eq(vbr, 2);352353mock_tx_destroy((mock_dmu_tx_t *)tx);354unit_true(mock_zap_is_params(dn, params, "type"));355mock_zap_destroy(dn);356357return (MUNIT_OK);358}359360/* zap_count: number of entries, typically without lookup or traversal. */361static MunitResult362test_zap_count(const MunitParameter params[], void *data)363{364(void) data;365366dnode_t *dn = mock_zap_create_params(params, "type");367dmu_tx_t *tx = (dmu_tx_t *)mock_tx_create();368369/* A new ZAP has zero entries. */370uint64_t count = 0;371unit_ok(zap_count_by_dnode(dn, &count));372unit_eq(count, 0);373374/* Adding two keys bumps the count to 2. */375uint64_t v = 1;376unit_ok(zap_add_by_dnode(dn, "a", sizeof (uint64_t), 1, &v, tx));377unit_ok(zap_add_by_dnode(dn, "b", sizeof (uint64_t), 1, &v, tx));378unit_ok(zap_count_by_dnode(dn, &count));379unit_eq(count, 2);380381/* Removing a key reduces the count. */382unit_ok(zap_remove_by_dnode(dn, "a", tx));383unit_ok(zap_count_by_dnode(dn, &count));384unit_eq(count, 1);385386mock_tx_destroy((mock_dmu_tx_t *)tx);387unit_true(mock_zap_is_params(dn, params, "type"));388mock_zap_destroy(dn);389390return (MUNIT_OK);391}392393/* zap_contains: existence check without reading the value. */394static MunitResult395test_zap_contains(const MunitParameter params[], void *data)396{397(void) data;398399dnode_t *dn = mock_zap_create_params(params, "type");400dmu_tx_t *tx = (dmu_tx_t *)mock_tx_create();401402uint64_t v = 1;403unit_ok(zap_add_by_dnode(dn, "a", sizeof (uint64_t), 1, &v, tx));404unit_ok(zap_contains_by_dnode(dn, "a"));405unit_err(zap_contains_by_dnode(dn, "b"), ENOENT);406407mock_tx_destroy((mock_dmu_tx_t *)tx);408unit_true(mock_zap_is_params(dn, params, "type"));409mock_zap_destroy(dn);410411return (MUNIT_OK);412}413414/* zap_length: item metadata without reading the value. */415static MunitResult416test_zap_length(const MunitParameter params[], void *data)417{418(void) data;419420dnode_t *dn = mock_zap_create_params(params, "type");421dmu_tx_t *tx = (dmu_tx_t *)mock_tx_create();422423/* uint64: integer_size=8, num_integers=1. */424uint64_t v = 42;425unit_ok(zap_add_by_dnode(dn, "u64",426sizeof (uint64_t), 1, &v, tx));427428uint64_t isz = 0, nint = 0;429unit_ok(zap_length_by_dnode(dn, "u64", &isz, &nint));430unit_eq(isz, 8);431unit_eq(nint, 1);432433/* Missing key returns ENOENT. */434unit_err(zap_length_by_dnode(dn, "nope", &isz, &nint), ENOENT);435436/* Either output pointer may be NULL. */437isz = 0; nint = 0;438unit_ok(zap_length_by_dnode(dn, "u64", NULL, &nint));439unit_ok(zap_length_by_dnode(dn, "u64", &isz, NULL));440unit_eq(isz, 8);441unit_eq(nint, 1);442443mock_tx_destroy((mock_dmu_tx_t *)tx);444unit_true(mock_zap_is_params(dn, params, "type"));445mock_zap_destroy(dn);446447return (MUNIT_OK);448}449450/* zap_increment: add integer value to existing integer */451static MunitResult452test_zap_increment(const MunitParameter params[], void *data)453{454(void) data;455456dnode_t *dn = mock_zap_create_params(params, "type");457dmu_tx_t *tx = (dmu_tx_t *)mock_tx_create();458459uint64_t r = 0;460461/* Increment a missing key creates it with that value. */462unit_ok(zap_increment_by_dnode(dn, "a", 5, tx));463unit_ok(zap_lookup_by_dnode(dn, "a", sizeof (uint64_t), 1, &r));464unit_eq(r, 5);465466/* Further increments accumulate. */467unit_ok(zap_increment_by_dnode(dn, "a", 3, tx));468unit_ok(zap_lookup_by_dnode(dn, "a", sizeof (uint64_t), 1, &r));469unit_eq(r, 8);470471/* Decrement works. */472unit_ok(zap_increment_by_dnode(dn, "a", -2, tx));473unit_ok(zap_lookup_by_dnode(dn, "a", sizeof (uint64_t), 1, &r));474unit_eq(r, 6);475476/* Zero delta leaves it unchanged. */477r = 0;478unit_ok(zap_increment_by_dnode(dn, "a", 0, tx));479unit_ok(zap_lookup_by_dnode(dn, "a", sizeof (uint64_t), 1, &r));480unit_eq(r, 6);481482/* Decrementing to zero removes the entry. */483unit_ok(zap_increment_by_dnode(dn, "a", -6, tx));484unit_err(zap_lookup_by_dnode(dn, "a",485sizeof (uint64_t), 1, &r), ENOENT);486487/* Delta of zero is a no-op even for a missing key. */488unit_ok(zap_increment_by_dnode(dn, "a", 0, tx));489unit_err(zap_lookup_by_dnode(dn, "a",490sizeof (uint64_t), 1, &r), ENOENT);491492mock_tx_destroy((mock_dmu_tx_t *)tx);493unit_true(mock_zap_is_params(dn, params, "type"));494mock_zap_destroy(dn);495496return (MUNIT_OK);497}498499/* ========== */500501/*502* zap_add_int/zap_remove_int/zap_lookup_int: single uint64_t value,503* stringified to form the key.504*/505static MunitResult506test_zap_int(const MunitParameter params[], void *data)507{508(void) data;509510dnode_t *dn = mock_zap_create_params(params, "type");511dmu_tx_t *tx = (dmu_tx_t *)mock_tx_create();512513/* Add some ints. */514unit_ok(zap_add_int_by_dnode(dn, 5, tx));515unit_ok(zap_add_int_by_dnode(dn, 17, tx));516517/* Confirm they're there. */518unit_ok(zap_lookup_int_by_dnode(dn, 17));519unit_ok(zap_lookup_int_by_dnode(dn, 5));520521/* But not something we didn't add. */522unit_err(zap_lookup_int_by_dnode(dn, 23), ENOENT);523524/* Adding something that already exists fails. */525unit_err(zap_add_int_by_dnode(dn, 17, tx), EEXIST);526527/* Removing it works, and then it can't be found. */528unit_ok(zap_remove_int_by_dnode(dn, 17, tx));529unit_err(zap_lookup_int_by_dnode(dn, 17), ENOENT);530531/* Add it can be added back. */532unit_ok(zap_add_int_by_dnode(dn, 17, tx));533unit_ok(zap_lookup_int_by_dnode(dn, 17));534535mock_tx_destroy((mock_dmu_tx_t *)tx);536unit_true(mock_zap_is_params(dn, params, "type"));537mock_zap_destroy(dn);538539return (MUNIT_OK);540}541542/* zap_*_int_key: like zap_*_int, but with separate value. */543static MunitResult544test_zap_int_keys(const MunitParameter params[], void *data)545{546(void) data;547548dnode_t *dn = mock_zap_create_params(params, "type");549dmu_tx_t *tx = (dmu_tx_t *)mock_tx_create();550551/* Add some ints. */552unit_ok(zap_add_int_key_by_dnode(dn, 5, 17, tx));553unit_ok(zap_add_int_key_by_dnode(dn, 23, 35, tx));554555/* Confirm they're there. */556uint64_t r = 0;557unit_ok(zap_lookup_int_key_by_dnode(dn, 5, &r));558unit_eq(r, 17);559unit_ok(zap_lookup_int_key_by_dnode(dn, 23, &r));560unit_eq(r, 35);561562/* But not something we didn't add. */563unit_err(zap_lookup_int_key_by_dnode(dn, 79, &r), ENOENT);564565/* Adding something that already exists fails. */566unit_err(zap_add_int_key_by_dnode(dn, 23, 51, tx), EEXIST);567568/* Updating it works though. */569unit_ok(zap_update_int_key_by_dnode(dn, 23, 51, tx));570571/* Removing it works, and then it can't be found. */572unit_ok(zap_remove_int_by_dnode(dn, 23, tx));573unit_err(zap_lookup_int_key_by_dnode(dn, 23, &r), ENOENT);574575/* Add it can be added back. */576unit_ok(zap_add_int_key_by_dnode(dn, 23, 11, tx));577unit_ok(zap_lookup_int_key_by_dnode(dn, 23, &r));578unit_eq(r, 11);579580mock_tx_destroy((mock_dmu_tx_t *)tx);581unit_true(mock_zap_is_params(dn, params, "type"));582mock_zap_destroy(dn);583584return (MUNIT_OK);585}586587/* ========== */588589/*590* Separate stats tests for each ZAP type, since they are about internals and591* so can and will produce different results.592*/593594static MunitResult595test_microzap_stats(const MunitParameter params[], void *data)596{597(void) params; (void) data;598599dnode_t *dn = mock_zap_create_microzap();600dmu_tx_t *tx = (dmu_tx_t *)mock_tx_create();601602zap_stats_t zs;603uint64_t v = 1;604unit_ok(zap_add_by_dnode(dn, "a", sizeof (uint64_t), 1, &v, tx));605unit_ok(zap_add_by_dnode(dn, "b", sizeof (uint64_t), 1, &v, tx));606unit_ok(zap_get_stats_by_dnode(dn, &zs));607608/* We added two entries. */609unit_eq(zs.zs_num_entries, 2);610611/* MicroZAP is always a single block. */612unit_eq(zs.zs_num_blocks, 1);613614/* Blocksize matches what we passed to mock_dnode_create(). */615unit_eq(zs.zs_blocksize, 512);616617mock_tx_destroy((mock_dmu_tx_t *)tx);618unit_true(mock_zap_is_microzap(dn));619mock_zap_destroy(dn);620621return (MUNIT_OK);622}623624static MunitResult625test_fatzap_stats(const MunitParameter params[], void *data)626{627(void) params; (void) data;628629dnode_t *dn = mock_zap_create_fatzap();630dmu_tx_t *tx = (dmu_tx_t *)mock_tx_create();631632zap_stats_t zs;633uint64_t v = 1;634unit_ok(zap_add_by_dnode(dn, "a", sizeof (uint64_t), 1, &v, tx));635unit_ok(zap_add_by_dnode(dn, "b", sizeof (uint64_t), 1, &v, tx));636unit_ok(zap_get_stats_by_dnode(dn, &zs));637638/* We added two entries. */639unit_eq(zs.zs_num_entries, 2);640641/* One header block, one leaf block. */642unit_eq(zs.zs_num_blocks, 2);643644/* FatZAP block size set by tuneable. */645unit_eq(zs.zs_blocksize, 1 << fzap_default_block_shift);646647mock_tx_destroy((mock_dmu_tx_t *)tx);648unit_true(mock_zap_is_fatzap(dn));649mock_zap_destroy(dn);650651return (MUNIT_OK);652}653654/* ========== */655656/* Cursor tests. */657658/*659* Basic cursor test. Add a bunch of keys+values to a ZAP, read them back660* via cursor, confirm they're all there and nothing else is.661*/662static MunitResult663test_cursor(const MunitParameter params[], void *data)664{665(void) data;666667dnode_t *dn = mock_zap_create_params(params, "type");668dmu_tx_t *tx = (dmu_tx_t *)mock_tx_create();669670/* For each ASCII letter as key, add a unique value to the ZAP. */671for (int i = 0; i < 26; i++) {672char c = (char)i + 'a';673char k[2] = { c, '\0' };674uint64_t v = (uint64_t)c * 11;675unit_ok(zap_add_by_dnode(dn, k, sizeof (uint64_t), 1, &v, tx));676}677678/* Sanity check; confirm they're all there by count. */679uint64_t count = 0;680unit_ok(zap_count_by_dnode(dn, &count));681unit_eq(count, 26);682683zap_cursor_t zc;684zap_attribute_t *za = zap_attribute_alloc();685686unit_ok(zap_cursor_init_by_dnode(&zc, dn));687688/*689* Cursors don't guarantee an order, so we run over them them all,690* confirm the key matches the value, and then set a bit for each691* one we've seen. By the end, we should have seen them all.692*/693uint64_t seen = 0;694for (int i = 0; i < 26; i++) {695unit_ok(zap_cursor_retrieve(&zc, za));696697/* Confirm attribute has the right details for the value. */698unit_eq(za->za_integer_length, sizeof (uint64_t));699unit_eq(za->za_num_integers, 1);700701/*702* And the right key in za_name. Note that we don't check703* za_name_len, which is the length of a buffer that can704* definitely hold the key, not the key length itself.705*/706char c = za->za_name[0];707unit_true(c >= 'a' && c <= 'z');708unit_zero(za->za_name[1]);709710/* Check the value in the attribute. */711uint64_t v = (uint64_t)c * 11;712unit_eq(za->za_first_integer, v);713714/*715* Also do a direct lookup and confirm the value matches716* the value from the attribute.717*/718char k[2] = { c, '\0' };719uint64_t result = 0;720unit_ok(zap_lookup_by_dnode(dn, k,721sizeof (uint64_t), 1, &result));722unit_eq(result, v);723724/* This one is good, set the bit to remember this fact. */725seen |= 1 << (c-'a');726727zap_cursor_advance(&zc);728}729730/* There should be no more keys in the ZAP. */731unit_err(zap_cursor_retrieve(&zc, za), ENOENT);732733/* Bits 0-25 should be set if we've seen them all. */734unit_eq(seen, (1 << 26) - 1);735736zap_attribute_free(za);737zap_cursor_fini(&zc);738739mock_tx_destroy((mock_dmu_tx_t *)tx);740unit_true(mock_zap_is_params(dn, params, "type"));741mock_zap_destroy(dn);742743return (MUNIT_OK);744}745746/*747* Cursor serialize test. Add a bunch of items, use the cursor to read half of748* them back, then serialize the cursor. Reload the cursor from the serialized749* state and confirm that we pick up where we left off. Then do it again to750* ensure it doesn't rely on any internal state.751*/752static MunitResult753test_cursor_serialize(const MunitParameter params[], void *data)754{755(void) data;756757dnode_t *dn = mock_zap_create_params(params, "type");758dmu_tx_t *tx = (dmu_tx_t *)mock_tx_create();759760/* For each ASCII letter as key, add a unique value to the ZAP. */761for (int i = 0; i < 26; i++) {762char c = (char)i + 'a';763char k[2] = { c, '\0' };764uint64_t v = (uint64_t)c * 11;765unit_ok(zap_add_by_dnode(dn, k, sizeof (uint64_t), 1, &v, tx));766}767768/* Sanity check; confirm they're all there by count. */769uint64_t count = 0;770unit_ok(zap_count_by_dnode(dn, &count));771unit_eq(count, 26);772773/*774* Like test_cursor above, we'll walk over the ZAP and set bits775* for each key we see.776*/777zap_cursor_t zc;778zap_attribute_t *za = zap_attribute_alloc();779uint64_t seen = 0;780781unit_ok(zap_cursor_init_by_dnode(&zc, dn));782for (int i = 0; i < 13; i++) {783unit_ok(zap_cursor_retrieve(&zc, za));784785char c = za->za_name[0];786unit_true(c >= 'a' && c <= 'z');787788/* This one is good, set the bit to remember this fact. */789seen |= 1 << (c-'a');790791zap_cursor_advance(&zc);792}793794/* Serialise the and terminate the cursor. */795uint64_t cookie = zap_cursor_serialize(&zc);796zap_cursor_fini(&zc);797798/*799* Record the bits we saw in the first iteration; we'll use this800* when we reload the cursor a second time below.801*/802uint64_t orig_seen = seen;803804/* Reinitialise the cursor from the cookie. */805unit_ok(zap_cursor_init_serialized_by_dnode(&zc, dn, cookie));806807/* Loop over the remaining entries and track them. */808for (int i = 0; i < 13; i++) {809unit_ok(zap_cursor_retrieve(&zc, za));810811char c = za->za_name[0];812unit_true(c >= 'a' && c <= 'z');813814/* This one is good, set the bit to remember this fact. */815seen |= 1 << (c-'a');816817zap_cursor_advance(&zc);818}819820/* There should be no more keys in the ZAP. */821unit_err(zap_cursor_retrieve(&zc, za), ENOENT);822823/* Bits 0-25 should be set if we've seen them all. */824unit_eq(seen, (1 << 26) - 1);825826/* Cursor done. */827zap_cursor_fini(&zc);828829/*830* Restore the seen state to before when we reinitialised the saved831* cursor.832*/833seen = orig_seen;834835/*836* Do it all again a second time. This is making sure that the saved837* cursor is usable even after the its been "used".838*/839unit_ok(zap_cursor_init_serialized_by_dnode(&zc, dn, cookie));840for (int i = 0; i < 13; i++) {841unit_ok(zap_cursor_retrieve(&zc, za));842843char c = za->za_name[0];844unit_true(c >= 'a' && c <= 'z');845846seen |= 1 << (c-'a');847848zap_cursor_advance(&zc);849}850851unit_err(zap_cursor_retrieve(&zc, za), ENOENT);852unit_eq(seen, (1 << 26) - 1);853854zap_attribute_free(za);855zap_cursor_fini(&zc);856857mock_tx_destroy((mock_dmu_tx_t *)tx);858unit_true(mock_zap_is_params(dn, params, "type"));859mock_zap_destroy(dn);860861return (MUNIT_OK);862}863864/*865* The following tests confirm that the cursor is properly cleaning up dnode866* holds taken (or not) across the lifetime of the cursor. The test is not867* about how or when it takes holds, only that the dnode refcount is the868* same before zap_cursor_init() as after zap_cursor_fini().869*/870static MunitResult871test_cursor_release_unused(const MunitParameter params[], void *data)872{873(void) data;874875dnode_t *dn = mock_zap_create_params(params, "type");876877uint64_t refcount = mock_dnode_refcount((mock_dnode_t *)dn);878879zap_cursor_t zc;880unit_ok(zap_cursor_init_by_dnode(&zc, dn));881zap_cursor_fini(&zc);882883unit_eq(refcount, mock_dnode_refcount((mock_dnode_t *)dn));884885unit_true(mock_zap_is_params(dn, params, "type"));886mock_zap_destroy(dn);887888return (MUNIT_OK);889}890891static MunitResult892test_cursor_release_advance(const MunitParameter params[], void *data)893{894(void) data;895896dnode_t *dn = mock_zap_create_params(params, "type");897898uint64_t refcount = mock_dnode_refcount((mock_dnode_t *)dn);899900zap_cursor_t zc;901unit_ok(zap_cursor_init_by_dnode(&zc, dn));902zap_cursor_advance(&zc);903zap_cursor_fini(&zc);904905unit_eq(refcount, mock_dnode_refcount((mock_dnode_t *)dn));906907unit_true(mock_zap_is_params(dn, params, "type"));908mock_zap_destroy(dn);909910return (MUNIT_OK);911}912913static MunitResult914test_cursor_release_empty(const MunitParameter params[], void *data)915{916(void) data;917918dnode_t *dn = mock_zap_create_params(params, "type");919920uint64_t refcount = mock_dnode_refcount((mock_dnode_t *)dn);921922zap_cursor_t zc;923zap_attribute_t *za = zap_attribute_alloc();924925unit_ok(zap_cursor_init_by_dnode(&zc, dn));926unit_err(zap_cursor_retrieve(&zc, za), ENOENT);927928zap_attribute_free(za);929zap_cursor_fini(&zc);930931unit_eq(refcount, mock_dnode_refcount((mock_dnode_t *)dn));932933unit_true(mock_zap_is_params(dn, params, "type"));934mock_zap_destroy(dn);935936return (MUNIT_OK);937}938939static MunitResult940test_cursor_release_one(const MunitParameter params[], void *data)941{942(void) data;943944dnode_t *dn = mock_zap_create_params(params, "type");945dmu_tx_t *tx = (dmu_tx_t *)mock_tx_create();946947uint64_t v = 1;948unit_ok(zap_add_by_dnode(dn, "a", sizeof (uint64_t), 1, &v, tx));949unit_ok(zap_add_by_dnode(dn, "b", sizeof (uint64_t), 1, &v, tx));950951uint64_t refcount = mock_dnode_refcount((mock_dnode_t *)dn);952953zap_cursor_t zc;954zap_attribute_t *za = zap_attribute_alloc();955956unit_ok(zap_cursor_init_by_dnode(&zc, dn));957unit_ok(zap_cursor_retrieve(&zc, za));958959zap_attribute_free(za);960zap_cursor_fini(&zc);961962unit_eq(refcount, mock_dnode_refcount((mock_dnode_t *)dn));963964mock_tx_destroy((mock_dmu_tx_t *)tx);965unit_true(mock_zap_is_params(dn, params, "type"));966mock_zap_destroy(dn);967968return (MUNIT_OK);969}970971/* ========== */972973/* zap_value_search: find key with given uint64 value. */974static MunitResult975test_zap_value_search(const MunitParameter params[], void *data)976{977(void) data;978979dnode_t *dn = mock_zap_create_params(params, "type");980dmu_tx_t *tx = (dmu_tx_t *)mock_tx_create();981982/* Add some items. */983uint64_t v1 = 1, v2 = 2, v3 = 3;984unit_ok(zap_add_by_dnode(dn, "one", sizeof (uint64_t), 1, &v1, tx));985unit_ok(zap_add_by_dnode(dn, "two", sizeof (uint64_t), 1, &v2, tx));986unit_ok(zap_add_by_dnode(dn, "three", sizeof (uint64_t), 1, &v3, tx));987988char name[ZAP_MAXNAMELEN];989990/* Find one of them. */991unit_ok(zap_value_search_by_dnode(dn, 2, 0, name, sizeof (name)));992unit_str_eq(name, "two");993994/* Nonexistent value. */995unit_err(zap_value_search_by_dnode(dn, 10, 0,996name, sizeof (name)), ENOENT);997998/* Buffer too small for the key. */999unit_err(zap_value_search_by_dnode(dn, 3, 0, name, 2), ENAMETOOLONG);10001001mock_tx_destroy((mock_dmu_tx_t *)tx);1002unit_true(mock_zap_is_params(dn, params, "type"));1003mock_zap_destroy(dn);10041005return (MUNIT_OK);1006}10071008/* zap_value_search: value masks */1009static MunitResult1010test_zap_value_search_mask(const MunitParameter params[], void *data)1011{1012(void) data;10131014dnode_t *dn = mock_zap_create_params(params, "type");1015dmu_tx_t *tx = (dmu_tx_t *)mock_tx_create();10161017/*1018* Add a set of values. These all have the same bottom 16 bits, with1019* different upper 48 bits, segmented so we can mask them in different1020* and interesting ways.1021*/1022uint64_t v1 = 0x000000000000f0f0ull;1023uint64_t v2 = 0x00000000fffff0f0ull;1024uint64_t v3 = 0x0000ffff0000f0f0ull;1025uint64_t v4 = 0xffff00000000f0f0ull;10261027/*1028* Generate four random keys. We do this because zap_value_search() is1029* implemented with a simple cursor walk, so will always return the1030* first match in hash order, which with fixed keys will always give1031* exactly the same results. Using random keys ensures the test values1032* are encountered in different orders between test runs, giving us1033* better coverage when there are multiple matches.1034*/10351036char k1[9], k2[9], k3[9], k4[9];1037unit_rand_str(k1, sizeof (k1));1038unit_rand_str(k2, sizeof (k2));1039unit_rand_str(k3, sizeof (k3));1040unit_rand_str(k4, sizeof (k4));10411042unit_ok(zap_add_by_dnode(dn, k1, sizeof (uint64_t), 1, &v1, tx));1043unit_ok(zap_add_by_dnode(dn, k2, sizeof (uint64_t), 1, &v2, tx));1044unit_ok(zap_add_by_dnode(dn, k3, sizeof (uint64_t), 1, &v3, tx));1045unit_ok(zap_add_by_dnode(dn, k4, sizeof (uint64_t), 1, &v4, tx));10461047char name[ZAP_MAXNAMELEN];10481049/* 0 mask is equivalent to all bits set in mask ie exact match. */1050unit_ok(zap_value_search_by_dnode(dn,10510xf0f0, 0, name, sizeof (name)));1052unit_str_eq(name, k1);1053unit_ok(zap_value_search_by_dnode(dn,10540xf0f0, 0xffffffffffffffffull, name, sizeof (name)));1055unit_str_eq(name, k1);10561057/* Low 16 bits could match any. */1058unit_ok(zap_value_search_by_dnode(dn,10590xf0f0, 0xffff, name, sizeof (name)));10601061/* Low 32 bits, 3/1 matches. */1062unit_ok(zap_value_search_by_dnode(dn,10630x0000f0f0, 0xffffffff, name, sizeof (name)));1064unit_true(strcmp(name, k1) == 0 || strcmp(name, k3) == 0 ||1065strcmp(name, k4) == 0);1066unit_ok(zap_value_search_by_dnode(dn,10670xfffff0f0, 0xffffffff, name, sizeof (name)));1068unit_str_eq(name, k2);10691070/* Low 48 bits, 2/1/1 matches */1071unit_ok(zap_value_search_by_dnode(dn,10720x00000000f0f0ull, 0xffffffffffffull, name, sizeof (name)));1073unit_true(strcmp(name, k1) == 0 || strcmp(name, k4) == 0);1074unit_ok(zap_value_search_by_dnode(dn,10750x0000fffff0f0ull, 0xffffffffffffull, name, sizeof (name)));1076unit_str_eq(name, k2);1077unit_ok(zap_value_search_by_dnode(dn,10780xffff0000f0f0ull, 0xffffffffffffull, name, sizeof (name)));1079unit_str_eq(name, k3);10801081/* Value doesn't exist directly, but matches when mask applied. */1082unit_ok(zap_value_search_by_dnode(dn,10830xffffffff, 0xffff0000, name, sizeof (name)));1084unit_str_eq(name, k2);10851086mock_tx_destroy((mock_dmu_tx_t *)tx);1087unit_true(mock_zap_is_params(dn, params, "type"));1088mock_zap_destroy(dn);10891090return (MUNIT_OK);1091}10921093/* ========== */10941095/* Test suite definition and boilerplate. */10961097#define UNIT_PARAM_ZAP_TYPES(p) \1098UNIT_PARAM((p), "micro", "fat")10991100static const MunitParameterEnum zap_type_params[] = {1101UNIT_PARAM_ZAP_TYPES("type"),1102{ 0 },1103};11041105#define UNIT_TEST_ZAP_TYPES(name, func) \1106UNIT_TEST(name, func, zap_type_params)11071108static const MunitTest zap_tests[] = {1109UNIT_TEST("mock_microzap_sanity", test_mock_microzap_sanity),1110UNIT_TEST("mock_fatzap_sanity", test_mock_fatzap_sanity),11111112UNIT_TEST_ZAP_TYPES("zap_basic", test_zap_basic),11131114UNIT_TEST_ZAP_TYPES("zap_add", test_zap_add),1115UNIT_TEST_ZAP_TYPES("zap_update", test_zap_update),1116UNIT_TEST_ZAP_TYPES("zap_remove", test_zap_remove),1117UNIT_TEST_ZAP_TYPES("zap_count", test_zap_count),1118UNIT_TEST_ZAP_TYPES("zap_contains", test_zap_contains),1119UNIT_TEST_ZAP_TYPES("zap_length", test_zap_length),11201121UNIT_TEST_ZAP_TYPES("zap_increment", test_zap_increment),11221123UNIT_TEST_ZAP_TYPES("zap_int", test_zap_int),1124UNIT_TEST_ZAP_TYPES("zap_int_keys", test_zap_int_keys),11251126UNIT_TEST("microzap_stats", test_microzap_stats),1127UNIT_TEST("fatzap_stats", test_fatzap_stats),11281129UNIT_TEST_ZAP_TYPES("cursor", test_cursor),1130UNIT_TEST_ZAP_TYPES("cursor_serialize", test_cursor_serialize),11311132UNIT_TEST_ZAP_TYPES(1133"cursor_release_unused", test_cursor_release_unused),1134UNIT_TEST_ZAP_TYPES(1135"cursor_release_advance", test_cursor_release_advance),1136UNIT_TEST_ZAP_TYPES(1137"cursor_release_empty", test_cursor_release_empty),1138UNIT_TEST_ZAP_TYPES(1139"cursor_release_one", test_cursor_release_one),11401141UNIT_TEST_ZAP_TYPES(1142"zap_value_search", test_zap_value_search),1143UNIT_TEST_ZAP_TYPES(1144"zap_value_search_mask", test_zap_value_search_mask),11451146{ 0 },1147};11481149static const MunitSuite zap_test_suite = {1150"zap.",1151zap_tests,1152NULL,11531,1154MUNIT_SUITE_OPTION_NONE,1155};11561157int1158main(int argc, char **argv)1159{1160mock_crc64_init();11611162zap_init();11631164int rc = munit_suite_main(&zap_test_suite, NULL, argc, argv);11651166zap_fini();11671168return (rc);1169}117011711172