blob: 733988d6304923fc838959c1836e03c718b03af7 [file]
// Copyright 2026 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <gtest/gtest.h>
#include <array>
#include "partition_alloc/pointers/raw_ptr.h"
#include "src/utils/span.h"
#include "src/utils/typed_integer.h"
namespace dawn {
namespace {
static constexpr std::array<int, 5> kSpanData = {1, 2, 3, 4, 5};
using Index = TypedInteger<struct IndexT, uint32_t>;
using Index8 = TypedInteger<struct IndexT, uint8_t>;
using Index64 = TypedInteger<struct IndexT, uint64_t>;
struct FakeRange {
size_t size() const { return kSpanData.size(); }
const int* data() const { return kSpanData.data(); }
};
struct FakeTypedRange {
Index size() const {
return Index{uint32_t{kSpanData.size()}};
}
const int* data() const { return kSpanData.data(); }
};
struct FakeTyped64Range {
Index64 size() const {
return Index64{uint64_t{kSpanData.size()}};
}
const int* data() const { return kSpanData.data(); }
};
TEST(SpanTest, Constructor_Default) {
{
Span<int> sp;
EXPECT_EQ(sp.size(), 0u);
EXPECT_EQ(sp.data(), nullptr);
}
{
ityp::span<Index, int> sp;
EXPECT_EQ(sp.size(), Index{0u});
EXPECT_EQ(sp.data(), nullptr);
}
}
TEST(SpanTest, Constructor_PointerAndSize) {
int data[] = {1, 2, 3};
int constData[] = {1, 2, 3};
raw_ptr<int> rptr = data;
// T* + size for Span<T>
{
// SAFETY: Test for the unsafe constructor.
Span<int> DAWN_UNSAFE_BUFFERS(sp{&data[0], 3});
EXPECT_EQ(sp.size(), 3u);
EXPECT_EQ(sp.data(), data);
}
// const T* + size for Span<const T>
{
// SAFETY: Test for the unsafe constructor.
Span<const int> DAWN_UNSAFE_BUFFERS(sp{&constData[0], 3});
EXPECT_EQ(sp.size(), 3u);
EXPECT_EQ(sp.data(), constData);
}
// T* + size for Span<const T>
{
// SAFETY: Test for the unsafe constructor.
Span<const int> DAWN_UNSAFE_BUFFERS(sp{&data[0], 3});
EXPECT_EQ(sp.size(), 3u);
EXPECT_EQ(sp.data(), data);
}
// T* + size for ityp::span<T, ...>
{
// SAFETY: Test for the unsafe constructor.
ityp::span<Index, int> DAWN_UNSAFE_BUFFERS(sp{&data[0], Index{3u}});
EXPECT_EQ(sp.size(), Index{3u});
EXPECT_EQ(sp.data(), data);
}
// const T* + size for ityp::span<const T, ...>
{
// SAFETY: Test for the unsafe constructor.
ityp::span<Index, const int> DAWN_UNSAFE_BUFFERS(sp{&constData[0], Index{3u}});
EXPECT_EQ(sp.size(), Index{3u});
EXPECT_EQ(sp.data(), constData);
}
// T* + size for ityp::span<const T, ...>
{
// SAFETY: Test for the unsafe constructor.
ityp::span<Index, const int> DAWN_UNSAFE_BUFFERS(sp{&data[0], Index{3u}});
EXPECT_EQ(sp.size(), Index{3u});
EXPECT_EQ(sp.data(), data);
}
}
TEST(SpanDeathTest, Constructor_PointerAndSizeOversizedIndex) {
// These tests are only relevant on 32-bit builds.
if constexpr (sizeof(size_t) > sizeof(uint32_t)) {
GTEST_SKIP();
}
constexpr Index64 kHugeSize{0x1'0000'0000LLU};
// SAFETY: Test for the unsafe constructor.
DAWN_UNSAFE_BUFFERS(EXPECT_DEATH_IF_SUPPORTED(
(ityp::span<Index64, const int>(kSpanData.data(), kHugeSize)), ""));
}
TEST(SpanTest, Constructor_TwoIterators) {
std::array<int, 3> data = {1, 2, 3};
const std::array<int, 3> constData = {1, 2, 3};
// 2 x iterator for Span<T>
{
// SAFETY: Test for the unsafe constructor.
Span<int> DAWN_UNSAFE_BUFFERS(sp{data.begin(), data.end()});
EXPECT_EQ(sp.size(), 3u);
EXPECT_EQ(sp.data(), data.data());
}
// 2 x const_iterator for Span<const T>
{
// SAFETY: Test for the unsafe constructor.
Span<const int> DAWN_UNSAFE_BUFFERS(sp{constData.begin(), constData.end()});
EXPECT_EQ(sp.size(), 3u);
EXPECT_EQ(sp.data(), constData.data());
}
// 2 x iterator for Span<const T>
{
// SAFETY: Test for the unsafe constructor.
Span<const int> DAWN_UNSAFE_BUFFERS(sp{data.begin(), data.end()});
EXPECT_EQ(sp.size(), 3u);
EXPECT_EQ(sp.data(), data.data());
}
// 2 x iterator for ityp::span<T, ...>
{
// SAFETY: Test for the unsafe constructor.
ityp::span<Index, int> DAWN_UNSAFE_BUFFERS(sp{data.begin(), data.end()});
EXPECT_EQ(sp.size(), Index{3u});
EXPECT_EQ(sp.data(), data.data());
}
// 2 x const_iterator for ityp::span<const T, ...>
{
// SAFETY: Test for the unsafe constructor.
ityp::span<Index, const int> DAWN_UNSAFE_BUFFERS(sp{constData.begin(), constData.end()});
EXPECT_EQ(sp.size(), Index{3u});
EXPECT_EQ(sp.data(), constData.data());
}
// 2 x iterator for ityp::span<const T, ...>
{
// SAFETY: Test for the unsafe constructor.
ityp::span<Index, const int> DAWN_UNSAFE_BUFFERS(sp{data.begin(), data.end()});
EXPECT_EQ(sp.size(), Index{3u});
EXPECT_EQ(sp.data(), data.data());
}
}
TEST(SpanDeathTest, Constructor_TwoIteratorsInverted) {
std::array<int, 3> data = {1, 2, 3};
// SAFETY: Test for the unsafe constructor.
DAWN_UNSAFE_BUFFERS(EXPECT_DEATH_IF_SUPPORTED((Span<int>{data.end(), data.begin()}), ""));
}
TEST(SpanDeathTest, Constructor_TwoIteratorsLargerThanIndexType) {
std::vector<int> data;
data.resize(256); // Larger than indices that can be store in a uint8_t.
// SAFETY: Test for the unsafe constructor.
DAWN_UNSAFE_BUFFERS(
EXPECT_DEATH_IF_SUPPORTED((ityp::span<Index8, int>(data.begin(), data.end())), ""));
// Fits exactly in uint8_t for indexing.
data.resize(255);
// SAFETY: Test for the unsafe constructor.
DAWN_UNSAFE_BUFFERS((ityp::span<Index8, int>(data.begin(), data.end())));
}
std::span<std::byte> GetByteSpan() {
return {};
}
TEST(SpanTest, ConstructorFromCompatibleRange) {
{
std::array<int, 3> data = {1, 2, 3};
Span<int> sp{data};
EXPECT_EQ(sp.size(), data.size());
EXPECT_EQ(sp.data(), data.data());
}
{
const std::array<int, 3> data = {1, 2, 3};
Span<const int> sp{data};
EXPECT_EQ(sp.size(), data.size());
EXPECT_EQ(sp.data(), data.data());
}
{
std::vector<int> data{{1, 2, 3}};
Span<const int> sp{data};
EXPECT_EQ(sp.size(), data.size());
EXPECT_EQ(sp.data(), data.data());
}
{
std::string data = "foo";
Span<char> sp{data};
EXPECT_EQ(sp.size(), data.size());
EXPECT_EQ(sp.data(), data.data());
}
{
std::string_view data = "foo";
Span<const char> sp{data};
EXPECT_EQ(sp.size(), data.size());
EXPECT_EQ(sp.data(), data.data());
}
{
FakeRange data;
Span<const int> sp{data};
EXPECT_EQ(sp.size(), data.size());
EXPECT_EQ(sp.data(), data.data());
}
{
// Fails to compile if the constructor from range take a reference and not an rvalue
// reference.
Span<std::byte> sp;
sp = GetByteSpan();
}
{
FakeTypedRange data;
ityp::span<Index, const int> sp{data};
EXPECT_EQ(sp.size(), data.size());
EXPECT_EQ(sp.data(), data.data());
}
}
TEST(SpanTest, CopyConstructor) {
std::array<int, 3> data = {1, 2, 3};
Span<int> sp{data};
Span<int> sp2{sp};
ASSERT_EQ(sp.data(), sp2.data());
ASSERT_EQ(sp.size(), sp2.size());
// Actually calls the constructor from a range.
Span<const int> sp3{sp};
ASSERT_EQ(sp.data(), sp3.data());
ASSERT_EQ(sp.size(), sp3.size());
}
TEST(SpanTest, CopyAssignment) {
std::array<int, 3> data = {1, 2, 3};
Span<int> sp{data};
Span<int> sp2;
sp2 = sp;
ASSERT_EQ(sp.data(), sp2.data());
ASSERT_EQ(sp.size(), sp2.size());
// Actually calls the constructor from a range and then assigns.
Span<const int> sp3;
sp3 = sp;
ASSERT_EQ(sp.data(), sp3.data());
ASSERT_EQ(sp.size(), sp3.size());
}
TEST(SpanTest, MoveConstructor) {
std::array<int, 3> data = {1, 2, 3};
Span<int> sp{data};
Span<int> sp2{std::move(sp)};
ASSERT_EQ(data.data(), sp2.data());
ASSERT_EQ(data.size(), sp2.size());
// Move actually does a copy so sp stays the same.
ASSERT_EQ(data.data(), sp.data());
ASSERT_EQ(data.size(), sp.size());
}
TEST(SpanTest, MoveAssignment) {
std::array<int, 3> data = {1, 2, 3};
Span<int> sp{data};
Span<int> sp2;
sp2 = std::move(sp);
ASSERT_EQ(data.data(), sp2.data());
ASSERT_EQ(data.size(), sp2.size());
// Move actually does a copy so sp stays the same.
ASSERT_EQ(data.data(), sp.data());
ASSERT_EQ(data.size(), sp.size());
}
TEST(SpanTest, BeginEnd) {
{
Span<const int> sp(FakeRange{});
ASSERT_EQ(&*sp.begin(), &*kSpanData.begin());
ASSERT_EQ(&*sp.end(), &*kSpanData.end());
}
{
ityp::span<Index, const int> sp(FakeTypedRange{});
ASSERT_EQ(&*sp.begin(), &*kSpanData.begin());
ASSERT_EQ(&*sp.end(), &*kSpanData.end());
}
}
TEST(SpanTest, BeginEndForIteration) {
// Uses begin/end
{
Span<const int> sp(FakeRange{});
int expected = 1;
for (const int& i : sp) {
EXPECT_EQ(i, expected);
expected++;
}
}
// Uses cbegin/cend
{
const Span<const int> sp(FakeRange{});
int expected = 1;
for (const int& i : sp) {
EXPECT_EQ(i, expected);
expected++;
}
}
// ityp, uses begin/end
{
ityp::span<Index, const int> sp(FakeTypedRange{});
int expected = 1;
for (const int& i : sp) {
EXPECT_EQ(i, expected);
expected++;
}
}
// ityp, uses cbegin/cend
{
const ityp::span<Index, const int> sp(FakeTypedRange{});
int expected = 1;
for (const int& i : sp) {
EXPECT_EQ(i, expected);
expected++;
}
}
}
TEST(SpanTest, FrontBack) {
{
Span<const int> sp(FakeRange{});
EXPECT_EQ(&sp.front(), &kSpanData.front());
EXPECT_EQ(&sp.back(), &kSpanData.back());
}
{
ityp::span<Index, const int> sp(FakeTypedRange{});
EXPECT_EQ(&sp.front(), &kSpanData.front());
EXPECT_EQ(&sp.back(), &kSpanData.back());
}
}
TEST(SpanDeathTest, FrontBackOfEmpty) {
Span<const int> sp;
EXPECT_DEATH_IF_SUPPORTED(sp.front(), "");
EXPECT_DEATH_IF_SUPPORTED(sp.back(), "");
}
TEST(SpanTest, Indexing) {
{
Span<const int> sp(FakeRange{});
for (size_t i = 0; i < kSpanData.size(); i++) {
EXPECT_EQ(&sp.at(i), &kSpanData[i]);
EXPECT_EQ(&sp[i], &kSpanData[i]);
}
}
{
ityp::span<Index, const int> sp(FakeTypedRange{});
for (size_t i = 0; i < kSpanData.size(); i++) {
Index id{static_cast<uint32_t>(i)};
EXPECT_EQ(&sp.at(id), &kSpanData[i]);
EXPECT_EQ(&sp[id], &kSpanData[i]);
}
}
}
TEST(SpanDeathTest, IndexingOOB) {
Span<const int> sp(FakeRange{});
EXPECT_DEATH_IF_SUPPORTED(sp.at(sp.size()), "");
EXPECT_DEATH_IF_SUPPORTED(sp[sp.size()], "");
Span<const int> spEmpty;
EXPECT_DEATH_IF_SUPPORTED(spEmpty.at(0u), "");
EXPECT_DEATH_IF_SUPPORTED(spEmpty[0u], "");
}
TEST(SpanDeathTest, IndexingOversizedIndex) {
// These tests are only relevant on 32-bit builds.
if constexpr (sizeof(size_t) > sizeof(uint32_t)) {
GTEST_SKIP();
}
auto sp = ityp::span<Index64, const int>(FakeTyped64Range());
// The narrowing to size_t would give 0 which is in bounds, so this checks that the cast to
// size_t itself causes a crash.
constexpr Index64 kHugeIndex{0x1'0000'0000LLU};
EXPECT_DEATH_IF_SUPPORTED(sp[kHugeIndex], "");
}
// .data() and .size() are tested in every test essentially.
TEST(SpanTest, Empty) {
ASSERT_FALSE(Span<const int>{FakeRange{}}.empty());
// SAFETY: Test for the unsafe constructor.
ASSERT_FALSE(DAWN_UNSAFE_BUFFERS((Span<const int>{static_cast<int*>(nullptr), 1u})).empty());
ASSERT_TRUE(Span<const int>{}.empty());
// SAFETY: Test for the unsafe constructor.
ASSERT_TRUE(DAWN_UNSAFE_BUFFERS((Span<const int>{kSpanData.data(), 0u})).empty());
ASSERT_FALSE((ityp::span<Index, const int>{FakeTypedRange{}}.empty()));
ASSERT_FALSE(
// SAFETY: Test for the unsafe constructor.
DAWN_UNSAFE_BUFFERS((ityp::span<Index, const int>{static_cast<int*>(nullptr), Index{1u}}))
.empty());
ASSERT_TRUE((ityp::span<Index, const int>{}.empty()));
ASSERT_TRUE(
// SAFETY: Test for the unsafe constructor.
DAWN_UNSAFE_BUFFERS((ityp::span<Index, const int>{kSpanData.data(), Index{0u}})).empty());
}
TEST(SpanTest, SizeBytes) {
ASSERT_EQ(Span<int>{}.size_bytes(), 0u);
ASSERT_EQ((ityp::span<Index, int>{}.size_bytes()), 0u);
std::array<int, 3> ints{};
ASSERT_EQ(Span<int>{ints}.size_bytes(), 3 * sizeof(int));
std::array<double, 10> doubles{};
ASSERT_EQ(Span<double>{doubles}.size_bytes(), 10 * sizeof(double));
}
TEST(SpanTest, FirstLast) {
{
Span<const int> sp{FakeRange()};
Span<const int> first0 = sp.first(0);
Span<const int> first2 = sp.first(2);
Span<const int> last0 = sp.last(0);
Span<const int> last2 = sp.last(2);
EXPECT_EQ(first0.data(), sp.data());
EXPECT_EQ(first0.size(), 0u);
EXPECT_EQ(first2.data(), sp.data());
EXPECT_EQ(first2.size(), 2u);
EXPECT_EQ(last0.data(), &*sp.end());
EXPECT_EQ(last0.size(), 0u);
EXPECT_EQ(last2.data(), &sp.at(sp.size() - 2));
EXPECT_EQ(last2.size(), 2u);
}
{
ityp::span<Index, const int> sp{FakeTypedRange()};
ityp::span<Index, const int> first0 = sp.first(Index{0u});
ityp::span<Index, const int> first2 = sp.first(Index{2u});
ityp::span<Index, const int> last0 = sp.last(Index{0u});
ityp::span<Index, const int> last2 = sp.last(Index{2u});
EXPECT_EQ(first0.data(), sp.data());
EXPECT_EQ(first0.size(), Index{0u});
EXPECT_EQ(first2.data(), sp.data());
EXPECT_EQ(first2.size(), Index{2u});
EXPECT_EQ(last0.data(), &*sp.end());
EXPECT_EQ(last0.size(), Index{0u});
EXPECT_EQ(last2.data(), &sp.at(sp.size() - Index{2u}));
EXPECT_EQ(last2.size(), Index{2u});
}
}
TEST(SpanDeathTest, FirstLastOOB) {
Span<const int> sp{FakeRange()};
sp.first(sp.size());
EXPECT_DEATH_IF_SUPPORTED(sp.first(sp.size() + 1), "");
sp.last(sp.size());
EXPECT_DEATH_IF_SUPPORTED(sp.last(sp.size() + 1), "");
}
TEST(SpanTest, Subspan1Arg) {
{
Span<const int> sp{FakeRange()};
Span<const int> subspan0 = sp.subspan(0);
Span<const int> subspan2 = sp.subspan(2);
EXPECT_EQ(subspan0.data(), sp.data());
EXPECT_EQ(subspan0.size(), sp.size());
EXPECT_EQ(subspan2.data(), &sp.at(2));
EXPECT_EQ(subspan2.size(), sp.size() - 2);
}
{
ityp::span<Index, const int> sp{FakeTypedRange()};
ityp::span<Index, const int> subspan0 = sp.subspan(Index{0u});
ityp::span<Index, const int> subspan2 = sp.subspan(Index{2u});
EXPECT_EQ(subspan0.data(), sp.data());
EXPECT_EQ(subspan0.size(), sp.size());
EXPECT_EQ(subspan2.data(), &sp.at(Index{2u}));
EXPECT_EQ(subspan2.size(), sp.size() - Index{2u});
}
}
TEST(SpanDeathTest, Subspan1ArgOOB) {
Span<const int> sp{FakeRange()};
sp.subspan(sp.size());
EXPECT_DEATH_IF_SUPPORTED(sp.subspan(sp.size() + 1), "");
}
TEST(SpanTest, Subspan2Args) {
{
Span<const int> sp{FakeRange()};
Span<const int> subspan0_2 = sp.subspan(0, 2);
Span<const int> subspan3_2 = sp.subspan(3, 2);
EXPECT_EQ(subspan0_2.data(), sp.data());
EXPECT_EQ(subspan0_2.size(), 2u);
EXPECT_EQ(subspan3_2.data(), &sp.at(3));
EXPECT_EQ(subspan3_2.size(), 2u);
}
{
ityp::span<Index, const int> sp{FakeTypedRange()};
ityp::span<Index, const int> subspan0_2 = sp.subspan(Index{0u}, Index{2u});
ityp::span<Index, const int> subspan3_2 = sp.subspan(Index{3u}, Index{2u});
EXPECT_EQ(subspan0_2.data(), sp.data());
EXPECT_EQ(subspan0_2.size(), Index{2u});
EXPECT_EQ(subspan3_2.data(), &sp.at(Index{3u}));
EXPECT_EQ(subspan3_2.size(), Index{2u});
}
}
TEST(SpanDeathTest, Subspan2ArgOOB) {
Span<const int> sp{FakeRange()};
sp.subspan(2, sp.size() - 2);
EXPECT_DEATH_IF_SUPPORTED(sp.subspan(2, sp.size() - 1), "");
// Check that overflows of offset + count is handled.
EXPECT_DEATH_IF_SUPPORTED(sp.subspan(std::numeric_limits<size_t>::max(), 1), "");
EXPECT_DEATH_IF_SUPPORTED(sp.subspan(1, std::numeric_limits<size_t>::max()), "");
// SAFETY: This is the same range as kSpanData, just viewed with a uint8_t index (which fits the
// size of kSpanData since it is 5).
auto sp8 = DAWN_UNSAFE_BUFFERS(
ityp::span<Index8, const int>(kSpanData.data(), Index8{uint8_t{kSpanData.size()}}));
Index8 kOne = Index8{uint8_t{1}};
Index8 kTwo = Index8{uint8_t{2}};
sp8.subspan(kTwo, sp8.size() - kTwo);
EXPECT_DEATH_IF_SUPPORTED(sp8.subspan(kTwo, sp8.size() - kOne), "");
// Check that overflows of offset + count is handled.
EXPECT_DEATH_IF_SUPPORTED(sp8.subspan(std::numeric_limits<Index8>::max(), kOne), "");
EXPECT_DEATH_IF_SUPPORTED(sp8.subspan(kOne, std::numeric_limits<Index8>::max()), "");
}
} // anonymous namespace
} // namespace dawn