| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * Copyright (C) 2026 Meta. All rights reserved.
- */
- #include <linux/sizes.h>
- #include "btrfs-tests.h"
- #include "../volumes.h"
- #include "../disk-io.h"
- #include "../extent-io-tree.h"
- /*
- * Tests for chunk allocator pending extent internals.
- * These two functions form the core of searching the chunk allocation pending
- * extent bitmap and have relatively easily definable semantics, so unit
- * testing them can help ensure the correctness of chunk allocation.
- */
- /*
- * Describes the inputs to the system and expected results
- * when testing btrfs_find_hole_in_pending_extents().
- */
- struct pending_extent_test_case {
- const char *name;
- /* Input range to search. */
- u64 hole_start;
- u64 hole_len;
- /* The size of hole we are searching for. */
- u64 min_hole_size;
- /*
- * Pending extents to set up (up to 2 for up to 3 holes)
- * If len == 0, then it is skipped.
- */
- struct {
- u64 start;
- u64 len;
- } pending_extents[2];
- /* Expected outputs. */
- bool expected_found;
- u64 expected_start;
- u64 expected_len;
- };
- static const struct pending_extent_test_case find_hole_tests[] = {
- {
- .name = "no pending extents",
- .hole_start = 0,
- .hole_len = 10ULL * SZ_1G,
- .min_hole_size = SZ_1G,
- .pending_extents = { },
- .expected_found = true,
- .expected_start = 0,
- .expected_len = 10ULL * SZ_1G,
- },
- {
- .name = "pending extent at start of range",
- .hole_start = 0,
- .hole_len = 10ULL * SZ_1G,
- .min_hole_size = SZ_1G,
- .pending_extents = {
- { .start = 0, .len = SZ_1G },
- },
- .expected_found = true,
- .expected_start = SZ_1G,
- .expected_len = 9ULL * SZ_1G,
- },
- {
- .name = "pending extent overlapping start of range",
- .hole_start = SZ_1G,
- .hole_len = 9ULL * SZ_1G,
- .min_hole_size = SZ_1G,
- .pending_extents = {
- { .start = 0, .len = SZ_2G },
- },
- .expected_found = true,
- .expected_start = SZ_2G,
- .expected_len = 8ULL * SZ_1G,
- },
- {
- .name = "two holes; first hole is exactly big enough",
- .hole_start = 0,
- .hole_len = 10ULL * SZ_1G,
- .min_hole_size = SZ_1G,
- .pending_extents = {
- { .start = SZ_1G, .len = SZ_1G },
- },
- .expected_found = true,
- .expected_start = 0,
- .expected_len = SZ_1G,
- },
- {
- .name = "two holes; first hole is big enough",
- .hole_start = 0,
- .hole_len = 10ULL * SZ_1G,
- .min_hole_size = SZ_1G,
- .pending_extents = {
- { .start = SZ_2G, .len = SZ_1G },
- },
- .expected_found = true,
- .expected_start = 0,
- .expected_len = SZ_2G,
- },
- {
- .name = "two holes; second hole is big enough",
- .hole_start = 0,
- .hole_len = 10ULL * SZ_1G,
- .min_hole_size = SZ_2G,
- .pending_extents = {
- { .start = SZ_1G, .len = SZ_1G },
- },
- .expected_found = true,
- .expected_start = SZ_2G,
- .expected_len = 8ULL * SZ_1G,
- },
- {
- .name = "three holes; first hole big enough",
- .hole_start = 0,
- .hole_len = 10ULL * SZ_1G,
- .min_hole_size = SZ_2G,
- .pending_extents = {
- { .start = SZ_2G, .len = SZ_1G },
- { .start = 4ULL * SZ_1G, .len = SZ_1G },
- },
- .expected_found = true,
- .expected_start = 0,
- .expected_len = SZ_2G,
- },
- {
- .name = "three holes; second hole big enough",
- .hole_start = 0,
- .hole_len = 10ULL * SZ_1G,
- .min_hole_size = SZ_2G,
- .pending_extents = {
- { .start = SZ_1G, .len = SZ_1G },
- { .start = 5ULL * SZ_1G, .len = SZ_1G },
- },
- .expected_found = true,
- .expected_start = SZ_2G,
- .expected_len = 3ULL * SZ_1G,
- },
- {
- .name = "three holes; third hole big enough",
- .hole_start = 0,
- .hole_len = 10ULL * SZ_1G,
- .min_hole_size = SZ_2G,
- .pending_extents = {
- { .start = SZ_1G, .len = SZ_1G },
- { .start = 3ULL * SZ_1G, .len = 5ULL * SZ_1G },
- },
- .expected_found = true,
- .expected_start = 8ULL * SZ_1G,
- .expected_len = SZ_2G,
- },
- {
- .name = "three holes; all holes too small",
- .hole_start = 0,
- .hole_len = 10ULL * SZ_1G,
- .min_hole_size = SZ_2G,
- .pending_extents = {
- { .start = SZ_1G, .len = SZ_1G },
- { .start = 3ULL * SZ_1G, .len = 6ULL * SZ_1G },
- },
- .expected_found = false,
- .expected_start = 0,
- .expected_len = SZ_1G,
- },
- {
- .name = "three holes; all holes too small; first biggest",
- .hole_start = 0,
- .hole_len = 10ULL * SZ_1G,
- .min_hole_size = 3ULL * SZ_1G,
- .pending_extents = {
- { .start = SZ_2G, .len = SZ_1G },
- { .start = 4ULL * SZ_1G, .len = 5ULL * SZ_1G },
- },
- .expected_found = false,
- .expected_start = 0,
- .expected_len = SZ_2G,
- },
- {
- .name = "three holes; all holes too small; second biggest",
- .hole_start = 0,
- .hole_len = 10ULL * SZ_1G,
- .min_hole_size = 3ULL * SZ_1G,
- .pending_extents = {
- { .start = SZ_1G, .len = SZ_1G },
- { .start = 4ULL * SZ_1G, .len = 5ULL * SZ_1G },
- },
- .expected_found = false,
- .expected_start = SZ_2G,
- .expected_len = SZ_2G,
- },
- {
- .name = "three holes; all holes too small; third biggest",
- .hole_start = 0,
- .hole_len = 10ULL * SZ_1G,
- .min_hole_size = 3ULL * SZ_1G,
- .pending_extents = {
- { .start = SZ_1G, .len = SZ_1G },
- { .start = 3ULL * SZ_1G, .len = 5ULL * SZ_1G },
- },
- .expected_found = false,
- .expected_start = 8ULL * SZ_1G,
- .expected_len = SZ_2G,
- },
- {
- .name = "hole entirely allocated by pending",
- .hole_start = 0,
- .hole_len = 10ULL * SZ_1G,
- .min_hole_size = SZ_1G,
- .pending_extents = {
- { .start = 0, .len = 10ULL * SZ_1G },
- },
- .expected_found = false,
- .expected_start = 10ULL * SZ_1G,
- .expected_len = 0,
- },
- {
- .name = "pending extent at end of range",
- .hole_start = 0,
- .hole_len = 10ULL * SZ_1G,
- .min_hole_size = SZ_1G,
- .pending_extents = {
- { .start = 9ULL * SZ_1G, .len = SZ_2G },
- },
- .expected_found = true,
- .expected_start = 0,
- .expected_len = 9ULL * SZ_1G,
- },
- {
- .name = "zero length input",
- .hole_start = SZ_1G,
- .hole_len = 0,
- .min_hole_size = SZ_1G,
- .pending_extents = { },
- .expected_found = false,
- .expected_start = SZ_1G,
- .expected_len = 0,
- },
- };
- static int test_find_hole_in_pending(u32 sectorsize, u32 nodesize)
- {
- struct btrfs_fs_info *fs_info;
- struct btrfs_device *device;
- int ret = 0;
- test_msg("running find_hole_in_pending_extents tests");
- fs_info = btrfs_alloc_dummy_fs_info(nodesize, sectorsize);
- if (!fs_info) {
- test_std_err(TEST_ALLOC_FS_INFO);
- return -ENOMEM;
- }
- device = btrfs_alloc_dummy_device(fs_info);
- if (IS_ERR(device)) {
- test_err("failed to allocate dummy device");
- ret = PTR_ERR(device);
- goto out_free_fs_info;
- }
- device->fs_info = fs_info;
- for (int i = 0; i < ARRAY_SIZE(find_hole_tests); i++) {
- const struct pending_extent_test_case *test_case = &find_hole_tests[i];
- u64 hole_start = test_case->hole_start;
- u64 hole_len = test_case->hole_len;
- bool found;
- for (int j = 0; j < ARRAY_SIZE(test_case->pending_extents); j++) {
- u64 start = test_case->pending_extents[j].start;
- u64 len = test_case->pending_extents[j].len;
- if (!len)
- continue;
- btrfs_set_extent_bit(&device->alloc_state,
- start, start + len - 1,
- CHUNK_ALLOCATED, NULL);
- }
- mutex_lock(&fs_info->chunk_mutex);
- found = btrfs_find_hole_in_pending_extents(device, &hole_start, &hole_len,
- test_case->min_hole_size);
- mutex_unlock(&fs_info->chunk_mutex);
- if (found != test_case->expected_found) {
- test_err("%s: expected found=%d, got found=%d",
- test_case->name, test_case->expected_found, found);
- ret = -EINVAL;
- goto out_clear_pending_extents;
- }
- if (hole_start != test_case->expected_start ||
- hole_len != test_case->expected_len) {
- test_err("%s: expected [%llu, %llu), got [%llu, %llu)",
- test_case->name, test_case->expected_start,
- test_case->expected_start +
- test_case->expected_len,
- hole_start, hole_start + hole_len);
- ret = -EINVAL;
- goto out_clear_pending_extents;
- }
- out_clear_pending_extents:
- btrfs_clear_extent_bit(&device->alloc_state, 0, (u64)-1,
- CHUNK_ALLOCATED, NULL);
- if (ret)
- break;
- }
- out_free_fs_info:
- btrfs_free_dummy_fs_info(fs_info);
- return ret;
- }
- /*
- * Describes the inputs to the system and expected results
- * when testing btrfs_first_pending_extent().
- */
- struct first_pending_test_case {
- const char *name;
- /* The range to look for a pending extent in. */
- u64 hole_start;
- u64 hole_len;
- /* The pending extent to look for. */
- struct {
- u64 start;
- u64 len;
- } pending_extent;
- /* Expected outputs. */
- bool expected_found;
- u64 expected_pending_start;
- u64 expected_pending_end;
- };
- static const struct first_pending_test_case first_pending_tests[] = {
- {
- .name = "no pending extent",
- .hole_start = 0,
- .hole_len = 10ULL * SZ_1G,
- .pending_extent = { 0, 0 },
- .expected_found = false,
- },
- {
- .name = "pending extent at search start",
- .hole_start = SZ_1G,
- .hole_len = 9ULL * SZ_1G,
- .pending_extent = { SZ_1G, SZ_1G },
- .expected_found = true,
- .expected_pending_start = SZ_1G,
- .expected_pending_end = SZ_2G - 1,
- },
- {
- .name = "pending extent overlapping search start",
- .hole_start = SZ_1G,
- .hole_len = 9ULL * SZ_1G,
- .pending_extent = { 0, SZ_2G },
- .expected_found = true,
- .expected_pending_start = 0,
- .expected_pending_end = SZ_2G - 1,
- },
- {
- .name = "pending extent inside search range",
- .hole_start = 0,
- .hole_len = 10ULL * SZ_1G,
- .pending_extent = { SZ_2G, SZ_1G },
- .expected_found = true,
- .expected_pending_start = SZ_2G,
- .expected_pending_end = 3ULL * SZ_1G - 1,
- },
- {
- .name = "pending extent outside search range",
- .hole_start = 0,
- .hole_len = SZ_1G,
- .pending_extent = { SZ_2G, SZ_1G },
- .expected_found = false,
- },
- {
- .name = "pending extent overlapping end of search range",
- .hole_start = 0,
- .hole_len = SZ_2G,
- .pending_extent = { SZ_1G, SZ_2G },
- .expected_found = true,
- .expected_pending_start = SZ_1G,
- .expected_pending_end = 3ULL * SZ_1G - 1,
- },
- };
- static int test_first_pending_extent(u32 sectorsize, u32 nodesize)
- {
- struct btrfs_fs_info *fs_info;
- struct btrfs_device *device;
- int ret = 0;
- test_msg("running first_pending_extent tests");
- fs_info = btrfs_alloc_dummy_fs_info(nodesize, sectorsize);
- if (!fs_info) {
- test_std_err(TEST_ALLOC_FS_INFO);
- return -ENOMEM;
- }
- device = btrfs_alloc_dummy_device(fs_info);
- if (IS_ERR(device)) {
- test_err("failed to allocate dummy device");
- ret = PTR_ERR(device);
- goto out_free_fs_info;
- }
- device->fs_info = fs_info;
- for (int i = 0; i < ARRAY_SIZE(first_pending_tests); i++) {
- const struct first_pending_test_case *test_case = &first_pending_tests[i];
- u64 start = test_case->pending_extent.start;
- u64 len = test_case->pending_extent.len;
- u64 pending_start, pending_end;
- bool found;
- if (len) {
- btrfs_set_extent_bit(&device->alloc_state,
- start, start + len - 1,
- CHUNK_ALLOCATED, NULL);
- }
- mutex_lock(&fs_info->chunk_mutex);
- found = btrfs_first_pending_extent(device, test_case->hole_start,
- test_case->hole_len,
- &pending_start, &pending_end);
- mutex_unlock(&fs_info->chunk_mutex);
- if (found != test_case->expected_found) {
- test_err("%s: expected found=%d, got found=%d",
- test_case->name, test_case->expected_found, found);
- ret = -EINVAL;
- goto out_clear_pending_extents;
- }
- if (!found)
- goto out_clear_pending_extents;
- if (pending_start != test_case->expected_pending_start ||
- pending_end != test_case->expected_pending_end) {
- test_err("%s: expected pending [%llu, %llu], got [%llu, %llu]",
- test_case->name,
- test_case->expected_pending_start,
- test_case->expected_pending_end,
- pending_start, pending_end);
- ret = -EINVAL;
- goto out_clear_pending_extents;
- }
- out_clear_pending_extents:
- btrfs_clear_extent_bit(&device->alloc_state, 0, (u64)-1,
- CHUNK_ALLOCATED, NULL);
- if (ret)
- break;
- }
- out_free_fs_info:
- btrfs_free_dummy_fs_info(fs_info);
- return ret;
- }
- int btrfs_test_chunk_allocation(u32 sectorsize, u32 nodesize)
- {
- int ret;
- test_msg("running chunk allocation tests");
- ret = test_first_pending_extent(sectorsize, nodesize);
- if (ret)
- return ret;
- ret = test_find_hole_in_pending(sectorsize, nodesize);
- if (ret)
- return ret;
- return 0;
- }
|