# Test this inner rbyd data-structure # test with a number of different erase values defines.ERASE_VALUE = [0xff, 0x00, 0x1b, -1] [cases.test_rbyd_commit] in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; // try an empty commit rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, NULL) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; // commit with one attribute rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; // commit with two attributes rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, NULL))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; ''' [cases.test_rbyd_multi_commit] in = 'lfs.c' if = 'BLOCK_SIZE/PROG_SIZE >= 2' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; // try an empty commit rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, NULL) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; // commit with one attribute rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; // commit with two attributes rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, NULL)) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; ''' [cases.test_rbyd_commit_fetch_commit] in = 'lfs.c' if = 'BLOCK_SIZE/PROG_SIZE >= 2' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; // commit with one attribute rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; // commit with the second attribute lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, NULL)) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; ''' # [cases.test_rbyd_fetchmatch] # [cases.test_rbyd_multi_fetchmatch] # TODO we really need to test dense keys... [cases.test_rbyd_lookup] in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; // try an empty commit rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; // commit with one attribute rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; // commit with two attributes rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, NULL))) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; // commit with two attributes, in the other direction rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, NULL))) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; ''' [cases.test_rbyd_multi_lookup] in = 'lfs.c' if = 'BLOCK_SIZE/PROG_SIZE >= 2' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; // try an empty commit rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; // commit with one attribute rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; // commit with two attributes rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, NULL)) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; // commit with two attributes, in the other direction rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; ''' [cases.test_rbyd_get] in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; uint8_t buffer[4]; // try an empty commit rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, NULL) => 0; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, buffer, 4) => LFS_ERR_NOENT; // commit with one attribute rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, buffer, 4) => LFS_ERR_NOENT; // commit with two attributes rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, NULL))) => 0; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, buffer, 4) => 4; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, buffer, 4) => 4; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, buffer, 4) => 4; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, buffer, 4) => 4; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, buffer, 4) => LFS_ERR_NOENT; // commit with two attributes, in the other direction rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, NULL))) => 0; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, buffer, 4) => 4; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, buffer, 4) => 4; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, buffer, 4) => 4; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, buffer, 4) => 4; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, buffer, 4) => LFS_ERR_NOENT; ''' [cases.test_rbyd_multi_get] in = 'lfs.c' if = 'BLOCK_SIZE/PROG_SIZE >= 2' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; uint8_t buffer[4]; // try an empty commit rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, NULL) => 0; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, buffer, 4) => LFS_ERR_NOENT; // commit with one attribute rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, buffer, 4) => LFS_ERR_NOENT; // commit with two attributes rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, NULL)) => 0; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, buffer, 4) => 4; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, buffer, 4) => 4; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, buffer, 4) => 4; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, buffer, 4) => 4; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, buffer, 4) => LFS_ERR_NOENT; // commit with two attributes, in the other direction rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, buffer, 4) => 4; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, buffer, 4) => 4; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, buffer, 4) => 4; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, buffer, 4) => 4; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, buffer, 4) => LFS_ERR_NOENT; ''' [cases.test_rbyd_bifoliate] in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; // create a split in the leaves // .-'| // 1 1 2 rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, NULL))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); // split the other direction // >b // => .-'| // 2 2 1 rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, NULL))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); ''' [cases.test_rbyd_bflips] in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; // ignore a black edge // .----'| // 1 2 1 2 2 rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, NULL)))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); // flip a black edge // b // .-'| => .-'| // 1 2 1 2 1 rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, NULL)))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); ''' [cases.test_rbyd_trifoliate] in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; // ignore a black edge // | 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(3), -1, "\xcc\xcc\xcc\xcc", 4, NULL)))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(3)); assert(id_ == -1); assert(size_ == 4); // flip a black edge // >r // .-'| // | >b // .-'| .--|-'| // 2 3 2 3 1 rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(3), -1, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, NULL)))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(3)); assert(id_ == -1); assert(size_ == 4); ''' [cases.test_rbyd_rflips] in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; // ignore a red edge and black edge // | 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(3), -1, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(3), -1, "\xcc\xcc\xcc\xcc", 4, NULL))))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(3)); assert(id_ == -1); assert(size_ == 4); // ignore a red edge, flip a black edge // | >b // | .-'| | .-'| // 1 2 3 1 2 3 2 rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(3), -1, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, NULL))))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(3)); assert(id_ == -1); assert(size_ == 4); // flip a red edge and black edge // r // .----'| .-'| // | | >b // | .-'| .--|-'| // 1 2 3 1 2 3 1 rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(3), -1, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, NULL))))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(3)); assert(id_ == -1); assert(size_ == 4); // flip a red edge, ignore a black edge // r // .-'| .-------'| // | >b => | >b // .--|-'| | .-'| // 3 1 2 3 1 2 1 rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(3), -1, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, NULL))))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(3)); assert(id_ == -1); assert(size_ == 4); ''' [cases.test_rbyd_quadrifoliate] in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; // ignore a red edge and black edge // | .----'| // | 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(3), -1, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(4), -1, "\xdd\xdd\xdd\xdd", 4, NULL))))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(3)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); // ignore a red edge, flip a black edge // y // .-------'| .-'| // r | >r // .----'| => | .-'| => .--|-'| // | b | | 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(3), -1, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(4), -1, "\xdd\xdd\xdd\xdd", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, NULL))))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(3)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); // flip a red edge and black edge // >y // .-'| // b // .----'| => .--|-'| // | b // | .-'| .--|--|-'| // 2 3 4 2 3 4 1 rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(3), -1, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(4), -1, "\xdd\xdd\xdd\xdd", 4, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, NULL))))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(3)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); // flip a red edge, ignore a black edge // >y // .-------'| // r // .-'| => | .-'| // | >b | | 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(4), -1, "\xdd\xdd\xdd\xdd", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(3), -1, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, NULL))))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(3)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); ''' [cases.test_rbyd_rotations] in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; // all three the same // | .----'| // | 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(3), -1, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(4), -1, "\xdd\xdd\xdd\xdd", 4, NULL))))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(3)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); // yellow and red alt the same // | .----'| // | b // | .-'| | | .-'| // 1 2 4 1 2 4 3 rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(4), -1, "\xdd\xdd\xdd\xdd", 4, LFSR_ATTR(UATTR(3), -1, "\xcc\xcc\xcc\xcc", 4, NULL))))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(3)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); // yellow and black alt the same // r | | .----'| => | .-'| // | >b | | b // | .-'| | | .-'| | .--|-'| // 1 4 2 1 4 2 3 1 4 2 3 rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(4), -1, "\xdd\xdd\xdd\xdd", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(3), -1, "\xcc\xcc\xcc\xcc", 4, NULL))))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(3)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); // red and black alt the same // >y r | | .----'| => | .-'| // | b // | .-'| | | .-'| .--|--|-'| // 4 1 2 4 1 2 3 4 1 2 3 rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(4), -1, "\xdd\xdd\xdd\xdd", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(3), -1, "\xcc\xcc\xcc\xcc", 4, NULL))))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(3)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); ''' [cases.test_rbyd_ysplits] in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; // split a yellow triple, not taking any alt // | 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(3), -1, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(4), -1, "\xdd\xdd\xdd\xdd", 4, LFSR_ATTR(UATTR(4), -1, "\xdd\xdd\xdd\xdd", 4, NULL)))))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(3)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); // split a yellow triple, taking the black alt // | b // | | .-'| | | .-'| // 1 2 3 4 1 2 3 4 3 rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(3), -1, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(4), -1, "\xdd\xdd\xdd\xdd", 4, LFSR_ATTR(UATTR(3), -1, "\xcc\xcc\xcc\xcc", 4, NULL)))))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(3)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); // split a yellow triple, taking the red alt // b // .-------'| .-'| // | .--------|-'| // | | 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(3), -1, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(4), -1, "\xdd\xdd\xdd\xdd", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, NULL)))))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(3)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); // split a yellow triple, taking the yellow alt // b // .-------'| .-'| // | b // | .----'| => .-----|-'| // | | 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(3), -1, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(4), -1, "\xdd\xdd\xdd\xdd", 4, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, NULL)))))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(3)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); ''' [cases.test_rbyd_quintifoliate] in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; // split a yellow triple, not taking any alt // | .----' | // | 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(3), -1, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(4), -1, "\xdd\xdd\xdd\xdd", 4, LFSR_ATTR(UATTR(5), -1, "\xee\xee\xee\xee", 4, NULL)))))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(3)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(5), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(5)); assert(id_ == -1); assert(size_ == 4); // split a yellow triple, taking the black alt // | .----' | // | r // | .----'| | | .-'| // | | b // | | .-'| | | .--|-'| // 1 2 4 5 1 2 4 5 3 rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(4), -1, "\xdd\xdd\xdd\xdd", 4, LFSR_ATTR(UATTR(5), -1, "\xee\xee\xee\xee", 4, LFSR_ATTR(UATTR(3), -1, "\xcc\xcc\xcc\xcc", 4, NULL)))))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(3)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(5), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(5)); assert(id_ == -1); assert(size_ == 4); // split a yellow triple, taking the red alt // >b // .-'| // b // | .----'| => | .-----|-'| // | | 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(3), -1, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(4), -1, "\xdd\xdd\xdd\xdd", 4, LFSR_ATTR(UATTR(5), -1, "\xee\xee\xee\xee", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, NULL)))))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(3)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(5), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(5)); assert(id_ == -1); assert(size_ == 4); // split a yellow triple, taking the yellow alt // >b // .-'| // r // .-------'| .-----|-'| // | b // | .----'| => .--|-----|-'| // | | 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(3), -1, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(4), -1, "\xdd\xdd\xdd\xdd", 4, LFSR_ATTR(UATTR(5), -1, "\xee\xee\xee\xee", 4, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, NULL)))))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(3)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(5), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(5)); assert(id_ == -1); assert(size_ == 4); ''' [cases.test_rbyd_prunes] in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; // don't prune // | .----' | // | | 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(3), -1, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(4), -1, "\xdd\xdd\xdd\xdd", 4, LFSR_ATTR(UATTR(5), -1, "\xee\xee\xee\xee", 4, LFSR_ATTR(UATTR(5), -1, "\xee\xee\xee\xee", 4, NULL))))))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(3)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(5), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(5)); assert(id_ == -1); assert(size_ == 4); // prune by taking a red alt // b // .-------'| | .-'| // | .-----------|-'| // | | 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(3), -1, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(4), -1, "\xdd\xdd\xdd\xdd", 4, LFSR_ATTR(UATTR(5), -1, "\xee\xee\xee\xee", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, NULL))))))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(3)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(5), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(5)); assert(id_ == -1); assert(size_ == 4); // prune by taking a yellow alt (this needs to prune during the rflip) // b // .-------'| | .-'| // | b // | .----' | => .--------|-'| // | | 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(3), -1, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(4), -1, "\xdd\xdd\xdd\xdd", 4, LFSR_ATTR(UATTR(5), -1, "\xee\xee\xee\xee", 4, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, NULL))))))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(3)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(5), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(5)); assert(id_ == -1); assert(size_ == 4); ''' [cases.test_rbyd_sextifoliate] in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; // don't prune // | | .-------'| // | | 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(3), -1, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(4), -1, "\xdd\xdd\xdd\xdd", 4, LFSR_ATTR(UATTR(5), -1, "\xee\xee\xee\xee", 4, LFSR_ATTR(UATTR(6), -1, "\xff\xff\xff\xff", 4, NULL))))))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(3)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(5), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(5)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(6), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(6)); assert(id_ == -1); assert(size_ == 4); // prune by taking a red alt // b // .-'| .-'| // b // | .----' | => | .--------|-'| // | | 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(3), -1, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(4), -1, "\xdd\xdd\xdd\xdd", 4, LFSR_ATTR(UATTR(5), -1, "\xee\xee\xee\xee", 4, LFSR_ATTR(UATTR(6), -1, "\xff\xff\xff\xff", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, NULL))))))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(3)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(5), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(5)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(6), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(6)); assert(id_ == -1); assert(size_ == 4); // prune by taking a yellow alt (this needs to prune during the rflip) // b // .-'| .-'| // r // .-------'| | .--------|-'| // | b // | .----' | => .--|--------|-'| // | | 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(3), -1, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(4), -1, "\xdd\xdd\xdd\xdd", 4, LFSR_ATTR(UATTR(5), -1, "\xee\xee\xee\xee", 4, LFSR_ATTR(UATTR(6), -1, "\xff\xff\xff\xff", 4, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, NULL))))))) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(3)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(5), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(5)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(6), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(6)); assert(id_ == -1); assert(size_ == 4); ''' [cases.test_rbyd_permutations] defines.N = 'range(1, 8)' in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; // keep track of the worst case log size lfs_size_t worst_size = 0; // test all permutations of a given size uint8_t perm[N]; unsigned stack[N]; for (uint8_t i = 0; i < N; i++) { perm[i] = i; stack[i] = 0; } unsigned i = 1; while (i < N) { // print permutation to help debugging printf("--- permutation: ["); for (unsigned j = 0; j < N; j++) { if (j > 0) { printf(", "); } printf("%d", perm[j]+1); } printf("] ---\n"); // build the attribute list for the current permutation struct lfsr_attr attrs[N]; for (unsigned j = 0; j < N; j++) { attrs[j] = *LFSR_ATTR( UATTR(perm[j]+1), -1, "\xaa\xaa\xaa\xaa", 4, (j+1 < N) ? &attrs[j+1] : NULL); } // test the given permutation rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, attrs) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; for (unsigned j = 0; j < N; j++) { lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(j+1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(j+1)); assert(id_ == -1); assert(size_ == 4); } // keep track of the worst size worst_size = lfs_max(worst_size, rbyd.off); // next permutation using Heap's algorithm if (stack[i] < i) { if (i % 2 == 0) { uint8_t t = perm[0]; perm[0] = perm[i]; perm[i] = t; } else { uint8_t t = perm[stack[i]]; perm[stack[i]] = perm[i]; perm[i] = t; } stack[i] += 1; i = 1; } else { stack[i] = 0; i += 1; } } // test that tree is self-balancing, we should be strictly bounded // by height <= 2*log(n)+1, assume tags are strictly <=12 bytes lfs_size_t n = 1 + N; printf("worst size: %u B (N=%u, estimate=%u)\n", worst_size, n, 12*n*(2*lfs_nlog2(n)+1)+4); printf("avg height: %u B (N=%u, estimate=%u)\n", worst_size / n, n, 12*(2*lfs_nlog2(n)+1)+4); // note this only holds true with byte-level progs if (PROG_SIZE == 1) { assert(worst_size / n <= 12*(2*lfs_nlog2(n)+1)+4); } ''' [cases.test_rbyd_multi_permutations] defines.N = 'range(1, 8)' in = 'lfs.c' if = 'BLOCK_SIZE/PROG_SIZE >= N' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; // keep track of the worst case log size lfs_size_t worst_size = 0; // test all permutations of a given size uint8_t perm[N]; unsigned stack[N]; for (uint8_t i = 0; i < N; i++) { perm[i] = i; stack[i] = 0; } unsigned i = 1; while (i < N) { // print permutation to help debugging printf("--- permutation: ["); for (unsigned j = 0; j < N; j++) { if (j > 0) { printf(", "); } printf("%d", perm[j]+1); } printf("] ---\n"); // test the given permutation with multiple commits rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; for (unsigned j = 0; j < N; j++) { lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(perm[j]+1), -1, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; } lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; for (unsigned j = 0; j < N; j++) { lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(j+1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(j+1)); assert(id_ == -1); assert(size_ == 4); } // keep track of the worst size worst_size = lfs_max(worst_size, rbyd.off); // next permutation using Heap's algorithm if (stack[i] < i) { if (i % 2 == 0) { uint8_t t = perm[0]; perm[0] = perm[i]; perm[i] = t; } else { uint8_t t = perm[stack[i]]; perm[stack[i]] = perm[i]; perm[i] = t; } stack[i] += 1; i = 1; } else { stack[i] = 0; i += 1; } } // test that tree is self-balancing, we should be strictly bounded // by height <= 2*log(n)+1, assume tags are strictly <=12 bytes lfs_size_t n = 1 + N; printf("worst size: %u B (N=%u, estimate=%u)\n", worst_size, n, 12*n*(2*lfs_nlog2(n)+1)+4); printf("avg height: %u B (N=%u, estimate=%u)\n", worst_size / n, n, 12*(2*lfs_nlog2(n)+1)+4); // note this only holds true with byte-level progs if (PROG_SIZE == 1) { assert(worst_size / n <= 12*(2*lfs_nlog2(n)+1)+4); } ''' [cases.test_rbyd_traverse] in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; // traverse requires correct biasing of the weights in the rbyd tree // so that lookups return strictly the tag greater than or equal to // the tag requested rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, NULL))) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(0), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(0), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; // also try the other direction rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, NULL))) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(0), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(0), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; ''' [cases.test_rbyd_multi_traverse] in = 'lfs.c' if = 'BLOCK_SIZE/PROG_SIZE >= 2' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; // traverse requires correct biasing of the weights in the rbyd tree // so that lookups return strictly the tag greater than or equal to // the tag requested rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, NULL)) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(0), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(0), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; // also try the other direction rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(0), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(0), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; ''' [cases.test_rbyd_traverse_permutations] defines.N = 'range(1, 8)' in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; // test all permutations of a given size uint8_t perm[N]; unsigned stack[N]; for (uint8_t i = 0; i < N; i++) { perm[i] = i; stack[i] = 0; } unsigned i = 1; while (i < N) { // print permutation to help debugging printf("--- permutation: ["); for (unsigned j = 0; j < N; j++) { if (j > 0) { printf(", "); } printf("%d", perm[j]+1); } printf("] ---\n"); // build the attribute list for the current permutation struct lfsr_attr attrs[N]; for (unsigned j = 0; j < N; j++) { attrs[j] = *LFSR_ATTR( UATTR(perm[j]+1), -1, "\xaa\xaa\xaa\xaa", 4, (j+1 < N) ? &attrs[j+1] : NULL); } // test the given permutation rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, attrs) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; // try traversing all tags tag_ = 0; id_ = -1; for (unsigned j = 0; j < N; j++) { lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(j+1)); assert(id_ == -1); assert(size_ == 4); } lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; // next permutation using Heap's algorithm if (stack[i] < i) { if (i % 2 == 0) { uint8_t t = perm[0]; perm[0] = perm[i]; perm[i] = t; } else { uint8_t t = perm[stack[i]]; perm[stack[i]] = perm[i]; perm[i] = t; } stack[i] += 1; i = 1; } else { stack[i] = 0; i += 1; } } ''' [cases.test_rbyd_multi_traverse_permutations] defines.N = 'range(1, 8)' in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; // test all permutations of a given size uint8_t perm[N]; unsigned stack[N]; for (uint8_t i = 0; i < N; i++) { perm[i] = i; stack[i] = 0; } unsigned i = 1; while (i < N) { // print permutation to help debugging printf("--- permutation: ["); for (unsigned j = 0; j < N; j++) { if (j > 0) { printf(", "); } printf("%d", perm[j]+1); } printf("] ---\n"); // test the given permutation rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; for (unsigned j = 0; j < N; j++) { lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(perm[j]+1), -1, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; } lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; // try traversing all tags tag_ = 0; id_ = -1; for (unsigned j = 0; j < N; j++) { lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(j+1)); assert(id_ == -1); assert(size_ == 4); } lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; // next permutation using Heap's algorithm if (stack[i] < i) { if (i % 2 == 0) { uint8_t t = perm[0]; perm[0] = perm[i]; perm[i] = t; } else { uint8_t t = perm[stack[i]]; perm[stack[i]] = perm[i]; perm[i] = t; } stack[i] += 1; i = 1; } else { stack[i] = 0; i += 1; } } ''' [cases.test_rbyd_update_permutations] defines.N = 'range(1, 8)' in = 'lfs.c' if = 'BLOCK_SIZE/PROG_SIZE >= N' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; // keep track of the worst case log size lfs_size_t worst_size = 0; // create one consistent block rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; for (unsigned j = 0; j < N; j++) { lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(j+1), -1, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; } // copy block so we can reset after each remove lfsr_rbyd_t backup_rbyd = rbyd; uint8_t backup_block[BLOCK_SIZE]; lfs_bd_read(&lfs, NULL, &lfs.rcache, rbyd.off, rbyd.block, 0, backup_block, rbyd.off) => 0; // test all permutations of a given size uint8_t perm[N]; unsigned stack[N]; for (uint8_t i = 0; i < N; i++) { perm[i] = i; stack[i] = 0; } unsigned i = 1; while (i < N) { // print permutation to help debugging printf("--- permutation: ["); for (unsigned j = 0; j < N; j++) { if (j > 0) { printf(", "); } printf("%d", perm[j]+1); } printf("] ---\n"); // restore backup rbyd = backup_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfs_bd_prog(&lfs, &lfs.pcache, &lfs.rcache, false, rbyd.block, 0, backup_block, rbyd.off) => 0; lfs_bd_flush(&lfs, &lfs.pcache, &lfs.rcache, false) => 0; // update each tag in permutation order for (unsigned j = 0; j < N; j++) { lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(perm[j]+1), -1, "\xaa\xaa\xaa\xaa\xaa\xaa", 6, NULL)) => 0; } // check that all tags have been updated lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; for (unsigned j = 0; j < N; j++) { lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(j+1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(j+1)); assert(id_ == -1); assert(size_ == 6); } // keep track of the worst size worst_size = lfs_max(worst_size, rbyd.off); // next permutation using Heap's algorithm if (stack[i] < i) { if (i % 2 == 0) { uint8_t t = perm[0]; perm[0] = perm[i]; perm[i] = t; } else { uint8_t t = perm[stack[i]]; perm[stack[i]] = perm[i]; perm[i] = t; } stack[i] += 1; i = 1; } else { stack[i] = 0; i += 1; } } // test that tree is self-balancing, we should be strictly bounded // by height <= 2*log(n)+1, assume tags are strictly <=12 bytes lfs_size_t n = 1 + N + N; printf("worst size: %u B (N=%u, estimate=%u)\n", worst_size, n, 12*n*(2*lfs_nlog2(n)+1)+4); printf("avg height: %u B (N=%u, estimate=%u)\n", worst_size / n, n, 12*(2*lfs_nlog2(n)+1)+4); // note this only holds true with byte-level progs if (PROG_SIZE == 1) { assert(worst_size / n <= 12*(2*lfs_nlog2(n)+1)+4); } ''' [cases.test_rbyd_large] in = 'lfs.c' # ORDER: # 0 = in-order # 1 = reverse-order # 2 = random-order defines.ORDER = [0, 1, 2] code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; // create the rbyd tree rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; // keep appending tags until we run out of space // // note, this will likely repeat tags, but that's ok // lfs_size_t count = 0; uint32_t prng = 42; for (lfs_size_t i = 0;; i++) { uint8_t x = (ORDER == 0) ? (uint8_t)i : (ORDER == 1) ? (uint8_t)(((lfs_size_t)-1) - i) : (uint8_t)TEST_PRNG(&prng); int err = lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(x), -1, "\xaa\xaa\xaa\xaa", 4, NULL)); // if we can't fit an fcrc, erased is set to false, but if we can, // lfsr_rbyd_commit may error later with LFS_ERR_RANGE if (!rbyd.erased || err == LFS_ERR_RANGE) { break; } assert(err == 0); count = i; } // check that we can still lookup all the tags prng = 42; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; for (lfs_size_t i = 0; i < count; i++) { uint8_t x = (ORDER == 0) ? (uint8_t)i : (ORDER == 1) ? (uint8_t)(((lfs_size_t)-1) - i) : (uint8_t)TEST_PRNG(&prng); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(x), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(x)); assert(id_ == -1); assert(size_ == 4); } ''' ### Removal testing ### [cases.test_rbyd_remove] in = 'lfs.c' if = 'BLOCK_SIZE/PROG_SIZE >= 2' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; // add and remove one attribute rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RMUATTR(1), -1, NULL, 0, NULL)) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; // commit with two attributes, remove the first one rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, NULL))) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RMUATTR(1), -1, NULL, 0, NULL)) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; // commit with two attributes, remove the second one rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, NULL))) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RMUATTR(2), -1, NULL, 0, NULL)) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; ''' [cases.test_rbyd_remove_permutations] defines.N = 'range(1, 7)' in = 'lfs.c' if = 'BLOCK_SIZE/PROG_SIZE >= N+1' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; // keep track of the worst case log size lfs_size_t worst_size = 0; // test all permutations of a given size uint8_t perm[N]; unsigned stack[N]; for (uint8_t i = 0; i < N; i++) { perm[i] = i; stack[i] = 0; } unsigned i = 1; while (i < N) { // print permutation to help debugging printf("--- permutation: ["); for (unsigned j = 0; j < N; j++) { if (j > 0) { printf(", "); } printf("%d", perm[j]+1); } printf("] ---\n"); // create given permutation with multiple commits rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; for (unsigned j = 0; j < N; j++) { lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(perm[j]+1), -1, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; } // copy block so we can reset after each remove lfsr_rbyd_t backup_rbyd = rbyd; uint8_t backup_block[BLOCK_SIZE]; lfs_bd_read(&lfs, NULL, &lfs.rcache, rbyd.off, rbyd.block, 0, backup_block, rbyd.off) => 0; // try removing each tag for (unsigned j = 0; j < N; j++) { // print what we are removing to help debugging printf("--- remove: %d ---\n", j+1); rbyd = backup_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfs_bd_prog(&lfs, &lfs.pcache, &lfs.rcache, false, rbyd.block, 0, backup_block, rbyd.off) => 0; lfs_bd_flush(&lfs, &lfs.pcache, &lfs.rcache, false) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RMUATTR(j+1), -1, NULL, 0, NULL)) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; for (unsigned k = 0; k < N; k++) { int err = lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(k+1), -1, &tag_, &id_, &off_, &size_); assert(!err || err == LFS_ERR_NOENT); if (k == j) { if (j == N-1) { assert(err == LFS_ERR_NOENT); } else { assert(!err); assert(tag_ == LFSR_TAG_UATTR(j+1+1)); assert(id_ == -1); assert(size_ == 4); } } else { assert(tag_ == LFSR_TAG_UATTR(k+1)); assert(id_ == -1); assert(size_ == 4); } } // try appending the tag back to make sure things still work printf("--- append: %d ---\n", j+1); lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(j+1), -1, "\xaa\xaa\xaa\xaa\xaa\xaa", 6, NULL)) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; for (unsigned k = 0; k < N; k++) { lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(k+1), -1, &tag_, &id_, &off_, &size_) => 0; if (k == j) { assert(tag_ == LFSR_TAG_UATTR(k+1)); assert(id_ == -1); assert(size_ == 6); } else { assert(tag_ == LFSR_TAG_UATTR(k+1)); assert(id_ == -1); assert(size_ == 4); } } // keep track of the worst size worst_size = lfs_max(worst_size, rbyd.off); } // next permutation using Heap's algorithm if (stack[i] < i) { if (i % 2 == 0) { uint8_t t = perm[0]; perm[0] = perm[i]; perm[i] = t; } else { uint8_t t = perm[stack[i]]; perm[stack[i]] = perm[i]; perm[i] = t; } stack[i] += 1; i = 1; } else { stack[i] = 0; i += 1; } } // test that tree is self-balancing, we should be strictly bounded // by height <= 2*log(n)+1, assume tags are strictly <=12 bytes lfs_size_t n = 1 + N + 2; printf("worst size: %u B (N=%u, estimate=%u)\n", worst_size, n, 12*n*(2*lfs_nlog2(n)+1)+4); printf("avg height: %u B (N=%u, estimate=%u)\n", worst_size / n, n, 12*(2*lfs_nlog2(n)+1)+4); // note this only holds true with byte-level progs if (PROG_SIZE == 1) { assert(worst_size / n <= 12*(2*lfs_nlog2(n)+1)+4); } ''' [cases.test_rbyd_remove_traverse_permutations] defines.N = 'range(1, 7)' in = 'lfs.c' if = 'BLOCK_SIZE/PROG_SIZE >= N+1' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; // test all permutations of a given size uint8_t perm[N]; unsigned stack[N]; for (uint8_t i = 0; i < N; i++) { perm[i] = i; stack[i] = 0; } unsigned i = 1; while (i < N) { // print permutation to help debugging printf("--- permutation: ["); for (unsigned j = 0; j < N; j++) { if (j > 0) { printf(", "); } printf("%d", perm[j]+1); } printf("] ---\n"); // create given permutation with multiple commits rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; for (unsigned j = 0; j < N; j++) { lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(perm[j]+1), -1, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; } // copy block so we can reset after each remove lfsr_rbyd_t backup_rbyd = rbyd; uint8_t backup_block[BLOCK_SIZE]; lfs_bd_read(&lfs, NULL, &lfs.rcache, rbyd.off, rbyd.block, 0, backup_block, rbyd.off) => 0; // try removing each tag for (unsigned j = 0; j < N; j++) { // print what we are removing to help debugging printf("--- remove: %d ---\n", j+1); rbyd = backup_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfs_bd_prog(&lfs, &lfs.pcache, &lfs.rcache, false, rbyd.block, 0, backup_block, rbyd.off) => 0; lfs_bd_flush(&lfs, &lfs.pcache, &lfs.rcache, false) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RMUATTR(j+1), -1, NULL, 0, NULL)) => 0; // try traversing over the tags lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; tag_ = 0; id_ = -1; for (unsigned k = 0; k < N-1; k++) { lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; if (k >= j) { assert(tag_ == LFSR_TAG_UATTR(k+1+1)); assert(id_ == -1); assert(size_ == 4); } else { assert(tag_ == LFSR_TAG_UATTR(k+1)); assert(id_ == -1); assert(size_ == 4); } } lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; } // next permutation using Heap's algorithm if (stack[i] < i) { if (i % 2 == 0) { uint8_t t = perm[0]; perm[0] = perm[i]; perm[i] = t; } else { uint8_t t = perm[stack[i]]; perm[stack[i]] = perm[i]; perm[i] = t; } stack[i] += 1; i = 1; } else { stack[i] = 0; i += 1; } } ''' [cases.test_rbyd_remove_missing] in = 'lfs.c' if = 'BLOCK_SIZE/PROG_SIZE >= 4' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; // create a tree two attributes rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(4), -1, "\xdd\xdd\xdd\xdd", 4, NULL))) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(5), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; // try to remove tags that aren't there, this should do nothing lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RMUATTR(1), -1, NULL, 0, NULL)) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(5), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RMUATTR(3), -1, NULL, 0, NULL)) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(5), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RMUATTR(5), -1, NULL, 0, NULL)) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(5), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; // one last fetch to make sure nothing was broken lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(5), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; ''' [cases.test_rbyd_remove_again] in = 'lfs.c' if = 'BLOCK_SIZE/PROG_SIZE >= 8' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; // create a tree rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(3), -1, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(4), -1, "\xdd\xdd\xdd\xdd", 4, LFSR_ATTR(UATTR(5), -1, "\xee\xee\xee\xee", 4, NULL)))))) => 0; // remove several attributes lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RMUATTR(1), -1, NULL, 0, LFSR_ATTR(RMUATTR(3), -1, NULL, 0, LFSR_ATTR(RMUATTR(5), -1, NULL, 0, NULL)))) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(5), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; // try to remove tags that aren't there, this should do nothing lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RMUATTR(1), -1, NULL, 0, NULL)) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(5), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RMUATTR(3), -1, NULL, 0, NULL)) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(5), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RMUATTR(5), -1, NULL, 0, NULL)) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(5), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; // try to remove the tags again, just to make sure (keep in mind // these removes still commit to the rbyd) lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RMUATTR(1), -1, NULL, 0, NULL)) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(5), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RMUATTR(3), -1, NULL, 0, NULL)) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(5), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RMUATTR(5), -1, NULL, 0, NULL)) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(5), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; // one last fetch to make sure nothing was broken lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(2)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(3), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(4), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(4)); assert(id_ == -1); assert(size_ == 4); lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(5), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; ''' [cases.test_rbyd_remove_all] in = 'lfs.c' if = 'BLOCK_SIZE/PROG_SIZE >= 2' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; // commit with one attribute, remove it rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RMUATTR(1), -1, NULL, 0, NULL)) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; // commit with two attributes, remove both rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, NULL))) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RMUATTR(1), -1, NULL, 0, LFSR_ATTR(RMUATTR(2), -1, NULL, 0, NULL))) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; // commit with two attributes, remove both in the other order rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(2), -1, "\xbb\xbb\xbb\xbb", 4, NULL))) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RMUATTR(2), -1, NULL, 0, LFSR_ATTR(RMUATTR(1), -1, NULL, 0, NULL))) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(2), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; ''' [cases.test_rbyd_remove_all_permutations] defines.N = 'range(1, 7)' in = 'lfs.c' if = 'BLOCK_SIZE/PROG_SIZE >= 2*N+1' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; // keep track of the worst case log size lfs_size_t worst_size = 0; // create one consistent block rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; for (unsigned j = 0; j < N; j++) { lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(j+1), -1, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; } // copy block so we can reset after each remove lfsr_rbyd_t backup_rbyd = rbyd; uint8_t backup_block[BLOCK_SIZE]; lfs_bd_read(&lfs, NULL, &lfs.rcache, rbyd.off, rbyd.block, 0, backup_block, rbyd.off) => 0; // test all permutations of a given size uint8_t perm[N]; unsigned stack[N]; for (uint8_t i = 0; i < N; i++) { perm[i] = i; stack[i] = 0; } unsigned i = 1; while (i < N) { // print permutation to help debugging printf("--- permutation: ["); for (unsigned j = 0; j < N; j++) { if (j > 0) { printf(", "); } printf("%d", perm[j]+1); } printf("] ---\n"); // restore backup rbyd = backup_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfs_bd_prog(&lfs, &lfs.pcache, &lfs.rcache, false, rbyd.block, 0, backup_block, rbyd.off) => 0; lfs_bd_flush(&lfs, &lfs.pcache, &lfs.rcache, false) => 0; // remove each tag in permutation order for (unsigned j = 0; j < N; j++) { lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RMUATTR(perm[j]+1), -1, NULL, 0, NULL)) => 0; } // check that all tags are now removed lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; for (unsigned j = 0; j < N; j++) { lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(j+1), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; } // try resuming from all tags being removed lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), -1, "\xaa\xaa\xaa\xaa\xaa\xaa", 6, NULL)) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(1), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == -1); assert(size_ == 6); for (unsigned j = 1; j < N; j++) { lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(j+1), -1, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; } // keep track of the worst size worst_size = lfs_max(worst_size, rbyd.off); // next permutation using Heap's algorithm if (stack[i] < i) { if (i % 2 == 0) { uint8_t t = perm[0]; perm[0] = perm[i]; perm[i] = t; } else { uint8_t t = perm[stack[i]]; perm[stack[i]] = perm[i]; perm[i] = t; } stack[i] += 1; i = 1; } else { stack[i] = 0; i += 1; } } // test that tree is self-balancing, we should be strictly bounded // by height <= 2*log(n)+1, assume tags are strictly <=12 bytes lfs_size_t n = 1 + N + N + 1; printf("worst size: %u B (N=%u, estimate=%u)\n", worst_size, n, 12*n*(2*lfs_nlog2(n)+1)+4); printf("avg height: %u B (N=%u, estimate=%u)\n", worst_size / n, n, 12*(2*lfs_nlog2(n)+1)+4); // note this only holds true with byte-level progs if (PROG_SIZE == 1) { assert(worst_size / n <= 12*(2*lfs_nlog2(n)+1)+4); } ''' # the main purpose of this test is to try to fuzz for failures in the # balancing algorithm [cases.test_rbyd_random_append_removes] defines.N = 'range(1, 33)' defines.ITER = 1000 in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; const char *alpha = "abcdefghijklmnopqrstuvwxyz"; uint8_t buffer[4]; // keep track of the worst case size and seed lfs_size_t worst_size = 0; uint32_t worst_seed = 0; // iterate through seeds so we can reproduce easily for (uint32_t seed = 1; seed < ITER+1; seed++) { printf("--- seed: %d ---\n", seed); printf("perm: ["); uint32_t prng = seed; for (unsigned i = 0; i < N; i++) { // choose an attr uint8_t attr = TEST_PRNG(&prng) % N; // choose append or remove if (TEST_PRNG(&prng) & 1) { printf("a0x%02x=%c", attr, alpha[i % 26]); } else { printf("r0x%02x", attr); } if (i < N-1) { printf(", "); } } printf("]\n"); // set up a simulation to compare against char *sim = malloc(N); memset(sim, 0, N); // set up rbyd block rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; prng = seed; for (unsigned i = 0; i < N; i++) { // choose an attr uint8_t attr = TEST_PRNG(&prng) % N; // choose append or remove if (TEST_PRNG(&prng) & 1) { // update our sim sim[attr] = alpha[i % 26]; // update our rbyd lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(attr), -1, &alpha[i % 26], 1, NULL)) => 0; } else { // update our sim sim[attr] = '\0'; // update our rbyd lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RMUATTR(attr), -1, NULL, 0, NULL)) => 0; } } // compare rbyd vs simulation printf("expd: ["); bool first = true; for (unsigned attr = 0; attr < N; attr++) { if (sim[attr]) { if (!first) { printf(", "); } first = false; printf("0x%02x=%c", attr, sim[attr]); } } printf("]\n"); printf("rbyd: ["); first = true; for (unsigned attr = 0; attr < N; attr++) { lfs_ssize_t size = lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(attr), -1, buffer, 4); if (size >= 0) { if (!first) { printf(", "); } first = false; printf("0x%02x=%.*s", attr, size, buffer); } } printf("]\n"); for (unsigned attr = 0; attr < N; attr++) { lfs_ssize_t size = lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(attr), -1, buffer, 4); if (sim[attr]) { assert(size == 1); assert(memcmp(&sim[attr], buffer, 1) == 0); } else { assert(size == LFS_ERR_NOENT); } } // keep track of the worst permutation if (rbyd.off > worst_size) { worst_size = rbyd.off; worst_seed = seed; } } // print the worst seed + size, and rerun it so it's left on the disk // if used with -ddisk printf("--- worst ---\n"); printf("worst_seed: %d\n", worst_seed); printf("worst_size: %d\n", worst_size); printf("worst_perm: ["); uint32_t prng = worst_seed; for (unsigned i = 0; i < N; i++) { // choose an attr uint8_t attr = TEST_PRNG(&prng) % N; // choose append or remove if (TEST_PRNG(&prng) & 1) { printf("a0x%02x=%c", attr, alpha[i % 26]); } else { printf("r0x%02x", attr); } if (i < N-1) { printf(", "); } } printf("]\n"); // set up rbyd block rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; prng = worst_seed; for (unsigned i = 0; i < N; i++) { // choose an attr uint8_t attr = TEST_PRNG(&prng) % N; // choose append or remove if (TEST_PRNG(&prng) & 1) { // update our rbyd lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(attr), -1, &alpha[i % 26], 1, NULL)) => 0; } else { // update our rbyd lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RMUATTR(attr), -1, NULL, 0, NULL)) => 0; } } // our tree should be strictly <= 2*log(n)+1, assume tags are strictly // <=12 bytes, note this only holds true with byte-level progs if (PROG_SIZE == 1) { assert(worst_size <= N*12*(2*lfs_nlog2(N)+1)+1); } ''' # TODO rm me #[cases.test_rbyd_remove_append_permutations] #defines.N = 'range(1, 6)' #in = 'lfs.c' #if = 'BLOCK_SIZE/PROG_SIZE >= N+2' #code = ''' # lfs_t lfs; # lfs_init(&lfs, cfg) => 0; # # lfsr_rbyd_t init_rbyd = { # .block = 0, # .rev = 1, # .off = 0, # .crc = 0, # .trunk = 0, # .weight = 0, # .erased = true, # }; # lfsr_rbyd_t rbyd; # lfsr_tag_t tag_; # lfsr_sid_t id_; # lfs_off_t off_; # lfs_size_t size_; # # // keep track of the worst case log size # lfs_size_t worst_size = 0; # # // test all permutations of a given size # uint8_t perm[N]; # unsigned stack[N]; # for (uint8_t i = 0; i < N; i++) { # perm[i] = i; # stack[i] = 0; # } # # unsigned i = 1; # while (i < N) { # // print permutation to help debugging # printf("--- permutation: ["); # for (unsigned j = 0; j < N; j++) { # if (j > 0) { # printf(", "); # } # printf("%d", perm[j]+1); # } # printf("] ---\n"); # # // create given permutation with multiple commits # rbyd = init_rbyd; # lfs_bd_erase(&lfs, rbyd.block) => 0; # # for (unsigned j = 0; j < N; j++) { # lfsr_rbyd_commit(&lfs, &rbyd, # LFSR_ATTR(UATTR(perm[j]+1), -1, "\xaa\xaa\xaa\xaa", 4, # NULL)) => 0; # } # # // copy block so we can reset after each remove # lfsr_rbyd_t backup_rbyd = rbyd; # uint8_t backup_block[BLOCK_SIZE]; # lfs_bd_read(&lfs, NULL, &lfs.rcache, rbyd.off, # rbyd.block, 0, backup_block, rbyd.off) => 0; # # // try removing each tag # for (unsigned j = 0; j < N; j++) { # for (unsigned l = 0; l < N; l++) { # // print what we are removing to help debugging # printf("--- remove: %d, append: %d ---\n", j+1, l+1); # # rbyd = backup_rbyd; # lfs_bd_erase(&lfs, rbyd.block) => 0; # lfs_bd_prog(&lfs, &lfs.pcache, &lfs.rcache, false, # rbyd.block, 0, backup_block, rbyd.off) => 0; # lfs_bd_flush(&lfs, &lfs.pcache, &lfs.rcache, false) => 0; # # // remove # lfsr_rbyd_commit(&lfs, &rbyd, # LFSR_ATTR(RMUATTR(j+1), -1, NULL, 0, NULL)) => 0; # # // try appending each tag to make sure the rbyd tree # // is still usable # lfsr_rbyd_commit(&lfs, &rbyd, # LFSR_ATTR(UATTR(l+1), -1, "\xaa\xaa\xaa\xaa\xaa\xaa", 6, # NULL)) => 0; # # lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, # cfg->block_size, NULL) => 0; # for (unsigned k = 0; k < N; k++) { # int err = lfsr_rbyd_lookup(&lfs, &rbyd, # LFSR_TAG_UATTR(k+1), -1, # &tag_, &id_, &off_, &size_); # assert(!err || err == LFS_ERR_NOENT); # if (k == l) { # assert(tag_ == LFSR_TAG_UATTR(l+1)); # assert(id_ == -1); # assert(size_ == 6); # } else if (k == j) { # if (j == N-1) { # assert(err == LFS_ERR_NOENT); # } else { # assert(!err); # assert(tag_ == LFSR_TAG_UATTR(j+1+1)); # assert(id_ == -1); # assert(size_ == 4 || size_ == 6); # } # } else { # assert(!err); # assert(tag_ == LFSR_TAG_UATTR(k+1)); # assert(id_ == -1); # assert(size_ == 4); # } # } # # // keep track of the worst size # worst_size = lfs_max(worst_size, rbyd.off); # } # } # # // next permutation using Heap's algorithm # if (stack[i] < i) { # if (i % 2 == 0) { # uint8_t t = perm[0]; # perm[0] = perm[i]; # perm[i] = t; # } else { # uint8_t t = perm[stack[i]]; # perm[stack[i]] = perm[i]; # perm[i] = t; # } # stack[i] += 1; # i = 1; # } else { # stack[i] = 0; # i += 1; # } # } # # // test that tree is self-balancing, we should be strictly bounded # // by height <= 2*log(n)+1, assume tags are strictly <=12 bytes # lfs_size_t n = 1 + N + 1 + 1; # printf("worst size: %u B (N=%u, estimate=%u)\n", # worst_size, n, 12*n*(2*lfs_nlog2(n)+1)+4); # printf("avg height: %u B (N=%u, estimate=%u)\n", # worst_size / n, n, 12*(2*lfs_nlog2(n)+1)+4); # // note this only holds true with byte-level progs # if (PROG_SIZE == 1) { # assert(worst_size / n <= 12*(2*lfs_nlog2(n)+1)+4); # } #''' ### Insertion testing ### [cases.test_rbyd_create] in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; uint8_t buffer[4]; // try to create one id rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; assert(rbyd.weight == 1); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 1); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); // try to create two ids rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, NULL))) => 0; assert(rbyd.weight == 2); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 2); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); // try to create two in the other direction rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, NULL))) => 0; assert(rbyd.weight == 2); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 2); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); // create a third to the right rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(MKREG, 2, "\xcc\xcc\xcc\xcc", 4, NULL)))) => 0; assert(rbyd.weight == 3); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 3); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); // create a third to the left rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(MKREG, 1, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, NULL)))) => 0; assert(rbyd.weight == 3); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 3); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); // create a third in the middle rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(MKREG, 1, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, NULL)))) => 0; assert(rbyd.weight == 3); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 3); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); ''' [cases.test_rbyd_multi_create] in = 'lfs.c' if = 'BLOCK_SIZE/PROG_SIZE >= 3' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; uint8_t buffer[4]; // try to create one id rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; assert(rbyd.weight == 1); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 1); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); // try to create two ids rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, NULL)) => 0; assert(rbyd.weight == 2); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 2); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); // try to create two in the other direction rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xbb\xbb\xbb\xbb", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; assert(rbyd.weight == 2); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 2); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); // create a third to the right rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 2, "\xcc\xcc\xcc\xcc", 4, NULL)) => 0; assert(rbyd.weight == 3); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 3); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); // create a third to the left rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xbb\xbb\xbb\xbb", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 1, "\xcc\xcc\xcc\xcc", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; assert(rbyd.weight == 3); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 3); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); // create a third in the middle rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 1, "\xcc\xcc\xcc\xcc", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, NULL)) => 0; assert(rbyd.weight == 3); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 3); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); ''' [cases.test_rbyd_create_permutations] defines.N = 'range(1, 8)' in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; const uint8_t names[6][4] = { "\xaa\xaa\xaa\xaa", "\xbb\xbb\xbb\xbb", "\xcc\xcc\xcc\xcc", "\xdd\xdd\xdd\xdd", "\xee\xee\xee\xee", "\xff\xff\xff\xff", }; uint8_t buffer[4]; // keep track of the worst case log size lfs_size_t worst_size = 0; // test all permutations of a given size uint16_t perm[N]; unsigned stack[N]; for (uint16_t i = 0; i < N; i++) { perm[i] = i; stack[i] = 0; } unsigned i = 1; while (i < N) { // print permutation to help debugging printf("--- permutation: ["); for (unsigned j = 0; j < N; j++) { if (j > 0) { printf(", "); } printf("%d", perm[j]+1); } printf("] ---\n"); // build the attribute list for the current permutation struct lfsr_attr attrs[N]; for (unsigned j = 0; j < N; j++) { // adjust id based on future insertions uint16_t id = perm[j]; for (unsigned k = j+1; k < N; k++) { if (perm[j] > perm[k]) { id -= 1; } } attrs[j] = *LFSR_ATTR( MKREG, id, names[perm[j] % 6], 4, (j+1 < N) ? &attrs[j+1] : NULL); } // test the given permutation rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, attrs) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == N); for (unsigned j = 0; j < N; j++) { lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, j, buffer, 4) => 4; assert(memcmp(buffer, names[j % 6], 4) == 0); } // keep track of the worst size worst_size = lfs_max(worst_size, rbyd.off); // next permutation using Heap's algorithm if (stack[i] < i) { if (i % 2 == 0) { uint16_t t = perm[0]; perm[0] = perm[i]; perm[i] = t; } else { uint16_t t = perm[stack[i]]; perm[stack[i]] = perm[i]; perm[i] = t; } stack[i] += 1; i = 1; } else { stack[i] = 0; i += 1; } } // test that tree is self-balancing, we should be strictly bounded // by height <= 2*log(n)+1, assume tags are strictly <=12 bytes lfs_size_t n = 1 + N; printf("worst size: %u B (N=%u, estimate=%u)\n", worst_size, n, 12*n*(2*lfs_nlog2(n)+1)+4); printf("avg height: %u B (N=%u, estimate=%u)\n", worst_size / n, n, 12*(2*lfs_nlog2(n)+1)+4); // note this only holds true with byte-level progs if (PROG_SIZE == 1) { assert(worst_size / n <= 12*(2*lfs_nlog2(n)+1)+4); } ''' [cases.test_rbyd_multi_create_permutations] defines.N = 'range(1, 8)' in = 'lfs.c' if = 'BLOCK_SIZE/PROG_SIZE >= N' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; const uint8_t names[6][4] = { "\xaa\xaa\xaa\xaa", "\xbb\xbb\xbb\xbb", "\xcc\xcc\xcc\xcc", "\xdd\xdd\xdd\xdd", "\xee\xee\xee\xee", "\xff\xff\xff\xff", }; uint8_t buffer[4]; // keep track of the worst case log size lfs_size_t worst_size = 0; // test all permutations of a given size uint16_t perm[N]; unsigned stack[N]; for (uint16_t i = 0; i < N; i++) { perm[i] = i; stack[i] = 0; } unsigned i = 1; while (i < N) { // print permutation to help debugging printf("--- permutation: ["); for (unsigned j = 0; j < N; j++) { if (j > 0) { printf(", "); } printf("%d", perm[j]+1); } printf("] ---\n"); // test the given permutation with multiple commits rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; for (unsigned j = 0; j < N; j++) { // adjust id based on future insertions uint16_t id = perm[j]; for (unsigned k = j+1; k < N; k++) { if (perm[j] > perm[k]) { id -= 1; } } lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, id, names[perm[j] % 6], 4, NULL)) => 0; } lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == N); for (unsigned j = 0; j < N; j++) { lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, j, buffer, 4) => 4; assert(memcmp(buffer, names[j % 6], 4) == 0); } // keep track of the worst size worst_size = lfs_max(worst_size, rbyd.off); // next permutation using Heap's algorithm if (stack[i] < i) { if (i % 2 == 0) { uint16_t t = perm[0]; perm[0] = perm[i]; perm[i] = t; } else { uint16_t t = perm[stack[i]]; perm[stack[i]] = perm[i]; perm[i] = t; } stack[i] += 1; i = 1; } else { stack[i] = 0; i += 1; } } // test that tree is self-balancing, we should be strictly bounded // by height <= 2*log(n)+1, assume tags are strictly <=12 bytes lfs_size_t n = 1 + N; printf("worst size: %u B (N=%u, estimate=%u)\n", worst_size, n, 12*n*(2*lfs_nlog2(n)+1)+4); printf("avg height: %u B (N=%u, estimate=%u)\n", worst_size / n, n, 12*(2*lfs_nlog2(n)+1)+4); // note this only holds true with byte-level progs if (PROG_SIZE == 1) { assert(worst_size / n <= 12*(2*lfs_nlog2(n)+1)+4); } ''' [cases.test_rbyd_create_traverse] in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; uint8_t buffer[4]; // traverse requires correct biasing of the weights in the rbyd tree // so that lookups return strictly the tag greater than or equal to // the tag requested rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, NULL))) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(0), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 0); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 1); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(0), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 0); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 1); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; // also try the other direction rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, NULL))) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(0), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 0); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 1); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(0), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 0); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 1); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; ''' [cases.test_rbyd_multi_create_traverse] in = 'lfs.c' if = 'BLOCK_SIZE/PROG_SIZE >= 2' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; uint8_t buffer[4]; // traverse requires correct biasing of the weights in the rbyd tree // so that lookups return strictly the tag greater than or equal to // the tag requested rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, NULL)) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(0), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 0); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 1); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(0), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 0); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 1); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; // also try the other direction rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xbb\xbb\xbb\xbb", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(0), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 0); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 1); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(0), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 0); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 1); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; ''' [cases.test_rbyd_create_traverse_permutations] defines.N = 'range(1, 8)' in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; const uint8_t names[6][4] = { "\xaa\xaa\xaa\xaa", "\xbb\xbb\xbb\xbb", "\xcc\xcc\xcc\xcc", "\xdd\xdd\xdd\xdd", "\xee\xee\xee\xee", "\xff\xff\xff\xff", }; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; uint8_t buffer[4]; // test all permutations of a given size uint16_t perm[N]; unsigned stack[N]; for (uint16_t i = 0; i < N; i++) { perm[i] = i; stack[i] = 0; } unsigned i = 1; while (i < N) { // print permutation to help debugging printf("--- permutation: ["); for (unsigned j = 0; j < N; j++) { if (j > 0) { printf(", "); } printf("%d", perm[j]+1); } printf("] ---\n"); // build the attribute list for the current permutation struct lfsr_attr attrs[N]; for (unsigned j = 0; j < N; j++) { // adjust id based on future insertions uint16_t id = perm[j]; for (unsigned k = j+1; k < N; k++) { if (perm[j] > perm[k]) { id -= 1; } } attrs[j] = *LFSR_ATTR( MKREG, id, names[perm[j] % 6], 4, (j+1 < N) ? &attrs[j+1] : NULL); } // test the given permutation rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, attrs) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == N); // try traversing all tags tag_ = 0; id_ = -1; for (unsigned j = 0; j < N; j++) { lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == j); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, names[j % 6], 4) == 0); } lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; // next permutation using Heap's algorithm if (stack[i] < i) { if (i % 2 == 0) { uint16_t t = perm[0]; perm[0] = perm[i]; perm[i] = t; } else { uint16_t t = perm[stack[i]]; perm[stack[i]] = perm[i]; perm[i] = t; } stack[i] += 1; i = 1; } else { stack[i] = 0; i += 1; } } ''' [cases.test_rbyd_multi_create_traverse_permutations] defines.N = 'range(1, 8)' in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; const uint8_t names[6][4] = { "\xaa\xaa\xaa\xaa", "\xbb\xbb\xbb\xbb", "\xcc\xcc\xcc\xcc", "\xdd\xdd\xdd\xdd", "\xee\xee\xee\xee", "\xff\xff\xff\xff", }; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; uint8_t buffer[4]; // test all permutations of a given size uint16_t perm[N]; unsigned stack[N]; for (uint16_t i = 0; i < N; i++) { perm[i] = i; stack[i] = 0; } unsigned i = 1; while (i < N) { // print permutation to help debugging printf("--- permutation: ["); for (unsigned j = 0; j < N; j++) { if (j > 0) { printf(", "); } printf("%d", perm[j]+1); } printf("] ---\n"); // test the given permutation with multiple commits rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; for (unsigned j = 0; j < N; j++) { // adjust id based on future insertions uint16_t id = perm[j]; for (unsigned k = j+1; k < N; k++) { if (perm[j] > perm[k]) { id -= 1; } } lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, id, names[perm[j] % 6], 4, NULL)) => 0; } lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == N); // try traversing all tags tag_ = 0; id_ = -1; for (unsigned j = 0; j < N; j++) { lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == j); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, names[j % 6], 4) == 0); } lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; // next permutation using Heap's algorithm if (stack[i] < i) { if (i % 2 == 0) { uint16_t t = perm[0]; perm[0] = perm[i]; perm[i] = t; } else { uint16_t t = perm[stack[i]]; perm[stack[i]] = perm[i]; perm[i] = t; } stack[i] += 1; i = 1; } else { stack[i] = 0; i += 1; } } ''' [cases.test_rbyd_create_large] in = 'lfs.c' # ORDER: # 0 = in-order # 1 = reverse-order # 2 = random-order defines.ORDER = [0, 1, 2] code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; const uint8_t names[6][4] = { "\xaa\xaa\xaa\xaa", "\xbb\xbb\xbb\xbb", "\xcc\xcc\xcc\xcc", "\xdd\xdd\xdd\xdd", "\xee\xee\xee\xee", "\xff\xff\xff\xff", }; // create the rbyd tree rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; // keep inserting tags until we run out of space // // note, the ids we create this way are both sparse and sometimes // repeated, so we need to mod our current rbyd size to avoid invalid // insertions // uint32_t prng = 42; for (lfs_size_t i = 0;; i++) { uint16_t x = (ORDER == 0) ? (uint16_t)i : (ORDER == 1) ? (uint16_t)(((lfs_size_t)-1) - i) : (uint16_t)TEST_PRNG(&prng); x = x % (rbyd.weight+1); int err = lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, x, names[x % 6], 4, NULL)); // if we can't fit an fcrc, erased is set to false, but if we can, // lfsr_rbyd_commit may error later with LFS_ERR_RANGE if (!rbyd.erased || err == LFS_ERR_RANGE) { break; } assert(err == 0); } // check that we can at least lookup all the tags // // note with random order we can't check that stored values reliably lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; for (uint16_t x = 0; x < rbyd.weight; x++) { lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_MKREG, x, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == x); assert(size_ == 4); } ''' ### Mixed create and attr testing ### [cases.test_rbyd_mixed] in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; uint8_t buffer[4]; // try to create one id rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(1), 0, "\xaa\xaa", 2, NULL))) => 0; assert(rbyd.weight == 1); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 1); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); // try to create two ids rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(1), 0, "\xaa\xaa", 2, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(1), 1, "\xbb\xbb", 2, NULL))))) => 0; assert(rbyd.weight == 2); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 1, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 2); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 1, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); // try to create two in the other direction rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(1), 0, "\xbb\xbb", 2, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(1), 0, "\xaa\xaa", 2, NULL))))) => 0; assert(rbyd.weight == 2); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 1, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 2); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 1, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); // create a third to the right rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(1), 0, "\xaa\xaa", 2, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(1), 1, "\xbb\xbb", 2, LFSR_ATTR(MKREG, 2, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(1), 2, "\xcc\xcc", 2, NULL))))))) => 0; assert(rbyd.weight == 3); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 3); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); assert(rbyd.weight == 3); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 1, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 2, buffer, 4) => 2; assert(memcmp(buffer, "\xcc\xcc", 2) == 0); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 3); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 1, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 2, buffer, 4) => 2; assert(memcmp(buffer, "\xcc\xcc", 2) == 0); // create a third to the left rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(1), 0, "\xbb\xbb", 2, LFSR_ATTR(MKREG, 1, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(1), 1, "\xcc\xcc", 2, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(1), 0, "\xaa\xaa", 2, NULL))))))) => 0; assert(rbyd.weight == 3); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 1, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 2, buffer, 4) => 2; assert(memcmp(buffer, "\xcc\xcc", 2) == 0); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 3); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 1, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 2, buffer, 4) => 2; assert(memcmp(buffer, "\xcc\xcc", 2) == 0); // create a third in the middle rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(1), 0, "\xaa\xaa", 2, LFSR_ATTR(MKREG, 1, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(1), 1, "\xcc\xcc", 2, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(1), 1, "\xbb\xbb", 2, NULL))))))) => 0; assert(rbyd.weight == 3); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 1, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 2, buffer, 4) => 2; assert(memcmp(buffer, "\xcc\xcc", 2) == 0); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 3); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 1, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 2, buffer, 4) => 2; assert(memcmp(buffer, "\xcc\xcc", 2) == 0); ''' [cases.test_rbyd_multi_mixed] in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; uint8_t buffer[4]; // try to create one id rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), 0, "\xaa\xaa", 2, NULL)) => 0; assert(rbyd.weight == 1); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 1); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); // try to create two ids rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), 0, "\xaa\xaa", 2, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), 1, "\xbb\xbb", 2, NULL)) => 0; assert(rbyd.weight == 2); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 1, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 2); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 1, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); // try to create two in the other direction rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xbb\xbb\xbb\xbb", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), 0, "\xbb\xbb", 2, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), 0, "\xaa\xaa", 2, NULL)) => 0; assert(rbyd.weight == 2); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 1, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 2); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 1, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); // create a third to the right rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), 0, "\xaa\xaa", 2, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), 1, "\xbb\xbb", 2, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 2, "\xcc\xcc\xcc\xcc", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), 2, "\xcc\xcc", 2, NULL)) => 0; assert(rbyd.weight == 3); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 3); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); assert(rbyd.weight == 3); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 1, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 2, buffer, 4) => 2; assert(memcmp(buffer, "\xcc\xcc", 2) == 0); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 3); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 1, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 2, buffer, 4) => 2; assert(memcmp(buffer, "\xcc\xcc", 2) == 0); // create a third to the left rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xbb\xbb\xbb\xbb", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), 0, "\xbb\xbb", 2, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 1, "\xcc\xcc\xcc\xcc", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), 1, "\xcc\xcc", 2, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), 0, "\xaa\xaa", 2, NULL)) => 0; assert(rbyd.weight == 3); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 1, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 2, buffer, 4) => 2; assert(memcmp(buffer, "\xcc\xcc", 2) == 0); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 3); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 1, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 2, buffer, 4) => 2; assert(memcmp(buffer, "\xcc\xcc", 2) == 0); // create a third in the middle rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), 0, "\xaa\xaa", 2, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 1, "\xcc\xcc\xcc\xcc", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), 1, "\xcc\xcc", 2, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), 1, "\xbb\xbb", 2, NULL)) => 0; assert(rbyd.weight == 3); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 1, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 2, buffer, 4) => 2; assert(memcmp(buffer, "\xcc\xcc", 2) == 0); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 3); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 1, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 2, buffer, 4) => 2; assert(memcmp(buffer, "\xcc\xcc", 2) == 0); ''' [cases.test_rbyd_mixed_permutations] defines.N = 'range(1, 7)' defines.M = 'range(1, 4)' in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; const uint8_t names[6][4] = { "\xaa\xaa\xaa\xaa", "\xbb\xbb\xbb\xbb", "\xcc\xcc\xcc\xcc", "\xdd\xdd\xdd\xdd", "\xee\xee\xee\xee", "\xff\xff\xff\xff", }; uint8_t buffer[4]; // keep track of the worst case log size lfs_size_t worst_size = 0; // test all permutations of a given size uint16_t perm[N]; unsigned stack[N]; for (uint16_t i = 0; i < N; i++) { perm[i] = i; stack[i] = 0; } unsigned i = 1; while (i < N) { // print permutation to help debugging printf("--- permutation: ["); for (unsigned j = 0; j < N; j++) { if (j > 0) { printf(", "); } printf("%d", perm[j]+1); } printf("] ---\n"); // build the attribute list for the current permutation struct lfsr_attr attrs[N+N*M]; for (unsigned j = 0; j < N; j++) { // adjust id based on future insertions uint16_t id = perm[j]; for (unsigned k = j+1; k < N; k++) { if (perm[j] > perm[k]) { id -= 1; } } attrs[j*(M+1)] = *LFSR_ATTR( MKREG, id, names[perm[j] % 6], 4, (j*(M+1)+1 < N+N*M) ? &attrs[j*(M+1)+1] : NULL); // note uattrs have a smaller size to help debugging for (unsigned u = 0; u < M; u++) { attrs[j*(M+1)+u+1] = *LFSR_ATTR( UATTR(u+1), id, names[perm[j] % 6], 2, (j*(M+1)+u+1+1 < N+N*M) ? &attrs[j*(M+1)+u+1+1] : NULL); } } // test the given permutation rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, attrs) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == N); for (unsigned j = 0; j < N; j++) { lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, j, buffer, 4) => 4; assert(memcmp(buffer, names[j % 6], 4) == 0); for (unsigned u = 0; u < M; u++) { lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(u+1), j, buffer, 4) => 2; assert(memcmp(buffer, names[j % 6], 2) == 0); } } // keep track of the worst size worst_size = lfs_max(worst_size, rbyd.off); // next permutation using Heap's algorithm if (stack[i] < i) { if (i % 2 == 0) { uint16_t t = perm[0]; perm[0] = perm[i]; perm[i] = t; } else { uint16_t t = perm[stack[i]]; perm[stack[i]] = perm[i]; perm[i] = t; } stack[i] += 1; i = 1; } else { stack[i] = 0; i += 1; } } // test that tree is self-balancing, we should be strictly bounded // by height <= 2*log(n)+1, assume tags are strictly <=12 bytes lfs_size_t n = 1 + N + N*M; printf("worst size: %u B (N=%u, estimate=%u)\n", worst_size, n, 12*n*(2*lfs_nlog2(n)+1)+4); printf("avg height: %u B (N=%u, estimate=%u)\n", worst_size / n, n, 12*(2*lfs_nlog2(n)+1)+4); // note this only holds true with byte-level progs if (PROG_SIZE == 1) { assert(worst_size / n <= 12*(2*lfs_nlog2(n)+1)+4); } ''' [cases.test_rbyd_multi_mixed_permutations] defines.N = 'range(1, 7)' defines.M = 'range(1, 4)' in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; const uint8_t names[6][4] = { "\xaa\xaa\xaa\xaa", "\xbb\xbb\xbb\xbb", "\xcc\xcc\xcc\xcc", "\xdd\xdd\xdd\xdd", "\xee\xee\xee\xee", "\xff\xff\xff\xff", }; uint8_t buffer[4]; // keep track of the worst case log size lfs_size_t worst_size = 0; // test all permutations of a given size uint16_t perm[N]; unsigned stack[N]; for (uint16_t i = 0; i < N; i++) { perm[i] = i; stack[i] = 0; } unsigned i = 1; while (i < N) { // print permutation to help debugging printf("--- permutation: ["); for (unsigned j = 0; j < N; j++) { if (j > 0) { printf(", "); } printf("%d", perm[j]+1); } printf("] ---\n"); // test the given permutation rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; for (unsigned j = 0; j < N; j++) { // adjust id based on future insertions uint16_t id = perm[j]; for (unsigned k = j+1; k < N; k++) { if (perm[j] > perm[k]) { id -= 1; } } lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, id, names[perm[j] % 6], 4, NULL)) => 0; // note uattrs have a smaller size to help debugging for (unsigned u = 0; u < M; u++) { lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(u+1), id, names[perm[j] % 6], 2, NULL)) => 0; } } lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == N); for (unsigned j = 0; j < N; j++) { lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, j, buffer, 4) => 4; assert(memcmp(buffer, names[j % 6], 4) == 0); for (unsigned u = 0; u < M; u++) { lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(u+1), j, buffer, 4) => 2; assert(memcmp(buffer, names[j % 6], 2) == 0); } } // keep track of the worst size worst_size = lfs_max(worst_size, rbyd.off); // next permutation using Heap's algorithm if (stack[i] < i) { if (i % 2 == 0) { uint16_t t = perm[0]; perm[0] = perm[i]; perm[i] = t; } else { uint16_t t = perm[stack[i]]; perm[stack[i]] = perm[i]; perm[i] = t; } stack[i] += 1; i = 1; } else { stack[i] = 0; i += 1; } } // test that tree is self-balancing, we should be strictly bounded // by height <= 2*log(n)+1, assume tags are strictly <=12 bytes lfs_size_t n = 1 + N + N*M; printf("worst size: %u B (N=%u, estimate=%u)\n", worst_size, n, 12*n*(2*lfs_nlog2(n)+1)+4); printf("avg height: %u B (N=%u, estimate=%u)\n", worst_size / n, n, 12*(2*lfs_nlog2(n)+1)+4); // note this only holds true with byte-level progs if (PROG_SIZE == 1) { assert(worst_size / n <= 12*(2*lfs_nlog2(n)+1)+4); } ''' [cases.test_rbyd_mixed_traverse] in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; uint8_t buffer[4]; // traverse requires correct biasing of the weights in the rbyd tree // so that lookups return strictly the tag greater than or equal to // the tag requested rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(1), 0, "\xaa\xaa", 2, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(1), 1, "\xbb\xbb", 2, NULL))))) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(0), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 0); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == 0); assert(size_ == 2); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 1); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == 1); assert(size_ == 2); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(0), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 0); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == 0); assert(size_ == 2); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 1); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == 1); assert(size_ == 2); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; // also try the other direction rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(1), 0, "\xbb\xbb", 2, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(1), 0, "\xaa\xaa", 2, NULL))))) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(0), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 0); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == 0); assert(size_ == 2); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 1); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == 1); assert(size_ == 2); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(0), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 0); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == 0); assert(size_ == 2); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 1); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == 1); assert(size_ == 2); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; ''' [cases.test_rbyd_multi_mixed_traverse] in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; uint8_t buffer[4]; // traverse requires correct biasing of the weights in the rbyd tree // so that lookups return strictly the tag greater than or equal to // the tag requested rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), 0, "\xaa\xaa", 2, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), 1, "\xbb\xbb", 2, NULL)) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(0), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 0); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == 0); assert(size_ == 2); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 1); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == 1); assert(size_ == 2); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(0), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 0); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == 0); assert(size_ == 2); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 1); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == 1); assert(size_ == 2); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; // also try the other direction rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xbb\xbb\xbb\xbb", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), 0, "\xbb\xbb", 2, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(1), 0, "\xaa\xaa", 2, NULL)) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(0), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 0); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == 0); assert(size_ == 2); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 1); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == 1); assert(size_ == 2); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(0), -1, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 0); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == 0); assert(size_ == 2); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == 1); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(1)); assert(id_ == 1); assert(size_ == 2); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; ''' [cases.test_rbyd_mixed_traverse_permutations] defines.N = 'range(1, 7)' defines.M = 'range(1, 4)' in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; const uint8_t names[6][4] = { "\xaa\xaa\xaa\xaa", "\xbb\xbb\xbb\xbb", "\xcc\xcc\xcc\xcc", "\xdd\xdd\xdd\xdd", "\xee\xee\xee\xee", "\xff\xff\xff\xff", }; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; uint8_t buffer[4]; // test all permutations of a given size uint16_t perm[N]; unsigned stack[N]; for (uint16_t i = 0; i < N; i++) { perm[i] = i; stack[i] = 0; } unsigned i = 1; while (i < N) { // print permutation to help debugging printf("--- permutation: ["); for (unsigned j = 0; j < N; j++) { if (j > 0) { printf(", "); } printf("%d", perm[j]+1); } printf("] ---\n"); // build the attribute list for the current permutation struct lfsr_attr attrs[N+N*M]; for (unsigned j = 0; j < N; j++) { // adjust id based on future insertions uint16_t id = perm[j]; for (unsigned k = j+1; k < N; k++) { if (perm[j] > perm[k]) { id -= 1; } } attrs[j*(M+1)] = *LFSR_ATTR( MKREG, id, names[perm[j] % 6], 4, (j*(M+1)+1 < N+N*M) ? &attrs[j*(M+1)+1] : NULL); // note uattrs have a smaller size to help debugging for (unsigned u = 0; u < M; u++) { attrs[j*(M+1)+u+1] = *LFSR_ATTR( UATTR(u+1), id, names[perm[j] % 6], 2, (j*(M+1)+u+1+1 < N+N*M) ? &attrs[j*(M+1)+u+1+1] : NULL); } } // test the given permutation rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, attrs) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == N); // try traversing all tags tag_ = 0; id_ = -1; for (unsigned j = 0; j < N; j++) { lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == j); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, names[j % 6], 4) == 0); for (unsigned u = 0; u < M; u++) { lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(u+1)); assert(id_ == j); assert(size_ == 2); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 2; assert(memcmp(buffer, names[j % 6], 2) == 0); } } lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; // next permutation using Heap's algorithm if (stack[i] < i) { if (i % 2 == 0) { uint16_t t = perm[0]; perm[0] = perm[i]; perm[i] = t; } else { uint16_t t = perm[stack[i]]; perm[stack[i]] = perm[i]; perm[i] = t; } stack[i] += 1; i = 1; } else { stack[i] = 0; i += 1; } } ''' [cases.test_rbyd_multi_mixed_traverse_permutations] defines.N = 'range(1, 7)' defines.M = 'range(1, 4)' in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; const uint8_t names[6][4] = { "\xaa\xaa\xaa\xaa", "\xbb\xbb\xbb\xbb", "\xcc\xcc\xcc\xcc", "\xdd\xdd\xdd\xdd", "\xee\xee\xee\xee", "\xff\xff\xff\xff", }; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; uint8_t buffer[4]; // test all permutations of a given size uint16_t perm[N]; unsigned stack[N]; for (uint16_t i = 0; i < N; i++) { perm[i] = i; stack[i] = 0; } unsigned i = 1; while (i < N) { // print permutation to help debugging printf("--- permutation: ["); for (unsigned j = 0; j < N; j++) { if (j > 0) { printf(", "); } printf("%d", perm[j]+1); } printf("] ---\n"); // test the given permutation rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; for (unsigned j = 0; j < N; j++) { // adjust id based on future insertions uint16_t id = perm[j]; for (unsigned k = j+1; k < N; k++) { if (perm[j] > perm[k]) { id -= 1; } } lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, id, names[perm[j] % 6], 4, NULL)) => 0; // note uattrs have a smaller size to help debugging for (unsigned u = 0; u < M; u++) { lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(u+1), id, names[perm[j] % 6], 2, NULL)) => 0; } } lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == N); // try traversing all tags tag_ = 0; id_ = -1; for (unsigned j = 0; j < N; j++) { lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == j); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; assert(memcmp(buffer, names[j % 6], 4) == 0); for (unsigned u = 0; u < M; u++) { lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(u+1)); assert(id_ == j); assert(size_ == 2); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 2; assert(memcmp(buffer, names[j % 6], 2) == 0); } } lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; // next permutation using Heap's algorithm if (stack[i] < i) { if (i % 2 == 0) { uint16_t t = perm[0]; perm[0] = perm[i]; perm[i] = t; } else { uint16_t t = perm[stack[i]]; perm[stack[i]] = perm[i]; perm[i] = t; } stack[i] += 1; i = 1; } else { stack[i] = 0; i += 1; } } ''' [cases.test_rbyd_mixed_update_permutations] defines.N = 'range(1, 4)' defines.M = 'range(1, 3)' in = 'lfs.c' if = 'BLOCK_SIZE/PROG_SIZE >= N' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; const uint8_t names[6][4] = { "\xaa\xaa\xaa\xaa", "\xbb\xbb\xbb\xbb", "\xcc\xcc\xcc\xcc", "\xdd\xdd\xdd\xdd", "\xee\xee\xee\xee", "\xff\xff\xff\xff", }; lfsr_rbyd_t rbyd; uint8_t buffer[4]; // keep track of the worst case log size lfs_size_t worst_size = 0; // create one consistent block rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; for (unsigned j = 0; j < N; j++) { lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, j, names[j % 6], 4, NULL)) => 0; // note uattrs have a smaller size to help debugging for (unsigned u = 0; u < M; u++) { lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(u+1), j, names[j % 6], 2, NULL)) => 0; } } // copy block so we can reset after each remove lfsr_rbyd_t backup_rbyd = rbyd; uint8_t backup_block[BLOCK_SIZE]; lfs_bd_read(&lfs, NULL, &lfs.rcache, rbyd.off, rbyd.block, 0, backup_block, rbyd.off) => 0; // test all permutations of a given size uint8_t perm[N*M]; unsigned stack[N*M]; for (uint8_t i = 0; i < N*M; i++) { perm[i] = i; stack[i] = 0; } unsigned i = 1; while (i < N*M) { // print permutation to help debugging printf("--- permutation: ["); for (unsigned j = 0; j < N*M; j++) { if (j > 0) { printf(", "); } printf("%d", perm[j]+1); } printf("] ---\n"); // restore backup rbyd = backup_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfs_bd_prog(&lfs, &lfs.pcache, &lfs.rcache, false, rbyd.block, 0, backup_block, rbyd.off) => 0; lfs_bd_flush(&lfs, &lfs.pcache, &lfs.rcache, false) => 0; // update each tag in permutation order for (unsigned j = 0; j < N*M; j++) { lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(perm[j]%M+1), perm[j]/M, names[(perm[j]/M) % 6], 3, NULL)) => 0; } // check that all tags have been updated lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; for (unsigned j = 0; j < N; j++) { lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, j, buffer, 4) => 4; assert(memcmp(buffer, names[j % 6], 4) == 0); for (unsigned u = 0; u < M; u++) { lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(u+1), j, buffer, 4) => 3; assert(memcmp(buffer, names[j % 6], 3) == 0); } } // keep track of the worst size worst_size = lfs_max(worst_size, rbyd.off); // next permutation using Heap's algorithm if (stack[i] < i) { if (i % 2 == 0) { uint8_t t = perm[0]; perm[0] = perm[i]; perm[i] = t; } else { uint8_t t = perm[stack[i]]; perm[stack[i]] = perm[i]; perm[i] = t; } stack[i] += 1; i = 1; } else { stack[i] = 0; i += 1; } } // test that tree is self-balancing, we should be strictly bounded // by height <= 2*log(n)+1, assume tags are strictly <=12 bytes lfs_size_t n = 1 + N + N*M + N*M; printf("worst size: %u B (N=%u, estimate=%u)\n", worst_size, n, 12*n*(2*lfs_nlog2(n)+1)+4); printf("avg height: %u B (N=%u, estimate=%u)\n", worst_size / n, n, 12*(2*lfs_nlog2(n)+1)+4); // note this only holds true with byte-level progs if (PROG_SIZE == 1) { assert(worst_size / n <= 12*(2*lfs_nlog2(n)+1)+4); } ''' [cases.test_rbyd_mixed_remove_permutations] defines.N = 'range(1, 7)' defines.M = 'range(1, 4)' in = 'lfs.c' if = 'BLOCK_SIZE/PROG_SIZE >= N+1' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; const uint8_t names[6][4] = { "\xaa\xaa\xaa\xaa", "\xbb\xbb\xbb\xbb", "\xcc\xcc\xcc\xcc", "\xdd\xdd\xdd\xdd", "\xee\xee\xee\xee", "\xff\xff\xff\xff", }; lfsr_rbyd_t rbyd; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; uint8_t buffer[4]; // keep track of the worst case log size lfs_size_t worst_size = 0; // test all permutations of a given size uint8_t perm[N]; unsigned stack[N]; for (uint8_t i = 0; i < N; i++) { perm[i] = i; stack[i] = 0; } unsigned i = 1; while (i < N) { // print permutation to help debugging printf("--- permutation: ["); for (unsigned j = 0; j < N; j++) { if (j > 0) { printf(", "); } printf("%d", perm[j]+1); } printf("] ---\n"); // create given permutation rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; for (unsigned j = 0; j < N; j++) { // adjust id based on future insertions uint16_t id = perm[j]; for (unsigned k = j+1; k < N; k++) { if (perm[j] > perm[k]) { id -= 1; } } lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, id, names[perm[j] % 6], 4, NULL)) => 0; // note uattrs have a smaller size to help debugging for (unsigned u = 0; u < M; u++) { lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(u+1), id, names[perm[j] % 6], 2, NULL)) => 0; } } // copy block so we can reset after each remove lfsr_rbyd_t backup_rbyd = rbyd; uint8_t backup_block[BLOCK_SIZE]; lfs_bd_read(&lfs, NULL, &lfs.rcache, rbyd.off, rbyd.block, 0, backup_block, rbyd.off) => 0; // try removing each tag for (unsigned j = 0; j < N*M; j++) { // print what we are removing to help debugging printf("--- remove: id%jd, %jd ---\n", j/M, (j%M)+1); rbyd = backup_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfs_bd_prog(&lfs, &lfs.pcache, &lfs.rcache, false, rbyd.block, 0, backup_block, rbyd.off) => 0; lfs_bd_flush(&lfs, &lfs.pcache, &lfs.rcache, false) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RMUATTR((j%M)+1), j/M, NULL, 0, NULL)) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; for (unsigned k = 0; k < N; k++) { lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, k, buffer, 4) => 4; assert(memcmp(buffer, names[k % 6], 4) == 0); for (unsigned u = 0; u < M; u++) { int err = lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(u+1), k, &tag_, &id_, &off_, &size_); assert(!err || err == LFS_ERR_NOENT); if (k == j/M && u == j%M) { if (u == M-1 && k == N-1) { assert(err == LFS_ERR_NOENT); } else if (u == M-1) { assert(!err); assert(tag_ == LFSR_TAG_MKREG); assert(id_ == k+1); assert(size_ == 4); } else { assert(!err); assert(tag_ == LFSR_TAG_UATTR(u+1+1)); assert(id_ == k); assert(size_ == 2); } } else { assert(tag_ == LFSR_TAG_UATTR(u+1)); assert(id_ == k); assert(size_ == 2); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 2; assert(memcmp(buffer, names[k % 6], 2) == 0); } } } // try append the tag back to make sure things still work printf("--- append: id%jd, %jd ---\n", j/M, (j%M)+1); lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR((j%M)+1), j/M, names[(j/M)%6], 3, NULL)) => 0; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; for (unsigned k = 0; k < N; k++) { lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, k, buffer, 4) => 4; assert(memcmp(buffer, names[k % 6], 4) == 0); for (unsigned u = 0; u < M; u++) { lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(u+1), k, &tag_, &id_, &off_, &size_) => 0; if (k == j/M && u == j%M) { assert(tag_ == LFSR_TAG_UATTR(u+1)); assert(id_ == k); assert(size_ == 3); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 3; assert(memcmp(buffer, names[k % 6], 3) == 0); } else { assert(tag_ == LFSR_TAG_UATTR(u+1)); assert(id_ == k); assert(size_ == 2); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 2; assert(memcmp(buffer, names[k % 6], 2) == 0); } } } // keep track of the worst size worst_size = lfs_max(worst_size, rbyd.off); } // next permutation using Heap's algorithm if (stack[i] < i) { if (i % 2 == 0) { uint8_t t = perm[0]; perm[0] = perm[i]; perm[i] = t; } else { uint8_t t = perm[stack[i]]; perm[stack[i]] = perm[i]; perm[i] = t; } stack[i] += 1; i = 1; } else { stack[i] = 0; i += 1; } } // test that tree is self-balancing, we should be strictly bounded // by height <= 2*log(n)+1, assume tags are strictly <=12 bytes lfs_size_t n = 1 + N+N*M + 2; printf("worst size: %u B (N=%u, estimate=%u)\n", worst_size, n, 12*n*(2*lfs_nlog2(n)+1)+4); printf("avg height: %u B (N=%u, estimate=%u)\n", worst_size / n, n, 12*(2*lfs_nlog2(n)+1)+4); // note this only holds true with byte-level progs if (PROG_SIZE == 1) { assert(worst_size / n <= 12*(2*lfs_nlog2(n)+1)+4); } ''' [cases.test_rbyd_mixed_remove_all_permutations] defines.N = 'range(1, 4)' defines.M = 'range(1, 3)' in = 'lfs.c' if = 'BLOCK_SIZE/PROG_SIZE >= N' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; const uint8_t names[6][4] = { "\xaa\xaa\xaa\xaa", "\xbb\xbb\xbb\xbb", "\xcc\xcc\xcc\xcc", "\xdd\xdd\xdd\xdd", "\xee\xee\xee\xee", "\xff\xff\xff\xff", }; lfsr_rbyd_t rbyd; uint8_t buffer[4]; // keep track of the worst case log size lfs_size_t worst_size = 0; // create one consistent block rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; for (unsigned j = 0; j < N; j++) { lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, j, names[j % 6], 4, NULL)) => 0; // note uattrs have a smaller size to help debugging for (unsigned u = 0; u < M; u++) { lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(u+1), j, names[j % 6], 2, NULL)) => 0; } } // copy block so we can reset after each remove lfsr_rbyd_t backup_rbyd = rbyd; uint8_t backup_block[BLOCK_SIZE]; lfs_bd_read(&lfs, NULL, &lfs.rcache, rbyd.off, rbyd.block, 0, backup_block, rbyd.off) => 0; // test all permutations of a given size uint8_t perm[N*M]; unsigned stack[N*M]; for (uint8_t i = 0; i < N*M; i++) { perm[i] = i; stack[i] = 0; } unsigned i = 1; while (i < N*M) { // print permutation to help debugging printf("--- permutation: ["); for (unsigned j = 0; j < N*M; j++) { if (j > 0) { printf(", "); } printf("%d", perm[j]+1); } printf("] ---\n"); // restore backup rbyd = backup_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfs_bd_prog(&lfs, &lfs.pcache, &lfs.rcache, false, rbyd.block, 0, backup_block, rbyd.off) => 0; lfs_bd_flush(&lfs, &lfs.pcache, &lfs.rcache, false) => 0; // remove each tag in permutation order for (unsigned j = 0; j < N*M; j++) { lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RMUATTR(perm[j]%M+1), perm[j]/M, NULL, 0, NULL)) => 0; } // check that all tags have been removed lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; for (unsigned j = 0; j < N; j++) { lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, j, buffer, 4) => 4; assert(memcmp(buffer, names[j % 6], 4) == 0); for (unsigned u = 0; u < M; u++) { lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(u+1), j, buffer, 4) => LFS_ERR_NOENT; } } // keep track of the worst size worst_size = lfs_max(worst_size, rbyd.off); // next permutation using Heap's algorithm if (stack[i] < i) { if (i % 2 == 0) { uint8_t t = perm[0]; perm[0] = perm[i]; perm[i] = t; } else { uint8_t t = perm[stack[i]]; perm[stack[i]] = perm[i]; perm[i] = t; } stack[i] += 1; i = 1; } else { stack[i] = 0; i += 1; } } // test that tree is self-balancing, we should be strictly bounded // by height <= 2*log(n)+1, assume tags are strictly <=12 bytes lfs_size_t n = 1 + N + N*M + N*M; printf("worst size: %u B (N=%u, estimate=%u)\n", worst_size, n, 12*n*(2*lfs_nlog2(n)+1)+4); printf("avg height: %u B (N=%u, estimate=%u)\n", worst_size / n, n, 12*(2*lfs_nlog2(n)+1)+4); // note this only holds true with byte-level progs if (PROG_SIZE == 1) { assert(worst_size / n <= 12*(2*lfs_nlog2(n)+1)+4); } ''' [cases.test_rbyd_mixed_large] in = 'lfs.c' # ORDER: # 0 = in-order # 1 = reverse-order # 2 = random-order defines.ORDER = [0, 1, 2] defines.M = 'range(1, 4)' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; const uint8_t names[6][4] = { "\xaa\xaa\xaa\xaa", "\xbb\xbb\xbb\xbb", "\xcc\xcc\xcc\xcc", "\xdd\xdd\xdd\xdd", "\xee\xee\xee\xee", "\xff\xff\xff\xff", }; // create the rbyd tree rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; // keep inserting tags until we run out of space // // note, the ids we create this way are both sparse and sometimes // repeated, so we need to mod our current rbyd size to avoid invalid // insertions // uint32_t prng = 42; for (lfs_size_t i = 0;; i++) { uint16_t x = (ORDER == 0) ? (uint16_t)i : (ORDER == 1) ? (uint16_t)(((lfs_size_t)-1) - i) : (uint16_t)TEST_PRNG(&prng); x = x % (rbyd.weight+1); // build a single attribute list with all attributes, if this fails // it should fail atomically struct lfsr_attr attrs[M+1]; attrs[0] = *LFSR_ATTR( MKREG, x, names[x % 6], 4, M > 0 ? &attrs[1] : NULL); for (unsigned u = 0; u < M; u++) { attrs[1+u] = *LFSR_ATTR( UATTR(u+1), x, names[x % 6], 2, (u+1 < M) ? &attrs[1+u+1] : NULL); } int err = lfsr_rbyd_commit(&lfs, &rbyd, attrs); // if we can't fit an fcrc, erased is set to false, but if we can, // lfsr_rbyd_commit may error later with LFS_ERR_RANGE if (!rbyd.erased || err == LFS_ERR_RANGE) { break; } assert(err == 0); } // check that we can at least lookup all the tags // // note with random order we can't check that stored values reliably lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; for (uint16_t x = 0; x < rbyd.weight; x++) { lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_MKREG, x, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == x); assert(size_ == 4); for (unsigned u = 0; u < M; u++) { lfsr_rbyd_lookup(&lfs, &rbyd, LFSR_TAG_UATTR(u+1), x, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(u+1)); assert(id_ == x); assert(size_ == 2); } } ''' ### Deletion testing ### [cases.test_rbyd_delete] in = 'lfs.c' if = 'BLOCK_SIZE/PROG_SIZE >= 2' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; uint8_t buffer[4]; // try to delete one id rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, NULL))) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RM, 1, NULL, 0, NULL)) => 0; assert(rbyd.weight == 1); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 1); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => LFS_ERR_NOENT; // try to delete the other id rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, NULL))) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RM, 0, NULL, 0, NULL)) => 0; assert(rbyd.weight == 1); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 1); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => LFS_ERR_NOENT; // try to delete the largest of three rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(MKREG, 2, "\xcc\xcc\xcc\xcc", 4, NULL)))) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RM, 2, NULL, 0, NULL)) => 0; assert(rbyd.weight == 2); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => LFS_ERR_NOENT; // try to delete the smallest of three rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(MKREG, 2, "\xcc\xcc\xcc\xcc", 4, NULL)))) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RM, 0, NULL, 0, NULL)) => 0; assert(rbyd.weight == 2); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => LFS_ERR_NOENT; // try to delete the middle rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(MKREG, 2, "\xcc\xcc\xcc\xcc", 4, NULL)))) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RM, 1, NULL, 0, NULL)) => 0; assert(rbyd.weight == 2); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => LFS_ERR_NOENT; ''' [cases.test_rbyd_delete_range] in = 'lfs.c' if = 'BLOCK_SIZE/PROG_SIZE >= 2' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; uint8_t buffer[4]; // try to delete one id rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(1), 0, "\xaa\xaa", 2, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(1), 1, "\xbb\xbb", 2, NULL))))) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RM, 1, NULL, 0, NULL)) => 0; assert(rbyd.weight == 1); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 1, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 1); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 1, buffer, 4) => LFS_ERR_NOENT; // try to delete the other id rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(1), 0, "\xaa\xaa", 2, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(1), 1, "\xbb\xbb", 2, NULL))))) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RM, 0, NULL, 0, NULL)) => 0; assert(rbyd.weight == 1); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 1, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 1); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 1, buffer, 4) => LFS_ERR_NOENT; // try to delete the largest of three rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(1), 0, "\xaa\xaa", 2, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(1), 1, "\xbb\xbb", 2, LFSR_ATTR(MKREG, 2, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(1), 2, "\xcc\xcc", 2, NULL))))))) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RM, 2, NULL, 0, NULL)) => 0; assert(rbyd.weight == 2); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 1, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 2, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 2); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 1, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 2, buffer, 4) => LFS_ERR_NOENT; // try to delete the smallest of three rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(1), 0, "\xaa\xaa", 2, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(1), 1, "\xbb\xbb", 2, LFSR_ATTR(MKREG, 2, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(1), 2, "\xcc\xcc", 2, NULL))))))) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RM, 0, NULL, 0, NULL)) => 0; assert(rbyd.weight == 2); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 1, buffer, 4) => 2; assert(memcmp(buffer, "\xcc\xcc", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 2, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 2); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xbb\xbb", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 1, buffer, 4) => 2; assert(memcmp(buffer, "\xcc\xcc", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 2, buffer, 4) => LFS_ERR_NOENT; // try to delete the middle rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(1), 0, "\xaa\xaa", 2, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(1), 1, "\xbb\xbb", 2, LFSR_ATTR(MKREG, 2, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(1), 2, "\xcc\xcc", 2, NULL))))))) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RM, 1, NULL, 0, NULL)) => 0; assert(rbyd.weight == 2); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 1, buffer, 4) => 2; assert(memcmp(buffer, "\xcc\xcc", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 2, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 2); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => 4; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 0, buffer, 4) => 2; assert(memcmp(buffer, "\xaa\xaa", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 4) => 4; assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 1, buffer, 4) => 2; assert(memcmp(buffer, "\xcc\xcc", 2) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 2, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 2, buffer, 4) => LFS_ERR_NOENT; ''' [cases.test_rbyd_delete_permutations] defines.N = 'range(1, 7)' in = 'lfs.c' if = 'BLOCK_SIZE/PROG_SIZE >= N+1' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; const uint8_t names[6][6] = { "\xaa\xaa\xaa\xaa\xaa\xaa", "\xbb\xbb\xbb\xbb\xbb\xbb", "\xcc\xcc\xcc\xcc\xcc\xcc", "\xdd\xdd\xdd\xdd\xdd\xdd", "\xee\xee\xee\xee\xee\xee", "\xff\xff\xff\xff\xff\xff", }; uint8_t buffer[6]; // keep track of the worst case log size lfs_size_t worst_size = 0; // test all permutations of a given size uint16_t perm[N]; unsigned stack[N]; for (uint16_t i = 0; i < N; i++) { perm[i] = i; stack[i] = 0; } unsigned i = 1; while (i < N) { // print permutation to help debugging printf("--- permutation: ["); for (unsigned j = 0; j < N; j++) { if (j > 0) { printf(", "); } printf("%d", perm[j]+1); } printf("] ---\n"); // create given permutation with multiple commits rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; for (unsigned j = 0; j < N; j++) { // adjust id based on future insertions uint16_t id = perm[j]; for (unsigned k = j+1; k < N; k++) { if (perm[j] > perm[k]) { id -= 1; } } lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, id, names[perm[j] % 6], 4, NULL)) => 0; } assert(rbyd.weight == N); // copy block so we can reset after each delete lfsr_rbyd_t backup_rbyd = rbyd; uint8_t backup_block[BLOCK_SIZE]; lfs_bd_read(&lfs, NULL, &lfs.rcache, rbyd.off, rbyd.block, 0, backup_block, rbyd.off) => 0; // try deleting each id for (unsigned j = 0; j < N; j++) { // print what we are deleting to help debugging printf("--- delete: %d ---\n", j+1); rbyd = backup_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfs_bd_prog(&lfs, &lfs.pcache, &lfs.rcache, false, rbyd.block, 0, backup_block, rbyd.off) => 0; lfs_bd_flush(&lfs, &lfs.pcache, &lfs.rcache, false) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RM, j, NULL, 0, NULL)) => 0; assert(rbyd.weight == N-1); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == N-1); for (unsigned k = 0; k < N-1; k++) { lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, k, buffer, 4) => 4; if (k >= j) { assert(memcmp(buffer, names[(k+1) % 6], 4) == 0); } else { assert(memcmp(buffer, names[k % 6], 4) == 0); } } lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, N-1, buffer, 4) => LFS_ERR_NOENT; // try recreating the id to make sure things still work printf("--- create: %d ---\n", j+1); lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, j, names[j % 6], 6, NULL)) => 0; assert(rbyd.weight == N); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == N); for (unsigned k = 0; k < N; k++) { if (k == j) { lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, k, buffer, 6) => 6; assert(memcmp(buffer, names[k % 6], 6) == 0); } else { lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, k, buffer, 6) => 4; assert(memcmp(buffer, names[k % 6], 4) == 0); } } // keep track of the worst size worst_size = lfs_max(worst_size, rbyd.off); } // next permutation using Heap's algorithm if (stack[i] < i) { if (i % 2 == 0) { uint16_t t = perm[0]; perm[0] = perm[i]; perm[i] = t; } else { uint16_t t = perm[stack[i]]; perm[stack[i]] = perm[i]; perm[i] = t; } stack[i] += 1; i = 1; } else { stack[i] = 0; i += 1; } } // test that tree is self-balancing, we should be strictly bounded // by height <= 2*log(n)+1, assume tags are strictly <=12 bytes lfs_size_t n = 1 + N + 2; printf("worst size: %u B (N=%u, estimate=%u)\n", worst_size, n, 12*n*(2*lfs_nlog2(n)+1)+4); printf("avg height: %u B (N=%u, estimate=%u)\n", worst_size / n, n, 12*(2*lfs_nlog2(n)+1)+4); // note this only holds true with byte-level progs if (PROG_SIZE == 1) { assert(worst_size / n <= 12*(2*lfs_nlog2(n)+1)+4); } ''' [cases.test_rbyd_delete_range_permutations] defines.N = 'range(1, 7)' defines.M = 'range(1, 4)' in = 'lfs.c' if = ''' BLOCK_SIZE/PROG_SIZE >= N+N*M+1 && BLOCK_SIZE >= 4096 ''' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; const uint8_t names[6][6] = { "\xaa\xaa\xaa\xaa\xaa\xaa", "\xbb\xbb\xbb\xbb\xbb\xbb", "\xcc\xcc\xcc\xcc\xcc\xcc", "\xdd\xdd\xdd\xdd\xdd\xdd", "\xee\xee\xee\xee\xee\xee", "\xff\xff\xff\xff\xff\xff", }; uint8_t buffer[6]; // keep track of the worst case log size lfs_size_t worst_size = 0; // test all permutations of a given size uint16_t perm[N]; unsigned stack[N]; for (uint16_t i = 0; i < N; i++) { perm[i] = i; stack[i] = 0; } unsigned i = 1; while (i < N) { // print permutation to help debugging printf("--- permutation: ["); for (unsigned j = 0; j < N; j++) { if (j > 0) { printf(", "); } printf("%d", perm[j]+1); } printf("] ---\n"); // create given permutation with multiple commits rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; for (unsigned j = 0; j < N; j++) { // adjust id based on future insertions uint16_t id = perm[j]; for (unsigned k = j+1; k < N; k++) { if (perm[j] > perm[k]) { id -= 1; } } lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, id, names[perm[j] % 6], 4, NULL)) => 0; // note uattrs have a smaller size to help debugging for (unsigned u = 0; u < M; u++) { lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(u+1), id, names[perm[j] % 6], 2, NULL)) => 0; } } assert(rbyd.weight == N); // copy block so we can reset after each delete lfsr_rbyd_t backup_rbyd = rbyd; uint8_t backup_block[BLOCK_SIZE]; lfs_bd_read(&lfs, NULL, &lfs.rcache, rbyd.off, rbyd.block, 0, backup_block, rbyd.off) => 0; // try deleting each id for (unsigned j = 0; j < N; j++) { // print what we are deleting to help debugging printf("--- delete: %d ---\n", j+1); rbyd = backup_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfs_bd_prog(&lfs, &lfs.pcache, &lfs.rcache, false, rbyd.block, 0, backup_block, rbyd.off) => 0; lfs_bd_flush(&lfs, &lfs.pcache, &lfs.rcache, false) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RM, j, NULL, 0, NULL)) => 0; assert(rbyd.weight == N-1); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == N-1); for (unsigned k = 0; k < N-1; k++) { lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, k, buffer, 4) => 4; if (k >= j) { assert(memcmp(buffer, names[(k+1) % 6], 4) == 0); } else { assert(memcmp(buffer, names[k % 6], 4) == 0); } for (unsigned u = 0; u < M; u++) { lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(u+1), k, buffer, 4) => 2; if (k >= j) { assert(memcmp(buffer, names[(k+1) % 6], 2) == 0); } else { assert(memcmp(buffer, names[k % 6], 2) == 0); } } } lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, N-1, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), N-1, buffer, 4) => LFS_ERR_NOENT; // try recreating the id to make sure things still work printf("--- create: %d ---\n", j+1); lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, j, names[j % 6], 6, NULL)) => 0; for (unsigned u = 0; u < M; u++) { lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(u+1), j, names[j % 6], 3, NULL)) => 0; } assert(rbyd.weight == N); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == N); for (unsigned k = 0; k < N; k++) { if (k == j) { lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, k, buffer, 6) => 6; assert(memcmp(buffer, names[k % 6], 6) == 0); for (unsigned u = 0; u < M; u++) { lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(u+1), k, buffer, 6) => 3; assert(memcmp(buffer, names[k % 6], 3) == 0); } } else { lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, k, buffer, 6) => 4; assert(memcmp(buffer, names[k % 6], 4) == 0); for (unsigned u = 0; u < M; u++) { lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(u+1), k, buffer, 6) => 2; assert(memcmp(buffer, names[k % 6], 2) == 0); } } } // keep track of the worst size worst_size = lfs_max(worst_size, rbyd.off); } // next permutation using Heap's algorithm if (stack[i] < i) { if (i % 2 == 0) { uint16_t t = perm[0]; perm[0] = perm[i]; perm[i] = t; } else { uint16_t t = perm[stack[i]]; perm[stack[i]] = perm[i]; perm[i] = t; } stack[i] += 1; i = 1; } else { stack[i] = 0; i += 1; } } // test that tree is self-balancing, we should be strictly bounded // by height <= 2*log(n)+1, assume tags are strictly <=12 bytes lfs_size_t n = 1 + N+N*M + 1 + 1+M; printf("worst size: %u B (N=%u, estimate=%u)\n", worst_size, n, 12*n*(2*lfs_nlog2(n)+1)+4); printf("avg height: %u B (N=%u, estimate=%u)\n", worst_size / n, n, 12*(2*lfs_nlog2(n)+1)+4); // note this only holds true with byte-level progs if (PROG_SIZE == 1) { assert(worst_size / n <= 12*(2*lfs_nlog2(n)+1)+4); } ''' [cases.test_rbyd_delete_traverse_permutations] defines.N = 'range(1, 7)' in = 'lfs.c' if = 'BLOCK_SIZE/PROG_SIZE >= N+1' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; const uint8_t names[6][4] = { "\xaa\xaa\xaa\xaa", "\xbb\xbb\xbb\xbb", "\xcc\xcc\xcc\xcc", "\xdd\xdd\xdd\xdd", "\xee\xee\xee\xee", "\xff\xff\xff\xff", }; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; uint8_t buffer[4]; // test all permutations of a given size uint16_t perm[N]; unsigned stack[N]; for (uint16_t i = 0; i < N; i++) { perm[i] = i; stack[i] = 0; } unsigned i = 1; while (i < N) { // print permutation to help debugging printf("--- permutation: ["); for (unsigned j = 0; j < N; j++) { if (j > 0) { printf(", "); } printf("%d", perm[j]+1); } printf("] ---\n"); // create given permutation with multiple commits rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; for (unsigned j = 0; j < N; j++) { // adjust id based on future insertions uint16_t id = perm[j]; for (unsigned k = j+1; k < N; k++) { if (perm[j] > perm[k]) { id -= 1; } } lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, id, names[perm[j] % 6], 4, NULL)) => 0; } assert(rbyd.weight == N); // copy block so we can reset after each delete lfsr_rbyd_t backup_rbyd = rbyd; uint8_t backup_block[BLOCK_SIZE]; lfs_bd_read(&lfs, NULL, &lfs.rcache, rbyd.off, rbyd.block, 0, backup_block, rbyd.off) => 0; // try deleting each id for (unsigned j = 0; j < N; j++) { // print what we are deleting to help debugging printf("--- delete: %d ---\n", j+1); rbyd = backup_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfs_bd_prog(&lfs, &lfs.pcache, &lfs.rcache, false, rbyd.block, 0, backup_block, rbyd.off) => 0; lfs_bd_flush(&lfs, &lfs.pcache, &lfs.rcache, false) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RM, j, NULL, 0, NULL)) => 0; assert(rbyd.weight == N-1); // try traversing over the tags lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == N-1); tag_ = 0; id_ = -1; for (unsigned k = 0; k < N-1; k++) { lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == k); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; if (k >= j) { assert(memcmp(buffer, names[(k+1) % 6], 4) == 0); } else { assert(memcmp(buffer, names[k % 6], 4) == 0); } } lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; } // next permutation using Heap's algorithm if (stack[i] < i) { if (i % 2 == 0) { uint16_t t = perm[0]; perm[0] = perm[i]; perm[i] = t; } else { uint16_t t = perm[stack[i]]; perm[stack[i]] = perm[i]; perm[i] = t; } stack[i] += 1; i = 1; } else { stack[i] = 0; i += 1; } } ''' [cases.test_rbyd_delete_traverse_range_permutations] defines.N = 'range(1, 7)' defines.M = 'range(1, 4)' in = 'lfs.c' if = ''' BLOCK_SIZE/PROG_SIZE >= N+N*M+1 && BLOCK_SIZE >= 4096 ''' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; const uint8_t names[6][4] = { "\xaa\xaa\xaa\xaa", "\xbb\xbb\xbb\xbb", "\xcc\xcc\xcc\xcc", "\xdd\xdd\xdd\xdd", "\xee\xee\xee\xee", "\xff\xff\xff\xff", }; lfsr_tag_t tag_; lfsr_sid_t id_; lfs_off_t off_; lfs_size_t size_; uint8_t buffer[4]; // test all permutations of a given size uint16_t perm[N]; unsigned stack[N]; for (uint16_t i = 0; i < N; i++) { perm[i] = i; stack[i] = 0; } unsigned i = 1; while (i < N) { // print permutation to help debugging printf("--- permutation: ["); for (unsigned j = 0; j < N; j++) { if (j > 0) { printf(", "); } printf("%d", perm[j]+1); } printf("] ---\n"); // create given permutation with multiple commits rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; for (unsigned j = 0; j < N; j++) { // adjust id based on future insertions uint16_t id = perm[j]; for (unsigned k = j+1; k < N; k++) { if (perm[j] > perm[k]) { id -= 1; } } lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, id, names[perm[j] % 6], 4, NULL)) => 0; // note uattrs have a smaller size to help debugging for (unsigned u = 0; u < M; u++) { lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(u+1), id, names[perm[j] % 6], 2, NULL)) => 0; } } assert(rbyd.weight == N); // copy block so we can reset after each delete lfsr_rbyd_t backup_rbyd = rbyd; uint8_t backup_block[BLOCK_SIZE]; lfs_bd_read(&lfs, NULL, &lfs.rcache, rbyd.off, rbyd.block, 0, backup_block, rbyd.off) => 0; // try deleting each id for (unsigned j = 0; j < N; j++) { // print what we are deleting to help debugging printf("--- delete: %d ---\n", j+1); rbyd = backup_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfs_bd_prog(&lfs, &lfs.pcache, &lfs.rcache, false, rbyd.block, 0, backup_block, rbyd.off) => 0; lfs_bd_flush(&lfs, &lfs.pcache, &lfs.rcache, false) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RM, j, NULL, 0, NULL)) => 0; assert(rbyd.weight == N-1); // try traversing over the tags lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == N-1); tag_ = 0; id_ = -1; for (unsigned k = 0; k < N-1; k++) { lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_MKREG); assert(id_ == k); assert(size_ == 4); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 4; if (k >= j) { assert(memcmp(buffer, names[(k+1) % 6], 4) == 0); } else { assert(memcmp(buffer, names[k % 6], 4) == 0); } for (unsigned u = 0; u < M; u++) { lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => 0; assert(tag_ == LFSR_TAG_UATTR(u+1)); assert(id_ == k); assert(size_ == 2); lfsr_rbyd_get(&lfs, &rbyd, tag_, id_, buffer, 4) => 2; if (k >= j) { assert(memcmp(buffer, names[(k+1) % 6], 2) == 0); } else { assert(memcmp(buffer, names[k % 6], 2) == 0); } } } lfsr_rbyd_lookup(&lfs, &rbyd, lfsr_tag_next(tag_), id_, &tag_, &id_, &off_, &size_) => LFS_ERR_NOENT; } // next permutation using Heap's algorithm if (stack[i] < i) { if (i % 2 == 0) { uint16_t t = perm[0]; perm[0] = perm[i]; perm[i] = t; } else { uint16_t t = perm[stack[i]]; perm[stack[i]] = perm[i]; perm[i] = t; } stack[i] += 1; i = 1; } else { stack[i] = 0; i += 1; } } ''' [cases.test_rbyd_delete_all] in = 'lfs.c' if = 'BLOCK_SIZE/PROG_SIZE >= 2' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; uint8_t buffer[4]; // create and delete one id rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RM, 0, NULL, 0, NULL)) => 0; assert(rbyd.weight == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => LFS_ERR_NOENT; // create and delete two ids rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, NULL))) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RM, 0, NULL, 0, LFSR_ATTR(RM, 0, NULL, 0, NULL))) => 0; assert(rbyd.weight == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => LFS_ERR_NOENT; // create and delete two ids in the other order rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, NULL))) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RM, 1, NULL, 0, LFSR_ATTR(RM, 0, NULL, 0, NULL))) => 0; assert(rbyd.weight == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => LFS_ERR_NOENT; // create and delete three ids rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(MKREG, 2, "\xcc\xcc\xcc\xcc", 4, NULL)))) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RM, 0, NULL, 0, LFSR_ATTR(RM, 0, NULL, 0, LFSR_ATTR(RM, 0, NULL, 0, NULL)))) => 0; assert(rbyd.weight == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => LFS_ERR_NOENT; // create and delete three ids in the other order rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(MKREG, 2, "\xcc\xcc\xcc\xcc", 4, NULL)))) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RM, 2, NULL, 0, LFSR_ATTR(RM, 1, NULL, 0, LFSR_ATTR(RM, 0, NULL, 0, NULL)))) => 0; assert(rbyd.weight == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => LFS_ERR_NOENT; ''' [cases.test_rbyd_delete_all_range] in = 'lfs.c' if = 'BLOCK_SIZE/PROG_SIZE >= 2' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; uint8_t buffer[4]; // create and delete one id rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(1), 0, "\xaa\xaa", 2, NULL))) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RM, 0, NULL, 0, NULL)) => 0; assert(rbyd.weight == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => LFS_ERR_NOENT; // create and delete two ids rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(1), 0, "\xaa\xaa", 2, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(1), 1, "\xbb\xbb", 2, NULL))))) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RM, 0, NULL, 0, LFSR_ATTR(RM, 0, NULL, 0, NULL))) => 0; assert(rbyd.weight == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => LFS_ERR_NOENT; // create and delete two ids in the other order rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(1), 0, "\xaa\xaa", 2, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(1), 1, "\xbb\xbb", 2, NULL))))) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RM, 1, NULL, 0, LFSR_ATTR(RM, 0, NULL, 0, NULL))) => 0; assert(rbyd.weight == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => LFS_ERR_NOENT; // create and delete three ids rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(1), 0, "\xaa\xaa", 2, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(1), 1, "\xbb\xbb", 2, LFSR_ATTR(MKREG, 2, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(1), 2, "\xcc\xcc", 2, NULL))))))) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RM, 0, NULL, 0, LFSR_ATTR(RM, 0, NULL, 0, LFSR_ATTR(RM, 0, NULL, 0, NULL)))) => 0; assert(rbyd.weight == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => LFS_ERR_NOENT; // create and delete three ids in the other order rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa", 4, LFSR_ATTR(UATTR(1), 0, "\xaa\xaa", 2, LFSR_ATTR(MKREG, 1, "\xbb\xbb\xbb\xbb", 4, LFSR_ATTR(UATTR(1), 1, "\xbb\xbb", 2, LFSR_ATTR(MKREG, 2, "\xcc\xcc\xcc\xcc", 4, LFSR_ATTR(UATTR(1), 2, "\xcc\xcc", 2, NULL))))))) => 0; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RM, 2, NULL, 0, LFSR_ATTR(RM, 1, NULL, 0, LFSR_ATTR(RM, 0, NULL, 0, NULL)))) => 0; assert(rbyd.weight == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => LFS_ERR_NOENT; lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => LFS_ERR_NOENT; ''' [cases.test_rbyd_delete_all_permutations] defines.N = 'range(1, 7)' in = 'lfs.c' if = ''' BLOCK_SIZE/PROG_SIZE >= 2*N+1 && BLOCK_SIZE >= 1024 ''' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; const uint8_t names[6][4] = { "\xaa\xaa\xaa\xaa", "\xbb\xbb\xbb\xbb", "\xcc\xcc\xcc\xcc", "\xdd\xdd\xdd\xdd", "\xee\xee\xee\xee", "\xff\xff\xff\xff", }; uint8_t buffer[6]; // keep track of the worst case log size lfs_size_t worst_size = 0; // create one consistent block rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; for (unsigned j = 0; j < N; j++) { lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, j, names[j % 6], 4, NULL)) => 0; } assert(rbyd.weight == N); // copy block so we can reset after each delete lfsr_rbyd_t backup_rbyd = rbyd; uint8_t backup_block[BLOCK_SIZE]; lfs_bd_read(&lfs, NULL, &lfs.rcache, rbyd.off, rbyd.block, 0, backup_block, rbyd.off) => 0; // test all permutations of a given size uint16_t perm[N]; unsigned stack[N]; for (uint16_t i = 0; i < N; i++) { perm[i] = i; stack[i] = 0; } unsigned i = 1; while (i < N) { // print permutation to help debugging printf("--- permutation: ["); for (unsigned j = 0; j < N; j++) { if (j > 0) { printf(", "); } printf("%d", perm[j]+1); } printf("] ---\n"); // restore backup rbyd = backup_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfs_bd_prog(&lfs, &lfs.pcache, &lfs.rcache, false, rbyd.block, 0, backup_block, rbyd.off) => 0; lfs_bd_flush(&lfs, &lfs.pcache, &lfs.rcache, false) => 0; // delete each id in permutation order for (unsigned j = 0; j < N; j++) { // adjust id based on previous deletions uint16_t id = perm[j]; for (unsigned k = 0; k < j; k++) { if (perm[k] < perm[j]) { id -= 1; } } lfs_size_t rbyd_weight_before = rbyd.weight; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RM, id, NULL, 0, NULL)) => 0; assert(rbyd.weight == rbyd_weight_before-1); } // check that all tags are now removed lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => LFS_ERR_NOENT; // try resuming from all tags being removed lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa\xaa\xaa", 6, NULL)) => 0; assert(rbyd.weight == 1); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 1); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 6) => 6; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa\xaa\xaa", 6) == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 6) => LFS_ERR_NOENT; // keep track of the worst size worst_size = lfs_max(worst_size, rbyd.off); // next permutation using Heap's algorithm if (stack[i] < i) { if (i % 2 == 0) { uint16_t t = perm[0]; perm[0] = perm[i]; perm[i] = t; } else { uint16_t t = perm[stack[i]]; perm[stack[i]] = perm[i]; perm[i] = t; } stack[i] += 1; i = 1; } else { stack[i] = 0; i += 1; } } // test that tree is self-balancing, we should be strictly bounded // by height <= 2*log(n)+1, assume tags are strictly <=12 bytes lfs_size_t n = 1 + 2*N + 1; printf("worst size: %u B (N=%u, estimate=%u)\n", worst_size, n, 12*n*(2*lfs_nlog2(n)+1)+4); printf("avg height: %u B (N=%u, estimate=%u)\n", worst_size / n, n, 12*(2*lfs_nlog2(n)+1)+4); // note this only holds true with byte-level progs if (PROG_SIZE == 1) { assert(worst_size / n <= 12*(2*lfs_nlog2(n)+1)+4); } ''' [cases.test_rbyd_delete_all_range_permutations] defines.N = 'range(1, 7)' defines.M = 'range(1, 4)' in = 'lfs.c' if = ''' BLOCK_SIZE/PROG_SIZE >= N+N*M + N + 1+M && BLOCK_SIZE >= 4096 ''' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; const uint8_t names[6][4] = { "\xaa\xaa\xaa\xaa", "\xbb\xbb\xbb\xbb", "\xcc\xcc\xcc\xcc", "\xdd\xdd\xdd\xdd", "\xee\xee\xee\xee", "\xff\xff\xff\xff", }; uint8_t buffer[6]; // keep track of the worst case log size lfs_size_t worst_size = 0; // create one consistent block rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; for (unsigned j = 0; j < N; j++) { lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, j, names[j % 6], 4, NULL)) => 0; // note uattrs have a smaller size to help debugging for (unsigned u = 0; u < M; u++) { lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(u+1), j, names[j % 6], 2, NULL)) => 0; } } assert(rbyd.weight == N); // copy block so we can reset after each delete lfsr_rbyd_t backup_rbyd = rbyd; uint8_t backup_block[BLOCK_SIZE]; lfs_bd_read(&lfs, NULL, &lfs.rcache, rbyd.off, rbyd.block, 0, backup_block, rbyd.off) => 0; // test all permutations of a given size uint16_t perm[N]; unsigned stack[N]; for (uint16_t i = 0; i < N; i++) { perm[i] = i; stack[i] = 0; } unsigned i = 1; while (i < N) { // print permutation to help debugging printf("--- permutation: ["); for (unsigned j = 0; j < N; j++) { if (j > 0) { printf(", "); } printf("%d", perm[j]+1); } printf("] ---\n"); // restore backup rbyd = backup_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; lfs_bd_prog(&lfs, &lfs.pcache, &lfs.rcache, false, rbyd.block, 0, backup_block, rbyd.off) => 0; lfs_bd_flush(&lfs, &lfs.pcache, &lfs.rcache, false) => 0; // delete each id in permutation order for (unsigned j = 0; j < N; j++) { // adjust id based on previous deletions uint16_t id = perm[j]; for (unsigned k = 0; k < j; k++) { if (perm[k] < perm[j]) { id -= 1; } } lfs_size_t rbyd_weight_before = rbyd.weight; lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RM, id, NULL, 0, NULL)) => 0; assert(rbyd.weight == rbyd_weight_before-1); } // check that all tags are now removed lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 0); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 4) => LFS_ERR_NOENT; // try resuming from all tags being removed lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, 0, "\xaa\xaa\xaa\xaa\xaa\xaa", 6, NULL)) => 0; for (unsigned u = 0; u < M; u++) { lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(UATTR(u+1), 0, "\xaa\xaa\xaa", 3, NULL)) => 0; } assert(rbyd.weight == 1); lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, cfg->block_size, NULL) => 0; assert(rbyd.weight == 1); lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 0, buffer, 6) => 6; assert(memcmp(buffer, "\xaa\xaa\xaa\xaa\xaa\xaa", 6) == 0); for (unsigned u = 0; u < M; u++) { lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(u+1), 0, buffer, 6) => 3; assert(memcmp(buffer, "\xaa\xaa\xaa", 3) == 0); } lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, 1, buffer, 6) => LFS_ERR_NOENT; lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_UATTR(1), 1, buffer, 6) => LFS_ERR_NOENT; // keep track of the worst size worst_size = lfs_max(worst_size, rbyd.off); // next permutation using Heap's algorithm if (stack[i] < i) { if (i % 2 == 0) { uint16_t t = perm[0]; perm[0] = perm[i]; perm[i] = t; } else { uint16_t t = perm[stack[i]]; perm[stack[i]] = perm[i]; perm[i] = t; } stack[i] += 1; i = 1; } else { stack[i] = 0; i += 1; } } // test that tree is self-balancing, we should be strictly bounded // by height <= 2*log(n)+1, assume tags are strictly <=12 bytes lfs_size_t n = 1 + N+N*M + N + 1+M; printf("worst size: %u B (N=%u, estimate=%u)\n", worst_size, n, 12*n*(2*lfs_nlog2(n)+1)+4); printf("avg height: %u B (N=%u, estimate=%u)\n", worst_size / n, n, 12*(2*lfs_nlog2(n)+1)+4); // note this only holds true with byte-level progs if (PROG_SIZE == 1) { assert(worst_size / n <= 12*(2*lfs_nlog2(n)+1)+4); } ''' # TODO rm me #[cases.test_rbyd_delete_create_permutations] #defines.N = 'range(1, 6)' #in = 'lfs.c' #if = 'BLOCK_SIZE/PROG_SIZE >= N+2' #code = ''' # lfs_t lfs; # lfs_init(&lfs, cfg) => 0; # # lfsr_rbyd_t init_rbyd = { # .block = 0, # .rev = 1, # .off = 0, # .crc = 0, # .trunk = 0, # .weight = 0, # .erased = true, # }; # lfsr_rbyd_t rbyd; # const uint8_t names[6][6] = { # "\xaa\xaa\xaa\xaa\xaa\xaa", # "\xbb\xbb\xbb\xbb\xbb\xbb", # "\xcc\xcc\xcc\xcc\xcc\xcc", # "\xdd\xdd\xdd\xdd\xdd\xdd", # "\xee\xee\xee\xee\xee\xee", # "\xff\xff\xff\xff\xff\xff", # }; # uint8_t buffer[6]; # # // keep track of the worst case log size # lfs_size_t worst_size = 0; # # // test all permutations of a given size # uint16_t perm[N]; # unsigned stack[N]; # for (uint16_t i = 0; i < N; i++) { # perm[i] = i; # stack[i] = 0; # } # # unsigned i = 1; # while (i < N) { # // print permutation to help debugging # printf("--- permutation: ["); # for (unsigned j = 0; j < N; j++) { # if (j > 0) { # printf(", "); # } # printf("%d", perm[j]+1); # } # printf("] ---\n"); # # // create given permutation with multiple commits # rbyd = init_rbyd; # lfs_bd_erase(&lfs, rbyd.block) => 0; # # for (unsigned j = 0; j < N; j++) { # // adjust id based on future insertions # uint16_t id = perm[j]; # for (unsigned k = j+1; k < N; k++) { # if (perm[j] > perm[k]) { # id -= 1; # } # } # # lfsr_rbyd_commit(&lfs, &rbyd, # LFSR_ATTR(MKREG, id, names[perm[j] % 6], 4, # NULL)) => 0; # } # assert(rbyd.weight == N); # # // copy block so we can reset after each delete # lfsr_rbyd_t backup_rbyd = rbyd; # uint8_t backup_block[BLOCK_SIZE]; # lfs_bd_read(&lfs, NULL, &lfs.rcache, rbyd.off, # rbyd.block, 0, backup_block, rbyd.off) => 0; # # // try deleting each id # for (unsigned j = 0; j < N; j++) { # for (unsigned l = 0; l < N; l++) { # // print what we are deleting to help debugging # printf("--- delete: %d, create: %d ---\n", j+1, l+1); # # rbyd = backup_rbyd; # lfs_bd_erase(&lfs, rbyd.block) => 0; # lfs_bd_prog(&lfs, &lfs.pcache, &lfs.rcache, false, # rbyd.block, 0, backup_block, rbyd.off) => 0; # lfs_bd_flush(&lfs, &lfs.pcache, &lfs.rcache, false) => 0; # # // delete # lfsr_rbyd_commit(&lfs, &rbyd, # LFSR_ATTR(RM, j, NULL, 0, # NULL)) => 0; # assert(rbyd.weight == N-1); # # // try creating each tag to make sure the rbyd tree # // is still usable # lfsr_rbyd_commit(&lfs, &rbyd, # LFSR_ATTR(MKREG, l, names[l % 6], 6, # NULL)) => 0; # assert(rbyd.weight == N); # # lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, # cfg->block_size, NULL) => 0; # for (unsigned k = 0; k < N; k++) { # lfs_ssize_t size = lfsr_rbyd_get(&lfs, &rbyd, # LFSR_TAG_MKREG, k, buffer, 6); # if (k == l) { # assert(size == 6); # assert(memcmp(buffer, names[l % 6], 6) == 0); # } else { # uint16_t expected = k; # if (expected > l) { # expected -= 1; # } # if (expected >= j) { # expected += 1; # } # assert(size == 4); # assert(memcmp(buffer, names[expected % 6], 4) == 0); # } # } # # // keep track of the worst size # worst_size = lfs_max(worst_size, rbyd.off); # } # } # # // next permutation using Heap's algorithm # if (stack[i] < i) { # if (i % 2 == 0) { # uint16_t t = perm[0]; # perm[0] = perm[i]; # perm[i] = t; # } else { # uint16_t t = perm[stack[i]]; # perm[stack[i]] = perm[i]; # perm[i] = t; # } # stack[i] += 1; # i = 1; # } else { # stack[i] = 0; # i += 1; # } # } # # // test that tree is self-balancing, we should be strictly bounded # // by height <= 2*log(n)+1, assume tags are strictly <=12 bytes # lfs_size_t n = 1 + N + 1 + 1; # printf("worst size: %u B (N=%u, estimate=%u)\n", # worst_size, n, 12*n*(2*lfs_nlog2(n)+1)+4); # printf("avg height: %u B (N=%u, estimate=%u)\n", # worst_size / n, n, 12*(2*lfs_nlog2(n)+1)+4); # // note this only holds true with byte-level progs # if (PROG_SIZE == 1) { # assert(worst_size / n <= 12*(2*lfs_nlog2(n)+1)+4); # } #''' # #[cases.test_rbyd_delete_create_range_permutations] #defines.N = 'range(1, 6)' #defines.M = 'range(1, 4)' #in = 'lfs.c' #if = ''' # BLOCK_SIZE/PROG_SIZE >= N+N*M + 1 + 1+M # && BLOCK_SIZE >= 4096 #''' #code = ''' # lfs_t lfs; # lfs_init(&lfs, cfg) => 0; # # lfsr_rbyd_t init_rbyd = { # .block = 0, # .rev = 1, # .off = 0, # .crc = 0, # .trunk = 0, # .weight = 0, # .erased = true, # }; # lfsr_rbyd_t rbyd; # const uint8_t names[6][6] = { # "\xaa\xaa\xaa\xaa\xaa\xaa", # "\xbb\xbb\xbb\xbb\xbb\xbb", # "\xcc\xcc\xcc\xcc\xcc\xcc", # "\xdd\xdd\xdd\xdd\xdd\xdd", # "\xee\xee\xee\xee\xee\xee", # "\xff\xff\xff\xff\xff\xff", # }; # uint8_t buffer[6]; # # // keep track of the worst case log size # lfs_size_t worst_size = 0; # # // test all permutations of a given size # uint16_t perm[N]; # unsigned stack[N]; # for (uint16_t i = 0; i < N; i++) { # perm[i] = i; # stack[i] = 0; # } # # unsigned i = 1; # while (i < N) { # // print permutation to help debugging # printf("--- permutation: ["); # for (unsigned j = 0; j < N; j++) { # if (j > 0) { # printf(", "); # } # printf("%d", perm[j]+1); # } # printf("] ---\n"); # # // create given permutation with multiple commits # rbyd = init_rbyd; # lfs_bd_erase(&lfs, rbyd.block) => 0; # # for (unsigned j = 0; j < N; j++) { # // adjust id based on future insertions # uint16_t id = perm[j]; # for (unsigned k = j+1; k < N; k++) { # if (perm[j] > perm[k]) { # id -= 1; # } # } # # lfsr_rbyd_commit(&lfs, &rbyd, # LFSR_ATTR(MKREG, id, names[perm[j] % 6], 4, # NULL)) => 0; # // note uattrs have a smaller size to help debugging # for (unsigned u = 0; u < M; u++) { # lfsr_rbyd_commit(&lfs, &rbyd, # LFSR_ATTR(UATTR(u+1), id, names[perm[j] % 6], 2, # NULL)) => 0; # } # } # assert(rbyd.weight == N); # # // copy block so we can reset after each delete # lfsr_rbyd_t backup_rbyd = rbyd; # uint8_t backup_block[BLOCK_SIZE]; # lfs_bd_read(&lfs, NULL, &lfs.rcache, rbyd.off, # rbyd.block, 0, backup_block, rbyd.off) => 0; # # // try deleting each id # for (unsigned j = 0; j < N; j++) { # for (unsigned l = 0; l < N; l++) { # // print what we are deleting to help debugging # printf("--- delete: %d, create: %d ---\n", j+1, l+1); # # rbyd = backup_rbyd; # lfs_bd_erase(&lfs, rbyd.block) => 0; # lfs_bd_prog(&lfs, &lfs.pcache, &lfs.rcache, false, # rbyd.block, 0, backup_block, rbyd.off) => 0; # lfs_bd_flush(&lfs, &lfs.pcache, &lfs.rcache, false) => 0; # # // delete # lfsr_rbyd_commit(&lfs, &rbyd, # LFSR_ATTR(RM, j, NULL, 0, # NULL)) => 0; # assert(rbyd.weight == N-1); # # // try creating each tag to make sure the rbyd tree # // is still usable # lfsr_rbyd_commit(&lfs, &rbyd, # LFSR_ATTR(MKREG, l, names[l % 6], 6, # NULL)) => 0; # for (unsigned u = 0; u < M; u++) { # lfsr_rbyd_commit(&lfs, &rbyd, # LFSR_ATTR(UATTR(u+1), l, names[l % 6], 3, # NULL)) => 0; # } # assert(rbyd.weight == N); # # lfsr_rbyd_fetch(&lfs, &rbyd, rbyd.block, # cfg->block_size, NULL) => 0; # for (unsigned k = 0; k < N; k++) { # lfs_ssize_t size = lfsr_rbyd_get(&lfs, &rbyd, # LFSR_TAG_MKREG, k, buffer, 6); # if (k == l) { # assert(size == 6); # assert(memcmp(buffer, names[l % 6], 6) == 0); # } else { # uint16_t expected = k; # if (expected > l) { # expected -= 1; # } # if (expected >= j) { # expected += 1; # } # assert(size == 4); # assert(memcmp(buffer, names[expected % 6], 4) == 0); # } # # for (unsigned u = 0; u < M; u++) { # size = lfsr_rbyd_get(&lfs, &rbyd, # LFSR_TAG_UATTR(u+1), k, buffer, 6); # if (k == l) { # assert(size == 3); # assert(memcmp(buffer, names[l % 6], 3) == 0); # } else { # uint16_t expected = k; # if (expected > l) { # expected -= 1; # } # if (expected >= j) { # expected += 1; # } # assert(size == 2); # assert(memcmp(buffer, names[expected % 6], 2) == 0); # } # } # } # # // keep track of the worst size # worst_size = lfs_max(worst_size, rbyd.off); # } # } # # // next permutation using Heap's algorithm # if (stack[i] < i) { # if (i % 2 == 0) { # uint16_t t = perm[0]; # perm[0] = perm[i]; # perm[i] = t; # } else { # uint16_t t = perm[stack[i]]; # perm[stack[i]] = perm[i]; # perm[i] = t; # } # stack[i] += 1; # i = 1; # } else { # stack[i] = 0; # i += 1; # } # } # # // test that tree is self-balancing, we should be strictly bounded # // by height <= 2*log(n)+1, assume tags are strictly <=12 bytes # lfs_size_t n = 1 + N+N*M + 1 + 1+M; # printf("worst size: %u B (N=%u, estimate=%u)\n", # worst_size, n, 12*n*(2*lfs_nlog2(n)+1)+4); # printf("avg height: %u B (N=%u, estimate=%u)\n", # worst_size / n, n, 12*(2*lfs_nlog2(n)+1)+4); # // note this only holds true with byte-level progs # if (PROG_SIZE == 1) { # assert(worst_size / n <= 12*(2*lfs_nlog2(n)+1)+4); # } #''' # the main purpose of this test is to try to fuzz for failures in the # balancing algorithm [cases.test_rbyd_random_create_deletes] defines.N = 'range(1, 33)' defines.ITER = 1000 in = 'lfs.c' code = ''' lfs_t lfs; lfs_init(&lfs, cfg) => 0; lfsr_rbyd_t init_rbyd = { .block = 0, .rev = 1, .off = 0, .crc = 0, .trunk = 0, .weight = 0, .erased = true, }; lfsr_rbyd_t rbyd; const char *alpha = "abcdefghijklmnopqrstuvwxyz"; uint8_t buffer[4]; // keep track of the worst case size and seed lfs_size_t worst_size = 0; uint32_t worst_seed = 0; // iterate through seeds so we can reproduce easily for (uint32_t seed = 1; seed < ITER+1; seed++) { printf("--- seed: %d ---\n", seed); printf("perm: ["); uint32_t prng = seed; lfs_size_t count = 0; for (unsigned i = 0; i < N; i++) { // choose an id lfsr_sid_t id = TEST_PRNG(&prng) % (count+1); // choose create or delete if (id == (lfsr_sid_t)count || (TEST_PRNG(&prng) & 1)) { printf("c%d=%c", id, alpha[i % 26]); count += 1; } else { printf("d%d", id); count -= 1; } if (i < N-1) { printf(", "); } } printf("]\n"); // set up a simulation to compare against, fun fact this performs // worst than our actual rbyd block! char *sim = malloc(N); memset(sim, 0, N); // set up rbyd block rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; prng = seed; count = 0; for (unsigned i = 0; i < N; i++) { // choose an id lfsr_sid_t id = TEST_PRNG(&prng) % (count+1); // choose create or delete if (id == (lfsr_sid_t)count || (TEST_PRNG(&prng) & 1)) { // update our sim memmove(sim+id+1, sim+id, count-id); sim[id] = alpha[i % 26]; count += 1; // update our rbyd lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, id, &alpha[i % 26], 1, NULL)) => 0; } else { // update our sim memmove(sim+id, sim+id+1, count-id-1); count -= 1; // update our rbyd lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RM, id, NULL, 0, NULL)) => 0; } } // compare rbyd vs simulation printf("expd: ["); for (lfsr_sid_t id = 0; id < (lfsr_sid_t)count; id++) { printf("%c", sim[id]); if (id < (lfsr_sid_t)count-1) { printf(", "); } } printf("]\n"); printf("rbyd: ["); for (lfsr_sid_t id = 0; id < (lfsr_sid_t)rbyd.weight; id++) { lfs_ssize_t size = lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, id, buffer, 4); if (size >= 0) { printf("%.*s", size, buffer); } else { printf("?"); } if (id < (lfsr_sid_t)count-1) { printf(", "); } } printf("]\n"); assert(count == rbyd.weight); for (lfsr_sid_t id = 0; id < (lfsr_sid_t)count; id++) { lfsr_rbyd_get(&lfs, &rbyd, LFSR_TAG_MKREG, id, buffer, 4) => 1; assert(memcmp(&sim[id], buffer, 1) == 0); } // keep track of the worst permutation if (rbyd.off > worst_size) { worst_size = rbyd.off; worst_seed = seed; } } // print the worst seed + size, and rerun it so it's left on the disk // if used with -ddisk printf("--- worst ---\n"); printf("worst_seed: %d\n", worst_seed); printf("worst_size: %d\n", worst_size); printf("worst_perm: ["); uint32_t prng = worst_seed; lfs_size_t count = 0; for (unsigned i = 0; i < N; i++) { // choose an id lfsr_sid_t id = TEST_PRNG(&prng) % (count+1); // choose create or delete if (id == (lfsr_sid_t)count || (TEST_PRNG(&prng) & 1)) { printf("c%d=%c", id, alpha[i % 26]); count += 1; } else { printf("d%d", id); count -= 1; } if (i < N-1) { printf(", "); } } printf("]\n"); // set up rbyd block rbyd = init_rbyd; lfs_bd_erase(&lfs, rbyd.block) => 0; prng = worst_seed; count = 0; for (unsigned i = 0; i < N; i++) { // choose an id lfsr_sid_t id = TEST_PRNG(&prng) % (count+1); // choose create or delete if (id == (lfsr_sid_t)count || (TEST_PRNG(&prng) & 1)) { count += 1; // update our rbyd lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(MKREG, id, &alpha[i % 26], 1, NULL)) => 0; } else { count -= 1; // update our rbyd lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTR(RM, id, NULL, 0, NULL)) => 0; } } // our tree should be strictly <= 2*log(n)+1, assume tags are strictly // <=12 bytes, note this only holds true with byte-level progs if (PROG_SIZE == 1) { assert(worst_size <= N*12*(2*lfs_nlog2(N)+1)+1); } '''