From 0cae5a18146ad3d0157d736a44bd8b72ab526eee Mon Sep 17 00:00:00 2001 From: Alberto Miranda Date: Mon, 24 Jul 2023 10:39:21 +0200 Subject: [PATCH] Add missing file `posix_file_tests.cpp` --- tests/posix_file_tests.cpp | 3194 ++++++++++++++++++++++++++++++++++++ 1 file changed, 3194 insertions(+) create mode 100644 tests/posix_file_tests.cpp diff --git a/tests/posix_file_tests.cpp b/tests/posix_file_tests.cpp new file mode 100644 index 0000000..55265a1 --- /dev/null +++ b/tests/posix_file_tests.cpp @@ -0,0 +1,3194 @@ +/****************************************************************************** + * Copyright 2021-2023, Barcelona Supercomputing Center (BSC), Spain + * + * This software was partially supported by the EuroHPC-funded project ADMIRE + * (Project ID: 956748, https://www.admire-eurohpc.eu). + * + * This file is part of scord. + * + * scord is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * scord is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with scord. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + *****************************************************************************/ + +#include +#include +#define POSIX_FILE_HAVE_FMT +#include +#include +#include +#include +#include "catch2/generators/catch_generators_range.hpp" + +using posix_file::views::all_of; +using posix_file::views::as_blocks; +using posix_file::views::some_of; +using posix_file::views::strided; + +namespace Catch { +template <> +struct StringMaker { + static std::string + convert(const posix_file::ranges::range& r) { + return fmt::format("{}", r); + } +}; +} // namespace Catch + +struct scoped_file : public posix_file::file { + + explicit scoped_file(std::filesystem::path filepath) + : posix_file::file(std::move(filepath)) {} + + ~scoped_file() { + remove(); + } +}; + +scoped_file +create_temporary_file(std::size_t desired_size) { + + char name[] = "posix_file_tests_XXXXXX"; + posix_file::file_handle fh{mkstemp(name)}; + + if(!fh) { + fmt::print(stderr, "mkstemp() error: {}\n", strerror(errno)); + abort(); + } + + if(ftruncate(fh.native(), static_cast(desired_size)) != 0) { + fmt::print(stderr, "ftruncate() error: {}\n", strerror(errno)); + abort(); + } + + return scoped_file{std::filesystem::path{name}}; +} + +std::vector +generate_ranges(posix_file::offset start_offset, std::size_t length, + std::size_t block_size, std::size_t file_size, + unsigned int step = 1, unsigned int disp = 0) { + + assert(step != 0); + + std::size_t n = + posix_file::math::block_count(start_offset, length, block_size); + + std::vector tmp; + tmp.reserve(n); + + if(n == 0) { + return tmp; + } + + const auto block_index = + posix_file::math::block_index(start_offset, block_size); + + for(auto i = block_index; i < block_index + n; ++i) { + tmp.emplace_back(i * block_size, block_size); + } + + // filter out any ranges beyond eof + tmp.erase(std::remove_if(tmp.begin(), tmp.end(), + [eof = static_cast(file_size)]( + const posix_file::ranges::range& r) { + return r.offset() >= eof; + }), + tmp.end()); + + // adjust potential partial offset for first block + if(start_offset != 0) { + tmp.at(0) = posix_file::ranges::range{ + start_offset, block_size - posix_file::math::block_overrun( + start_offset, block_size)}; + } + + // adjust potential partial length for last block + posix_file::offset end = std::min( + static_cast(file_size), start_offset + length); + if(!posix_file::math::is_aligned(end, block_size)) { + const auto r = tmp.back(); + tmp.back() = posix_file::ranges::range{r.offset(), end - r.offset()}; + } + + std::vector out; + + for(auto i = 0u; i < tmp.size(); ++i) { + + if(i < disp) { + continue; + } + + if((i - disp) % step == 0) { + out.push_back(tmp[i]); + } + } + + return out; +} + +SCENARIO("Generating ranges", "[posix_file][generate_ranges]") { + + GIVEN("An offset range") { + WHEN("range length is 1") { + WHEN("skip is 0") { + WHEN("step is 1") { + const auto ranges = generate_ranges(0, 10, 1, 100, 1); + std::vector expected_ranges{ + {0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}, + {5, 1}, {6, 1}, {7, 1}, {8, 1}, {9, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is 2") { + const auto ranges = generate_ranges(0, 10, 1, 100, 2); + std::vector expected_ranges{ + {0, 1}, + {2, 1}, + {4, 1}, + {6, 1}, + {8, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is 3") { + const auto ranges = generate_ranges(0, 10, 1, 100, 3); + std::vector expected_ranges{ + {0, 1}, + {3, 1}, + {6, 1}, + {9, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is 4") { + const auto ranges = generate_ranges(0, 10, 1, 100, 4); + std::vector expected_ranges{ + {0, 1}, + {4, 1}, + {8, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is 5") { + const auto ranges = generate_ranges(0, 10, 1, 100, 5); + std::vector expected_ranges{ + {0, 1}, + {5, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is 6") { + const auto ranges = generate_ranges(0, 10, 1, 100, 6); + std::vector expected_ranges{ + {0, 1}, + {6, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is 7") { + const auto ranges = generate_ranges(0, 10, 1, 100, 7); + std::vector expected_ranges{ + {0, 1}, + {7, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is 8") { + const auto ranges = generate_ranges(0, 10, 1, 100, 8); + std::vector expected_ranges{ + {0, 1}, + {8, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is 9") { + const auto ranges = generate_ranges(0, 10, 1, 100, 9); + std::vector expected_ranges{ + {0, 1}, + {9, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is >9") { + const int step = + GENERATE(Catch::Generators::range(10, 100)); + const auto ranges = generate_ranges(0, 10, 1, 100, step); + std::vector expected_ranges{ + {0, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + } + + WHEN("skip is 1") { + WHEN("step is 1") { + const auto ranges = generate_ranges(0, 10, 1, 100, 1, 1); + std::vector expected_ranges{ + {1, 1}, {2, 1}, {3, 1}, {4, 1}, {5, 1}, + {6, 1}, {7, 1}, {8, 1}, {9, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is 2") { + const auto ranges = generate_ranges(0, 10, 1, 100, 2, 1); + std::vector expected_ranges{ + {1, 1}, + {3, 1}, + {5, 1}, + {7, 1}, + {9, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is 3") { + const auto ranges = generate_ranges(0, 10, 1, 100, 3, 1); + std::vector expected_ranges{ + {1, 1}, + {4, 1}, + {7, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is 4") { + const auto ranges = generate_ranges(0, 10, 1, 100, 4, 1); + std::vector expected_ranges{ + {1, 1}, + {5, 1}, + {9, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is 5") { + const auto ranges = generate_ranges(0, 10, 1, 100, 5, 1); + std::vector expected_ranges{ + {1, 1}, + {6, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is 6") { + const auto ranges = generate_ranges(0, 10, 1, 100, 6, 1); + std::vector expected_ranges{ + {1, 1}, + {7, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is 7") { + const auto ranges = generate_ranges(0, 10, 1, 100, 7, 1); + std::vector expected_ranges{ + {1, 1}, + {8, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is 8") { + const auto ranges = generate_ranges(0, 10, 1, 100, 8, 1); + std::vector expected_ranges{ + {1, 1}, + {9, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is >8") { + const int step = GENERATE(Catch::Generators::range(9, 100)); + const auto ranges = generate_ranges(0, 10, 1, 100, step, 1); + std::vector expected_ranges{ + {1, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + } + + WHEN("skip is 2") { + WHEN("step is 1") { + const auto ranges = generate_ranges(0, 10, 1, 100, 1, 2); + std::vector expected_ranges{ + {2, 1}, {3, 1}, {4, 1}, {5, 1}, + {6, 1}, {7, 1}, {8, 1}, {9, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is 2") { + const auto ranges = generate_ranges(0, 10, 1, 100, 2, 2); + std::vector expected_ranges{ + {2, 1}, + {4, 1}, + {6, 1}, + {8, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is 3") { + const auto ranges = generate_ranges(0, 10, 1, 100, 3, 2); + std::vector expected_ranges{ + {2, 1}, + {5, 1}, + {8, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is 4") { + const auto ranges = generate_ranges(0, 10, 1, 100, 4, 2); + std::vector expected_ranges{ + {2, 1}, + {6, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is 5") { + const auto ranges = generate_ranges(0, 10, 1, 100, 5, 2); + std::vector expected_ranges{ + {2, 1}, + {7, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is 6") { + const auto ranges = generate_ranges(0, 10, 1, 100, 6, 2); + std::vector expected_ranges{ + {2, 1}, + {8, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is 7") { + const auto ranges = generate_ranges(0, 10, 1, 100, 7, 2); + std::vector expected_ranges{ + {2, 1}, + {9, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is >7") { + const int step = GENERATE(Catch::Generators::range(8, 100)); + const auto ranges = generate_ranges(0, 10, 1, 100, step, 2); + std::vector expected_ranges{ + {2, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + } + + WHEN("skip is 3") { + WHEN("step is 1") { + const auto ranges = generate_ranges(0, 10, 1, 100, 1, 3); + std::vector expected_ranges{ + {3, 1}, {4, 1}, {5, 1}, {6, 1}, + {7, 1}, {8, 1}, {9, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is 2") { + const auto ranges = generate_ranges(0, 10, 1, 100, 2, 3); + std::vector expected_ranges{ + {3, 1}, + {5, 1}, + {7, 1}, + {9, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is 3") { + const auto ranges = generate_ranges(0, 10, 1, 100, 3, 3); + std::vector expected_ranges{ + {3, 1}, + {6, 1}, + {9, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is 4") { + const auto ranges = generate_ranges(0, 10, 1, 100, 4, 3); + std::vector expected_ranges{ + {3, 1}, + {7, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is 5") { + const auto ranges = generate_ranges(0, 10, 1, 100, 5, 3); + std::vector expected_ranges{ + {3, 1}, + {8, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is 6") { + const auto ranges = generate_ranges(0, 10, 1, 100, 6, 3); + std::vector expected_ranges{ + {3, 1}, + {9, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is >6") { + const int step = GENERATE(Catch::Generators::range(7, 100)); + const auto ranges = generate_ranges(0, 10, 1, 100, step, 3); + std::vector expected_ranges{ + {3, 1}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + } + + WHEN("skip is >9") { + const int disp = GENERATE(Catch::Generators::range(10, 100)); + const int step = GENERATE(Catch::Generators::range(1, 100)); + + CAPTURE(disp, step); + + const auto ranges = generate_ranges(0, 10, 1, 100, step, disp); + std::vector expected_ranges{}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + } + + WHEN("range length is 2") { + WHEN("skip is 0") { + WHEN("step is 1") { + const auto ranges = generate_ranges(0, 10, 2, 100); + std::vector expected_ranges{ + {0, 2}, + {2, 2}, + {4, 2}, + {6, 2}, + {8, 2}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is 2") { + const auto ranges = generate_ranges(0, 10, 2, 100, 2); + std::vector expected_ranges{ + {0, 2}, + {4, 2}, + {8, 2}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is 3") { + const auto ranges = generate_ranges(0, 10, 2, 100, 3); + std::vector expected_ranges{ + {0, 2}, + {6, 2}, + }; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is 4") { + const auto ranges = generate_ranges(0, 10, 2, 100, 4); + std::vector expected_ranges{ + {0, 2}, + {8, 2}, + }; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is >4") { + const int step = GENERATE(Catch::Generators::range(5, 100)); + const auto ranges = generate_ranges(0, 10, 2, 100, step); + std::vector expected_ranges{ + {0, 2}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + } + + WHEN("skip is 1") { + WHEN("step is 1") { + const auto ranges = generate_ranges(0, 10, 2, 100, 1, 1); + std::vector expected_ranges{ + {2, 2}, + {4, 2}, + {6, 2}, + {8, 2}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is 2") { + const auto ranges = generate_ranges(0, 10, 2, 100, 2, 1); + std::vector expected_ranges{ + {2, 2}, + {6, 2}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is 3") { + const auto ranges = generate_ranges(0, 10, 2, 100, 3, 1); + std::vector expected_ranges{ + {2, 2}, + {8, 2}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("step is >3") { + const int step = GENERATE(Catch::Generators::range(4, 100)); + const auto ranges = generate_ranges(0, 10, 2, 100, step, 1); + std::vector expected_ranges{ + {2, 2}}; + + THEN("Generated ranges are correct") { + REQUIRE(ranges == expected_ranges); + } + } + } + } + } +} + +SCENARIO("Iterating over a file in byte units", + "[posix_file][views][all_of][as_bytes]") { + + const std::size_t block_size = 1; + CAPTURE(block_size); + + GIVEN("An empty file") { + + auto f = create_temporary_file(0); + + WHEN("Iterating over the file range") { + std::vector ranges; + for(const auto& r : all_of(f)) { + ranges.emplace_back(r, 1); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + } + + GIVEN("A non-empty file larger than a byte") { + + const auto file_size = 2000u; + auto f = create_temporary_file(file_size); + + CAPTURE(file_size); + + WHEN("Iterating over the file range") { + std::vector ranges; + for(const auto& r : all_of(f)) { + ranges.emplace_back(r, 1); + } + + THEN("Ranges for each byte are returned") { + const auto expected_ranges = + generate_ranges(0, file_size, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + } +} + +SCENARIO("Iterating over a file region in byte units", + "[posix_file][views][some_of][as_bytes]") { + + const std::size_t block_size = 1; + CAPTURE(block_size); + + GIVEN("An empty file") { + + auto f = create_temporary_file(0); + + WHEN("The region starts at the beginning of the file") { + std::vector ranges; + + for(const auto& r : some_of(f, 0, 10)) { + ranges.emplace_back(r, 1); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + + WHEN("The region starts in the middle of the file") { + std::vector ranges; + + for(const auto& r : some_of(f, 10, 10)) { + ranges.emplace_back(r, 1); + } + + CAPTURE(ranges); + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + } + + GIVEN("A non-empty file larger than a byte") { + + const auto file_size = 2000u; + auto f = create_temporary_file(file_size); + CAPTURE(file_size); + + WHEN("The region's length is 0") { + std::vector ranges; + for(const auto& r : some_of(f, 0, 0)) { + ranges.emplace_back(r, 1); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + + WHEN("The region's length is not 0") { + WHEN("The region starts at the beginning of the file") { + std::vector ranges; + + posix_file::offset offset = 0; + std::size_t length = 10; + CAPTURE(offset, length); + + for(const auto& r : some_of(f, offset, length)) { + ranges.emplace_back(r, 1); + } + + THEN("Ranges for each byte are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("The region starts in the middle of the file") { + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : some_of(f, offset, length)) { + ranges.emplace_back(r, 1); + } + + THEN("Ranges for each byte are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + + WHEN("The region starts at the end of the file") { + std::vector ranges; + + posix_file::offset offset = file_size; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : some_of(f, offset, length)) { + ranges.emplace_back(r, 1); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + } + } +} + +SCENARIO("Iterating over a file in block units", + "[posix_file][views][all_of][as_blocks]") { + + const std::size_t block_size = 512; + CAPTURE(block_size); + + GIVEN("An empty file") { + + auto f = create_temporary_file(0); + + WHEN("Iterating over the file range") { + std::vector ranges; + for(const auto& r : + all_of(f) | posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + } + + GIVEN("A non-empty file larger than a block") { + + const auto file_size = static_cast(block_size * 1.5); + auto f = create_temporary_file(file_size); + CAPTURE(file_size); + + WHEN("Iterating over the file range") { + std::vector ranges; + for(const auto& r : + all_of(f) | posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(0, file_size, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + } + + GIVEN("A non-empty file smaller than a block") { + + const auto file_size = static_cast(block_size / 1.5); + CAPTURE(file_size); + + auto f = create_temporary_file(file_size); + + WHEN("Iterating over the file range") { + std::vector ranges; + for(const auto& r : + all_of(f) | posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(0, file_size, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + } + + GIVEN("A non-empty file of exactly one block") { + + const auto file_size = static_cast(block_size); + CAPTURE(file_size); + + auto f = create_temporary_file(file_size); + + WHEN("Iterating over the file range") { + std::vector ranges; + for(const auto& r : + all_of(f) | posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(0, file_size, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + } + + GIVEN("A non-empty file of exactly n blocks") { + + WHEN("n is a power of 2") { + const auto n = 8; + const auto file_size = static_cast(n * block_size); + CAPTURE(file_size); + + auto f = create_temporary_file(file_size); + + WHEN("Iterating over the file range") { + std::vector ranges; + for(const auto& r : + all_of(f) | posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + 0, file_size, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + } + + WHEN("n is not a power of 2") { + const auto n = 11; + const auto file_size = static_cast(n * block_size); + CAPTURE(file_size); + + auto f = create_temporary_file(file_size); + + WHEN("Iterating over the file range") { + std::vector ranges; + for(const auto& r : + all_of(f) | posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + 0, file_size, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + } + } +} + +SCENARIO("Iterating over a file region in block units", + "[posix_file][views][some_of][as_blocks]") { + + const std::size_t block_size = 512; + CAPTURE(block_size); + + GIVEN("An empty file") { + + auto f = create_temporary_file(0); + + WHEN("The region starts at the beginning of the file") { + std::vector ranges; + for(const auto& r : + some_of(f, 0, 10) | posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + + WHEN("The region starts in the middle of the file") { + std::vector ranges; + for(const auto& r : + some_of(f, 0, 10) | posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + } + + GIVEN("A non-empty file larger than a block") { + + const auto file_size = static_cast(block_size * 1.5); + auto f = create_temporary_file(file_size); + CAPTURE(file_size); + + WHEN("The region's length is 0") { + std::vector ranges; + + posix_file::offset offset = 0; + std::size_t length = 0; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + + WHEN("The region's length is not 0") { + AND_WHEN("The region starts at the beginning of the file") { + AND_WHEN("The region does not end at a block boundary") { + std::vector ranges; + + posix_file::offset offset = 0; + std::size_t length = 10; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends at a block boundary") { + std::vector ranges; + + posix_file::offset offset = 0; + std::size_t length = 3.5 * block_size; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + } + + AND_WHEN("The region starts in the middle of the file") { + AND_WHEN("The region does not end at a block boundary") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends at a block boundary") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = + static_cast(3.5 * block_size) - + static_cast(offset); + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends at EOF") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = + file_size - static_cast(offset); + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends beyond EOF") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = file_size * 4; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + } + + AND_WHEN("The region starts at the end of the file") { + std::vector ranges; + + posix_file::offset offset = file_size; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + + AND_WHEN("The region starts beyond the end of the file") { + std::vector ranges; + + posix_file::offset offset = file_size + 1; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + } + } + + GIVEN("A non-empty file smaller than a block") { + + const auto file_size = static_cast(block_size / 1.5); + CAPTURE(file_size); + + auto f = create_temporary_file(file_size); + + WHEN("The region's length is 0") { + std::vector ranges; + + posix_file::offset offset = 0; + std::size_t length = 0; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + + WHEN("The region's length is not 0") { + AND_WHEN("The region starts at the beginning of the file") { + AND_WHEN("The region does not end at a block boundary") { + std::vector ranges; + + posix_file::offset offset = 0; + std::size_t length = 10; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends at a block boundary") { + std::vector ranges; + + posix_file::offset offset = 0; + std::size_t length = 3.5 * block_size; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + } + + AND_WHEN("The region starts in the middle of the file") { + AND_WHEN("The region does not end at a block boundary") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends at a block boundary") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = + static_cast(3.5 * block_size) - + static_cast(offset); + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends at EOF") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = + file_size - static_cast(offset); + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends beyond EOF") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = file_size * 4; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + } + + AND_WHEN("The region starts at the end of the file") { + std::vector ranges; + + posix_file::offset offset = file_size; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + + AND_WHEN("The region starts beyond the end of the file") { + std::vector ranges; + + posix_file::offset offset = file_size + 1; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + } + } + + GIVEN("A non-empty file of exactly one block") { + + const auto file_size = static_cast(block_size); + CAPTURE(file_size); + + auto f = create_temporary_file(file_size); + + WHEN("The region's length is 0") { + std::vector ranges; + + posix_file::offset offset = 0; + std::size_t length = 0; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + + WHEN("The region's length is not 0") { + AND_WHEN("The region starts at the beginning of the file") { + AND_WHEN("The region does not end at a block boundary") { + std::vector ranges; + + posix_file::offset offset = 0; + std::size_t length = 10; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends at a block boundary") { + std::vector ranges; + + posix_file::offset offset = 0; + std::size_t length = 3.5 * block_size; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + } + + AND_WHEN("The region starts in the middle of the file") { + AND_WHEN("The region does not end at a block boundary") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends at a block boundary") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = + static_cast(3.5 * block_size) - + static_cast(offset); + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends at EOF") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = + file_size - static_cast(offset); + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends beyond EOF") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = file_size * 4; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + } + + AND_WHEN("The region starts at the end of the file") { + std::vector ranges; + + posix_file::offset offset = file_size; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + + AND_WHEN("The region starts beyond the end of the file") { + std::vector ranges; + + posix_file::offset offset = file_size + 1; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + } + } + + GIVEN("A non-empty file of exactly n blocks") { + + WHEN("n is a power of 2") { + const auto n = 8; + const auto file_size = static_cast(n * block_size); + CAPTURE(file_size); + + auto f = create_temporary_file(file_size); + + AND_WHEN("The region's length is 0") { + std::vector ranges; + + posix_file::offset offset = 0; + std::size_t length = 0; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + + AND_WHEN("The region's length is not 0") { + AND_WHEN("The region starts at the beginning of the file") { + AND_WHEN("The region does not end at a block boundary") { + std::vector ranges; + + posix_file::offset offset = 0; + std::size_t length = 10; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends at a block boundary") { + std::vector ranges; + + posix_file::offset offset = 0; + std::size_t length = 3.5 * block_size; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + } + + AND_WHEN("The region starts in the middle of the file") { + AND_WHEN("The region does not end at a block boundary") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends at a block boundary") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = + static_cast(3.5 * block_size) - + static_cast(offset); + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends at EOF") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = + file_size - static_cast(offset); + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends beyond EOF") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = file_size * 4; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + } + + AND_WHEN("The region starts at the end of the file") { + std::vector ranges; + + posix_file::offset offset = file_size; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + + AND_WHEN("The region starts beyond the end of the file") { + std::vector ranges; + + posix_file::offset offset = file_size + 1; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + } + } + + WHEN("n is not a power of 2") { + const auto n = 11; + const auto file_size = static_cast(n * block_size); + CAPTURE(file_size); + + auto f = create_temporary_file(file_size); + + AND_WHEN("The region's length is 0") { + std::vector ranges; + + posix_file::offset offset = 0; + std::size_t length = 0; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + + AND_WHEN("The region's length is not 0") { + AND_WHEN("The region starts at the beginning of the file") { + AND_WHEN("The region does not end at a block boundary") { + std::vector ranges; + + posix_file::offset offset = 0; + std::size_t length = 10; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends at a block boundary") { + std::vector ranges; + + posix_file::offset offset = 0; + std::size_t length = 3.5 * block_size; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + } + + AND_WHEN("The region starts in the middle of the file") { + AND_WHEN("The region does not end at a block boundary") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends at a block boundary") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = + static_cast(3.5 * block_size) - + static_cast(offset); + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends at EOF") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = + file_size - static_cast(offset); + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends beyond EOF") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = file_size * 4; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + offset, length, block_size, file_size); + REQUIRE(ranges == expected_ranges); + } + } + } + + AND_WHEN("The region starts at the end of the file") { + std::vector ranges; + + posix_file::offset offset = file_size; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + + AND_WHEN("The region starts beyond the end of the file") { + std::vector ranges; + + posix_file::offset offset = file_size + 1; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + } + } + } +} + +SCENARIO("Iterating over a file in block units with stride", + "[posix_file][views][all_of][strided]") { + + const std::size_t block_size = 512; + const std::size_t step = GENERATE(Catch::Generators::range(1, 50)); + const std::size_t disp = GENERATE(Catch::Generators::range(0, 10)); + CAPTURE(block_size, step, disp); + + GIVEN("An empty file") { + + auto f = create_temporary_file(0); + + WHEN("Iterating over the file range") { + std::vector ranges; + for(const auto& r : + all_of(f) | as_blocks(block_size) | strided(step, disp)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + } + + GIVEN("A non-empty file larger than a block") { + + const auto file_size = static_cast(block_size * 1.5); + auto f = create_temporary_file(file_size); + CAPTURE(file_size); + + WHEN("Iterating over the file range") { + std::vector ranges; + for(const auto& r : + all_of(f) | as_blocks(block_size) | strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + 0, file_size, block_size, file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + } + + GIVEN("A non-empty file smaller than a block") { + + const auto file_size = static_cast(block_size / 1.5); + CAPTURE(file_size); + + auto f = create_temporary_file(file_size); + + WHEN("Iterating over the file range") { + std::vector ranges; + for(const auto& r : + all_of(f) | as_blocks(block_size) | strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + 0, file_size, block_size, file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + } + + GIVEN("A non-empty file of exactly one block") { + + const auto file_size = static_cast(block_size); + CAPTURE(file_size); + + auto f = create_temporary_file(file_size); + + WHEN("Iterating over the file range") { + std::vector ranges; + for(const auto& r : + all_of(f) | as_blocks(block_size) | strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + 0, file_size, block_size, file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + } + + GIVEN("A non-empty file of exactly n blocks") { + + WHEN("n is a power of 2") { + const auto n = 8; + const auto file_size = static_cast(n * block_size); + CAPTURE(file_size); + + auto f = create_temporary_file(file_size); + + WHEN("Iterating over the file range") { + std::vector ranges; + for(const auto& r : + all_of(f) | as_blocks(block_size) | strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + 0, file_size, block_size, file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + } + + WHEN("n is not a power of 2") { + const auto n = 11; + const auto file_size = static_cast(n * block_size); + CAPTURE(file_size); + + auto f = create_temporary_file(file_size); + + WHEN("Iterating over the file range") { + std::vector ranges; + for(const auto& r : + all_of(f) | as_blocks(block_size) | strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = generate_ranges( + 0, file_size, block_size, file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + } + } +} + +SCENARIO("Iterating over a file region in block units with stride", + "[posix_file][views][some_of][strided]") { + + const std::size_t block_size = 512; + const std::size_t step = GENERATE(Catch::Generators::range(1, 50)); + const std::size_t disp = GENERATE(Catch::Generators::range(0, 10)); + CAPTURE(block_size, step, disp); + + GIVEN("An empty file") { + + auto f = create_temporary_file(0); + + WHEN("The region starts at the beginning of the file") { + std::vector ranges; + for(const auto& r : some_of(f, 0, 10) | as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + + WHEN("The region starts in the middle of the file") { + std::vector ranges; + for(const auto& r : some_of(f, 0, 10) | as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + } + + GIVEN("A non-empty file larger than a block") { + + const auto file_size = static_cast(block_size * 1.5); + auto f = create_temporary_file(file_size); + CAPTURE(file_size); + + WHEN("The region's length is 0") { + std::vector ranges; + + posix_file::offset offset = 0; + std::size_t length = 0; + CAPTURE(offset, length); + + for(const auto& r : some_of(f, offset, length) | + as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + + WHEN("The region's length is not 0") { + AND_WHEN("The region starts at the beginning of the file") { + AND_WHEN("The region does not end at a block boundary") { + std::vector ranges; + + posix_file::offset offset = 0; + std::size_t length = 10; + CAPTURE(offset, length); + + for(const auto& r : some_of(f, offset, length) | + as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(offset, length, block_size, + file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends at a block boundary") { + std::vector ranges; + + posix_file::offset offset = 0; + std::size_t length = 3.5 * block_size; + CAPTURE(offset, length); + + for(const auto& r : some_of(f, offset, length) | + as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(offset, length, block_size, + file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + } + + AND_WHEN("The region starts in the middle of the file") { + AND_WHEN("The region does not end at a block boundary") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : some_of(f, offset, length) | + as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(offset, length, block_size, + file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends at a block boundary") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = + static_cast(3.5 * block_size) - + static_cast(offset); + CAPTURE(offset, length); + + for(const auto& r : some_of(f, offset, length) | + as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(offset, length, block_size, + file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends at EOF") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = + file_size - static_cast(offset); + CAPTURE(offset, length); + + for(const auto& r : some_of(f, offset, length) | + as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(offset, length, block_size, + file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends beyond EOF") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = file_size * 4; + CAPTURE(offset, length); + + for(const auto& r : some_of(f, offset, length) | + as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(offset, length, block_size, + file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + } + + AND_WHEN("The region starts at the end of the file") { + std::vector ranges; + + posix_file::offset offset = file_size; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : some_of(f, offset, length) | + as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + + AND_WHEN("The region starts beyond the end of the file") { + std::vector ranges; + + posix_file::offset offset = file_size + 1; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : some_of(f, offset, length) | + as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + } + } + + GIVEN("A non-empty file smaller than a block") { + + const auto file_size = static_cast(block_size / 1.5); + CAPTURE(file_size); + + auto f = create_temporary_file(file_size); + + WHEN("The region's length is 0") { + std::vector ranges; + + posix_file::offset offset = 0; + std::size_t length = 0; + CAPTURE(offset, length); + + for(const auto& r : some_of(f, offset, length) | + as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + + WHEN("The region's length is not 0") { + AND_WHEN("The region starts at the beginning of the file") { + AND_WHEN("The region does not end at a block boundary") { + std::vector ranges; + + posix_file::offset offset = 0; + std::size_t length = 10; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(offset, length, block_size, + file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends at a block boundary") { + std::vector ranges; + + posix_file::offset offset = 0; + std::size_t length = 3.5 * block_size; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(offset, length, block_size, + file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + } + + AND_WHEN("The region starts in the middle of the file") { + AND_WHEN("The region does not end at a block boundary") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(offset, length, block_size, + file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends at a block boundary") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = + static_cast(3.5 * block_size) - + static_cast(offset); + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(offset, length, block_size, + file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends at EOF") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = + file_size - static_cast(offset); + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(offset, length, block_size, + file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends beyond EOF") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = file_size * 4; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(offset, length, block_size, + file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + } + + AND_WHEN("The region starts at the end of the file") { + std::vector ranges; + + posix_file::offset offset = file_size; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + + AND_WHEN("The region starts beyond the end of the file") { + std::vector ranges; + + posix_file::offset offset = file_size + 1; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + } + } + + GIVEN("A non-empty file of exactly one block") { + + const auto file_size = static_cast(block_size); + CAPTURE(file_size); + + auto f = create_temporary_file(file_size); + + WHEN("The region's length is 0") { + std::vector ranges; + + posix_file::offset offset = 0; + std::size_t length = 0; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + + WHEN("The region's length is not 0") { + AND_WHEN("The region starts at the beginning of the file") { + AND_WHEN("The region does not end at a block boundary") { + std::vector ranges; + + posix_file::offset offset = 0; + std::size_t length = 10; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(offset, length, block_size, + file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends at a block boundary") { + std::vector ranges; + + posix_file::offset offset = 0; + std::size_t length = 3.5 * block_size; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(offset, length, block_size, + file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + } + + AND_WHEN("The region starts in the middle of the file") { + AND_WHEN("The region does not end at a block boundary") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(offset, length, block_size, + file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends at a block boundary") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = + static_cast(3.5 * block_size) - + static_cast(offset); + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(offset, length, block_size, + file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends at EOF") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = + file_size - static_cast(offset); + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(offset, length, block_size, + file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends beyond EOF") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = file_size * 4; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(offset, length, block_size, + file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + } + + AND_WHEN("The region starts at the end of the file") { + std::vector ranges; + + posix_file::offset offset = file_size; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + + AND_WHEN("The region starts beyond the end of the file") { + std::vector ranges; + + posix_file::offset offset = file_size + 1; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + } + } + + GIVEN("A non-empty file of exactly n blocks") { + + WHEN("n is a power of 2") { + const auto n = 8; + const auto file_size = static_cast(n * block_size); + CAPTURE(file_size); + + auto f = create_temporary_file(file_size); + + AND_WHEN("The region's length is 0") { + std::vector ranges; + + posix_file::offset offset = 0; + std::size_t length = 0; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + + AND_WHEN("The region's length is not 0") { + AND_WHEN("The region starts at the beginning of the file") { + AND_WHEN("The region does not end at a block boundary") { + std::vector ranges; + + posix_file::offset offset = 0; + std::size_t length = 10; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(offset, length, block_size, + file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends at a block boundary") { + std::vector ranges; + + posix_file::offset offset = 0; + std::size_t length = 3.5 * block_size; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(offset, length, block_size, + file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + } + + AND_WHEN("The region starts in the middle of the file") { + AND_WHEN("The region does not end at a block boundary") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(offset, length, block_size, + file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends at a block boundary") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = + static_cast(3.5 * block_size) - + static_cast(offset); + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(offset, length, block_size, + file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends at EOF") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = + file_size - static_cast(offset); + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(offset, length, block_size, + file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends beyond EOF") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = file_size * 4; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(offset, length, block_size, + file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + } + + AND_WHEN("The region starts at the end of the file") { + std::vector ranges; + + posix_file::offset offset = file_size; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + + AND_WHEN("The region starts beyond the end of the file") { + std::vector ranges; + + posix_file::offset offset = file_size + 1; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + } + } + + WHEN("n is not a power of 2") { + const auto n = 11; + const auto file_size = static_cast(n * block_size); + CAPTURE(file_size); + + auto f = create_temporary_file(file_size); + + AND_WHEN("The region's length is 0") { + std::vector ranges; + + posix_file::offset offset = 0; + std::size_t length = 0; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + + AND_WHEN("The region's length is not 0") { + AND_WHEN("The region starts at the beginning of the file") { + AND_WHEN("The region does not end at a block boundary") { + std::vector ranges; + + posix_file::offset offset = 0; + std::size_t length = 10; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(offset, length, block_size, + file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends at a block boundary") { + std::vector ranges; + + posix_file::offset offset = 0; + std::size_t length = 3.5 * block_size; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(offset, length, block_size, + file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + } + + AND_WHEN("The region starts in the middle of the file") { + AND_WHEN("The region does not end at a block boundary") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(offset, length, block_size, + file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends at a block boundary") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = + static_cast(3.5 * block_size) - + static_cast(offset); + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(offset, length, block_size, + file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends at EOF") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = + file_size - static_cast(offset); + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(offset, length, block_size, + file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + + AND_WHEN("The region ends beyond EOF") { + + std::vector ranges; + + posix_file::offset offset = 10; + std::size_t length = file_size * 4; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("Ranges for each block are returned") { + const auto expected_ranges = + generate_ranges(offset, length, block_size, + file_size, step, disp); + REQUIRE(ranges == expected_ranges); + } + } + } + + AND_WHEN("The region starts at the end of the file") { + std::vector ranges; + + posix_file::offset offset = file_size; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + + AND_WHEN("The region starts beyond the end of the file") { + std::vector ranges; + + posix_file::offset offset = file_size + 1; + std::size_t length = 20; + CAPTURE(offset, length); + + for(const auto& r : + some_of(f, offset, length) | + posix_file::views::as_blocks(block_size) | + strided(step, disp)) { + ranges.push_back(r); + } + + THEN("No ranges are returned") { + REQUIRE(ranges.empty()); + } + } + } + } + } +} -- GitLab