blob: b461d31054afe9e4e07a07272a90d37fc227353b [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "usb/request-cpp.h"
#include <unittest/unittest.h>
namespace {
using Request = usb::Request<void>;
constexpr size_t kParentReqSize = sizeof(usb_request_t);
constexpr usb_request_complete_t kNoCallback = {};
bool TrivialLifetimeTest() {
BEGIN_TEST;
usb::RequestList<void> list;
usb::UnownedRequestList<void> unowned_list;
END_TEST;
}
bool SingleRequestTest() {
BEGIN_TEST;
std::optional<Request> opt_request;
ASSERT_EQ(Request::Alloc(&opt_request, 0, 0, kParentReqSize),
ZX_OK);
Request request = *std::move(opt_request);
usb::RequestList<void> list;
// Empty list.
EXPECT_EQ(list.size(), 0u);
EXPECT_TRUE(list.begin() == std::nullopt);
list.push_back(&request);
EXPECT_EQ(list.size(), 1u);
// List only has one request.
EXPECT_TRUE(list.prev(&request) == std::nullopt);
EXPECT_TRUE(list.next(&request) == std::nullopt);
std::optional<size_t> idx = list.find(&request);
EXPECT_TRUE(idx.has_value());
EXPECT_EQ(idx.value(), 0u);
// Delete the request and verify it's no longer in the list.
EXPECT_TRUE(list.erase(&request));
EXPECT_EQ(list.size(), 0u);
idx = list.find(&request);
EXPECT_FALSE(idx.has_value());
END_TEST;
}
bool MultipleRequestTest() {
BEGIN_TEST;
usb::RequestList<void> list;
// This is for verifying prev / next pointer values when iterating the list.
usb_request_t* raw_reqs[10];
for (size_t i = 0; i < 10; i++) {
std::optional<Request> opt_request;
ASSERT_EQ(Request::Alloc(&opt_request, 0, 0, kParentReqSize),
ZX_OK);
Request request = *std::move(opt_request);
list.push_back(&request);
EXPECT_EQ(list.size(), i + 1);
raw_reqs[i] = request.take();
}
EXPECT_EQ(list.size(), 10u);
// Verify iterating in both directions.
auto opt_request = list.begin();
for (size_t i = 0; i < 10; i++) {
EXPECT_TRUE(opt_request.has_value());
Request request = *std::move(opt_request);
std::optional<size_t> idx = list.find(&request);
EXPECT_TRUE(idx.has_value());
EXPECT_EQ(idx.value(), i);
auto prev = list.prev(&request);
if (i == 0) {
EXPECT_FALSE(prev.has_value());
} else {
EXPECT_TRUE(prev.has_value());
EXPECT_EQ(prev->request(), raw_reqs[i - 1]);
}
auto next = list.next(&request);
if (i == 9) {
EXPECT_FALSE(next.has_value());
} else {
EXPECT_TRUE(next.has_value());
EXPECT_EQ(next->request(), raw_reqs[i + 1]);
}
opt_request = std::move(next);
}
EXPECT_FALSE(opt_request.has_value());
for (size_t i = 0; i < 10; i++) {
auto opt_request = list.begin();
EXPECT_TRUE(opt_request.has_value());
Request request = *std::move(opt_request);
EXPECT_TRUE(list.erase(&request));
// Force the destructor to run.
__UNUSED auto req = Request(raw_reqs[i], kParentReqSize);
}
EXPECT_EQ(list.size(), 0u);
EXPECT_FALSE(list.begin().has_value());
END_TEST;
}
bool MoveTest() {
BEGIN_TEST;
usb::RequestList<void> list1;
usb::RequestList<void> list2;
usb_request_t* raw_reqs[10];
for (size_t i = 0; i < 10; i++) {
std::optional<Request> opt_request;
ASSERT_EQ(Request::Alloc(&opt_request, 0, 0, kParentReqSize),
ZX_OK);
Request request = *std::move(opt_request);
list1.push_back(&request);
raw_reqs[i] = request.take();
}
EXPECT_EQ(list1.size(), 10u);
EXPECT_EQ(list2.size(), 0u);
list2 = std::move(list1);
EXPECT_EQ(list1.size(), 0u);
EXPECT_EQ(list2.size(), 10u);
size_t count = 0;
std::optional<Request> opt_request = list2.begin();
while (opt_request) {
Request request = *std::move(opt_request);
std::optional<Request> next = list2.next(&request);
EXPECT_EQ(request.request(), raw_reqs[count]);
EXPECT_TRUE(list2.erase(&request));
// Force the destructor to run.
__UNUSED auto req = Request(raw_reqs[count], kParentReqSize);
count++;
opt_request = std::move(next);
}
EXPECT_EQ(count, 10u);
EXPECT_TRUE(list2.begin() == std::nullopt);
END_TEST;
}
bool ReleaseTest() {
BEGIN_TEST;
usb::RequestList<void> list;
usb_request_t* raw_reqs[10];
for (size_t i = 0; i < 10; i++) {
std::optional<Request> opt_request;
ASSERT_EQ(Request::Alloc(&opt_request, 0, 0, kParentReqSize),
ZX_OK);
Request request = *std::move(opt_request);
list.push_back(&request);
EXPECT_EQ(list.size(), i + 1);
raw_reqs[i] = request.take();
}
list.Release();
EXPECT_EQ(list.size(), 0u);
EXPECT_FALSE(list.begin().has_value());
for (size_t i = 0; i < 10; i++) {
// Force the destructor to run.
__UNUSED auto req = Request(raw_reqs[i], kParentReqSize);
}
END_TEST;
}
bool MultipleLayerTest() {
BEGIN_TEST;
using FirstLayerReq = usb::UnownedRequest<void>;
using SecondLayerReq = usb::Request<void>;
constexpr size_t kBaseReqSize = sizeof(usb_request_t);
constexpr size_t kFirstLayerReqSize = FirstLayerReq::RequestSize(kBaseReqSize);
usb_request_t* raw_reqs[10];
usb::RequestList<void> second_layer_list;
for (size_t i = 0; i < 10; i++) {
std::optional<SecondLayerReq> opt_request;
ASSERT_EQ(SecondLayerReq::Alloc(&opt_request, 0, 0, kFirstLayerReqSize),
ZX_OK);
ASSERT_TRUE(opt_request.has_value());
Request request = *std::move(opt_request);
second_layer_list.push_back(&request);
raw_reqs[i] = request.take();
}
EXPECT_EQ(second_layer_list.size(), 10u);
usb::UnownedRequestList<void> first_layer_list;
// Add the requests also into the first layer list.
for (size_t i = 0; i < 10; i++) {
FirstLayerReq unowned(raw_reqs[i], kNoCallback, kBaseReqSize, /* allow_destruct */ false);
first_layer_list.push_back(&unowned);
}
EXPECT_EQ(first_layer_list.size(), 10u);
// Remove the requests from both lists.
for (size_t i = 0; i < 10; i++) {
FirstLayerReq unowned(raw_reqs[i], kBaseReqSize);
std::optional<size_t> idx = first_layer_list.find(&unowned);
EXPECT_TRUE(idx.has_value());
EXPECT_EQ(idx.value(), 0u);
EXPECT_TRUE(first_layer_list.erase(&unowned));
SecondLayerReq request(unowned.take(), kFirstLayerReqSize);
idx = second_layer_list.find(&request);
EXPECT_TRUE(idx.has_value());
EXPECT_EQ(idx.value(), 0u);
EXPECT_TRUE(second_layer_list.erase(&request));
}
EXPECT_EQ(first_layer_list.size(), 0u);
EXPECT_EQ(second_layer_list.size(), 0u);
END_TEST;
}
bool MultipleLayerWithStorageTest() {
BEGIN_TEST;
using FirstLayerReq = usb::UnownedRequest<char>;
using SecondLayerReq = usb::Request<uint64_t>;
constexpr size_t kBaseReqSize = sizeof(usb_request_t);
constexpr size_t kFirstLayerReqSize = FirstLayerReq::RequestSize(kBaseReqSize);
usb_request_t* raw_reqs[10];
usb::RequestList<uint64_t> second_layer_list;
for (size_t i = 0; i < 10; i++) {
std::optional<SecondLayerReq> opt_request;
ASSERT_EQ(SecondLayerReq::Alloc(&opt_request, 0, 0, kFirstLayerReqSize),
ZX_OK);
SecondLayerReq request = *std::move(opt_request);
*request.private_storage() = i;
EXPECT_EQ(*request.private_storage(), i);
second_layer_list.push_back(&request);
raw_reqs[i] = request.take();
}
EXPECT_EQ(second_layer_list.size(), 10u);
usb::UnownedRequestList<char> first_layer_list;
size_t count = 0;
// Add the requests also into the first layer list.
for (size_t i = 0; i < 10; i++) {
FirstLayerReq unowned(raw_reqs[i], kNoCallback, kBaseReqSize, /* allow_destruct */ false);
*unowned.private_storage() = static_cast<char>('a' + first_layer_list.size());
first_layer_list.push_back(&unowned);
}
EXPECT_EQ(first_layer_list.size(), 10u);
// Verify the first layer list node's private storage and also erase them along the way.
count = 0;
auto opt_unowned = first_layer_list.begin();
while (opt_unowned) {
auto unowned = *std::move(opt_unowned);
auto next = first_layer_list.next(&unowned);
EXPECT_EQ(*unowned.private_storage(), static_cast<char>('a' + count));
EXPECT_TRUE(first_layer_list.erase(&unowned));
++count;
opt_unowned = std::move(next);
}
EXPECT_EQ(count, 10);
EXPECT_EQ(first_layer_list.size(), 0u);
// Verify the second layer list node's private storage and also erase them along the way.
count = 0;
auto opt_request = second_layer_list.begin();
while (opt_request) {
auto request = *std::move(opt_request);
auto next = second_layer_list.next(&request);
EXPECT_EQ(*request.private_storage(), count);
EXPECT_TRUE(second_layer_list.erase(&request));
++count;
opt_request = std::move(next);
}
EXPECT_EQ(count, 10);
EXPECT_EQ(second_layer_list.size(), 0u);
for (size_t i = 0; i < 10; i++) {
// Force the destructor to run.
__UNUSED auto req = SecondLayerReq(raw_reqs[i], kFirstLayerReqSize);
}
END_TEST;
}
bool MultipleLayerWithCallbackTest() {
BEGIN_TEST;
using FirstLayerReq = usb::UnownedRequest<char>;
using SecondLayerReq = usb::Request<uint64_t>;
constexpr size_t kBaseReqSize = sizeof(usb_request_t);
constexpr size_t kFirstLayerReqSize = FirstLayerReq::RequestSize(kBaseReqSize);
usb_request_t* raw_reqs[10];
usb::RequestList<uint64_t> second_layer_list;
for (size_t i = 0; i < 10; i++) {
std::optional<SecondLayerReq> opt_request;
ASSERT_EQ(SecondLayerReq::Alloc(&opt_request, 0, 0, kFirstLayerReqSize),
ZX_OK);
SecondLayerReq request = *std::move(opt_request);
*request.private_storage() = i;
EXPECT_EQ(*request.private_storage(), i);
second_layer_list.push_back(&request);
raw_reqs[i] = request.take();
}
EXPECT_EQ(second_layer_list.size(), 10u);
std::atomic<size_t> num_callbacks{0};
auto callback = [](void* ctx, usb_request_t* request) {
auto counter = static_cast<std::atomic<size_t>*>(ctx);
++(*counter);
};
usb_request_complete_t complete_cb = {
.callback = callback,
.ctx = &num_callbacks,
};
{
usb::UnownedRequestList<char> first_layer_list;
// Store the requests into the first layer list.
for (size_t i = 0; i < 10; i++) {
FirstLayerReq unowned(raw_reqs[i], complete_cb, kBaseReqSize,
/* allow_destruct */ false);
first_layer_list.push_back(&unowned);
}
EXPECT_EQ(first_layer_list.size(), 10u);
EXPECT_EQ(second_layer_list.size(), 10u);
}
// The first layer list destruction should not trigger any callbacks.
EXPECT_EQ(num_callbacks.load(), 0u);
// Verify the second layer list node's private storage and also erase them along the way.
size_t count = 0;
auto opt_request = second_layer_list.begin();
while (opt_request) {
auto request = *std::move(opt_request);
auto next = second_layer_list.next(&request);
EXPECT_EQ(*request.private_storage(), count);
EXPECT_TRUE(second_layer_list.erase(&request));
++count;
opt_request = std::move(next);
}
EXPECT_EQ(count, 10);
EXPECT_EQ(second_layer_list.size(), 0u);
for (size_t i = 0; i < 10; i++) {
// Force the destructor to run.
__UNUSED auto req = SecondLayerReq(raw_reqs[i], kFirstLayerReqSize);
}
END_TEST;
}
} // namespace
BEGIN_TEST_CASE(UsbRequestListTests)
RUN_TEST_SMALL(TrivialLifetimeTest)
RUN_TEST_SMALL(SingleRequestTest)
RUN_TEST_SMALL(MultipleRequestTest)
RUN_TEST_SMALL(MoveTest)
RUN_TEST_SMALL(ReleaseTest)
RUN_TEST_SMALL(MultipleLayerTest)
RUN_TEST_SMALL(MultipleLayerWithStorageTest)
RUN_TEST_SMALL(MultipleLayerWithCallbackTest)
END_TEST_CASE(UsbRequestListTests)