blob: 87b253d932213a92c2f3edfc0a33adcae476e5d2 [file]
// Copyright 2024 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#include <atomic>
#include "pw_compilation_testing/negative_compilation.h"
#include "pw_polyfill/language_feature_macros.h"
#include "pw_sync/binary_semaphore.h"
#include "pw_thread/thread.h"
#include "pw_unit_test/framework.h"
namespace {
#if PW_THREAD_GENERIC_CREATION_IS_SUPPORTED
// If a thread can't be joined, it's unknown whether the stack/context is safe
// to reuse, so the test can only be run once. This macro ensures the test only
// runs once and fails on subsequent attempts, so the user doesn't assume a
// pass.
#define FAIL_IF_TEST_ALREADY_RAN() \
do { \
static std::atomic_flag test_ran = ATOMIC_FLAG_INIT; \
if (test_ran.test_and_set()) { \
FAIL(); \
} \
} while (false)
constexpr pw::ThreadAttrs kThread0 = pw::ThreadAttrs().set_stack_size_bytes(0);
constexpr pw::ThreadAttrs kThread1023 =
pw::ThreadAttrs().set_name("hello world").set_stack_size_bytes(1023);
constexpr pw::ThreadAttrs kThread1024 =
pw::ThreadAttrs(kThread1023).set_stack_size_bytes(1024);
[[maybe_unused]] constexpr pw::ThreadAttrs kThread1025 =
pw::ThreadAttrs(kThread1024).set_stack_size_bytes(1025);
PW_CONSTINIT pw::ThreadStack<0> stack_0;
PW_CONSTINIT pw::ThreadStack<1024> stack_1024;
[[maybe_unused]] PW_CONSTINIT pw::ThreadContext<> must_be_constinit_1;
[[maybe_unused]] PW_CONSTINIT pw::ThreadContext<1024> must_be_constinit_2;
constexpr pw::ThreadAttrs kThreadExternalMin =
pw::ThreadAttrs().set_stack(stack_0);
constexpr pw::ThreadAttrs kThreadExternal =
pw::ThreadAttrs().set_stack(stack_1024);
class ThreadCreationTest : public ::testing::Test {
public:
~ThreadCreationTest() override {
EXPECT_EQ(expected_thread_runs_, thread_runs_);
}
auto TestThread() {
expected_thread_runs_ += 1;
return [this] { thread_runs_ += 1; };
}
private:
int expected_thread_runs_ = 0;
int thread_runs_ = 0;
};
class ThreadCreationNoJoinTest : public ::testing::Test {
public:
~ThreadCreationNoJoinTest() override { EXPECT_TRUE(was_thread_run_); }
auto TestThread() {
return [this] {
was_thread_run_ = true;
thread_complete_.release();
};
}
protected:
pw::sync::BinarySemaphore thread_complete_;
private:
bool was_thread_run_ = false;
};
TEST_F(ThreadCreationNoJoinTest, StartThreadWithContextWorks) {
FAIL_IF_TEST_ALREADY_RAN();
static pw::ThreadContext<1024> context_1024;
pw::Thread(context_1024, kThread1024, TestThread()).detach();
thread_complete_.acquire();
}
TEST_F(ThreadCreationNoJoinTest, StartThreadGetOptionsWorks) {
FAIL_IF_TEST_ALREADY_RAN();
static pw::ThreadContext<1024> context_1024;
pw::Thread(pw::GetThreadOptions<kThread1024>(context_1024), TestThread())
.detach();
thread_complete_.acquire();
}
TEST_F(ThreadCreationNoJoinTest, StartThreadGetOptionsStaticAttrsWorks) {
FAIL_IF_TEST_ALREADY_RAN();
static pw::ThreadContext<1024> context_1024;
pw::Thread(pw::GetThreadOptions(context_1024, kThread1024), TestThread())
.detach();
thread_complete_.acquire();
}
TEST_F(ThreadCreationNoJoinTest,
StartThreadGetOptionsStackSizeSmallerThanStackStaticAttrsWorks) {
FAIL_IF_TEST_ALREADY_RAN();
static pw::ThreadContext<1024> context_1024;
pw::Thread(pw::GetThreadOptions<kThread1023>(context_1024), TestThread())
.detach();
thread_complete_.acquire();
}
TEST_F(ThreadCreationNoJoinTest,
StartThreadGetOptionsStackSizeSmallerThanStackWorks) {
FAIL_IF_TEST_ALREADY_RAN();
static pw::ThreadContext<1024> context_1024;
pw::Thread(pw::GetThreadOptions(context_1024, kThread1023), TestThread())
.detach();
thread_complete_.acquire();
}
TEST_F(ThreadCreationNoJoinTest, StartThreadDefaultContextWorks) {
FAIL_IF_TEST_ALREADY_RAN();
static pw::DefaultThreadContext default_context;
pw::Thread(default_context, pw::ThreadAttrs(), TestThread()).detach();
thread_complete_.acquire();
}
TEST_F(ThreadCreationNoJoinTest,
StartThreadDefaultContextGetOptionsStaticAttrsWorks) {
FAIL_IF_TEST_ALREADY_RAN();
static pw::DefaultThreadContext default_context;
static constexpr pw::ThreadAttrs kDefaultAttrs;
pw::Thread(pw::GetThreadOptions<kDefaultAttrs>(default_context), TestThread())
.detach();
thread_complete_.acquire();
}
TEST_F(ThreadCreationNoJoinTest, StartThreadDefaultContextGetOptionsWorks) {
FAIL_IF_TEST_ALREADY_RAN();
static pw::DefaultThreadContext default_context;
pw::Thread(pw::GetThreadOptions(default_context, pw::ThreadAttrs()),
TestThread())
.detach();
thread_complete_.acquire();
}
TEST_F(ThreadCreationNoJoinTest, StartThreadExtStackWorks) {
FAIL_IF_TEST_ALREADY_RAN();
PW_CONSTINIT static pw::ThreadStack<1024> stack;
pw::ThreadContext<> context; // external stack
pw::Thread(context, pw::ThreadAttrs().set_stack(stack), TestThread())
.detach();
thread_complete_.acquire();
}
TEST_F(ThreadCreationNoJoinTest,
StartThreadExtStackGetOptionsStaticAttrsWorks) {
FAIL_IF_TEST_ALREADY_RAN();
PW_CONSTINIT static pw::ThreadStack<1024> stack;
static constexpr pw::ThreadAttrs kAttrs = pw::ThreadAttrs().set_stack(stack);
pw::ThreadContext<> context; // external stack
pw::Thread(pw::GetThreadOptions<kAttrs>(context), TestThread()).detach();
thread_complete_.acquire();
}
TEST_F(ThreadCreationNoJoinTest, StartThreadExtStackGetOptionsWorks) {
FAIL_IF_TEST_ALREADY_RAN();
PW_CONSTINIT static pw::ThreadStack<1024> stack;
pw::ThreadContext<> context; // external stack
pw::Thread(pw::GetThreadOptions(context, pw::ThreadAttrs().set_stack(stack)),
TestThread())
.detach();
thread_complete_.acquire();
}
TEST_F(ThreadCreationNoJoinTest,
StartThreadContextWithStackButStaticAttrsWithExtStackGetOptionsWorks) {
FAIL_IF_TEST_ALREADY_RAN();
// This ThreadContext allocates space for a stack, but it is not used.
static pw::ThreadContext<128> context;
PW_CONSTINIT static pw::ThreadStack<1024> stack;
static constexpr pw::ThreadAttrs kAttrs = pw::ThreadAttrs().set_stack(stack);
pw::Thread(pw::GetThreadOptions<kAttrs>(context), TestThread()).detach();
thread_complete_.acquire();
}
TEST_F(ThreadCreationNoJoinTest,
StartThreadContextWithStackButAttrsWithExtStackGetOptionsWorks) {
FAIL_IF_TEST_ALREADY_RAN();
// This ThreadContext allocates space for a stack, but it is not used.
static pw::ThreadContext<128> context;
PW_CONSTINIT static pw::ThreadStack<1024> stack;
pw::Thread(pw::GetThreadOptions(context, pw::ThreadAttrs().set_stack(stack)),
TestThread())
.detach();
thread_complete_.acquire();
}
TEST_F(ThreadCreationNoJoinTest, StartThreadMinSizeExtStackWorks) {
FAIL_IF_TEST_ALREADY_RAN();
PW_CONSTINIT static pw::ThreadStack<0> stack_min;
pw::ThreadContext<> context; // external stack
pw::Thread(context, pw::ThreadAttrs().set_stack(stack_min), TestThread())
.detach();
thread_complete_.acquire();
}
TEST_F(ThreadCreationNoJoinTest,
StartThreadMinSizeExtStackGetOptionsStaticAttrsWorks) {
FAIL_IF_TEST_ALREADY_RAN();
PW_CONSTINIT static pw::ThreadStack<0> stack_min;
static constexpr pw::ThreadAttrs kAttrs =
pw::ThreadAttrs().set_stack(stack_min);
pw::ThreadContext<> context; // external stack
pw::Thread(pw::GetThreadOptions<kAttrs>(context), TestThread()).detach();
thread_complete_.acquire();
}
TEST_F(ThreadCreationNoJoinTest, StartThreadMinSizeExtStackGetOptionsWorks) {
FAIL_IF_TEST_ALREADY_RAN();
PW_CONSTINIT static pw::ThreadStack<0> stack_min;
pw::ThreadContext<> context; // external stack
pw::Thread(
pw::GetThreadOptions(context, pw::ThreadAttrs().set_stack(stack_min)),
TestThread())
.detach();
thread_complete_.acquire();
}
TEST_F(ThreadCreationNoJoinTest, StartThreadWithContextForWorks) {
FAIL_IF_TEST_ALREADY_RAN();
static pw::ThreadContextFor<kThread1024> context_for;
pw::Thread(context_for, TestThread()).detach();
thread_complete_.acquire();
}
TEST_F(ThreadCreationNoJoinTest, StartThreadWithContexForGetOptionsWorks) {
FAIL_IF_TEST_ALREADY_RAN();
static pw::ThreadContextFor<kThread1024> context_for;
pw::Thread(pw::GetThreadOptions(context_for), TestThread()).detach();
thread_complete_.acquire();
}
TEST_F(ThreadCreationNoJoinTest, StartThreadWithContextForExtStackWorks) {
FAIL_IF_TEST_ALREADY_RAN();
PW_CONSTINIT static pw::ThreadStack<1024> stack;
static constexpr pw::ThreadAttrs kAttrs = pw::ThreadAttrs().set_stack(stack);
pw::ThreadContextFor<kAttrs> context; // external stack
pw::Thread(context, TestThread()).detach();
thread_complete_.acquire();
}
TEST_F(ThreadCreationNoJoinTest,
StartThreadWithContextForExtStackGetOptionsWorks) {
FAIL_IF_TEST_ALREADY_RAN();
PW_CONSTINIT static pw::ThreadStack<1024> stack;
static constexpr pw::ThreadAttrs kAttrs = pw::ThreadAttrs().set_stack(stack);
pw::ThreadContextFor<kAttrs> context; // external stack
pw::Thread(pw::GetThreadOptions(context), TestThread()).detach();
thread_complete_.acquire();
}
TEST_F(ThreadCreationNoJoinTest, StartThreadWithContextForMinStackWorks) {
FAIL_IF_TEST_ALREADY_RAN();
static pw::ThreadContextFor<kThread0> context_for_min_stack;
pw::Thread(context_for_min_stack, TestThread()).detach();
thread_complete_.acquire();
}
TEST_F(ThreadCreationNoJoinTest,
StartThreadWithContextForMinStackGetOptionsWorks) {
FAIL_IF_TEST_ALREADY_RAN();
static pw::ThreadContextFor<kThread0> context_for_min_stack;
pw::Thread(pw::GetThreadOptions(context_for_min_stack), TestThread())
.detach();
thread_complete_.acquire();
}
pw::ThreadContext<1024> context_1024;
TEST(ThreadCreationGetOptionsTest, GetOptionsStackSizeLargerThanStackFails) {
#if PW_NC_TEST(StackTooSmall_TemplateArgument)
PW_NC_EXPECT(
"ThreadContext stack is too small for the requested ThreadAttrs");
pw::GetThreadOptions<kThread1025>(context_1024), TestThread();
#elif PW_NC_TEST(StackTooSmall_FunctionArgument)
PW_NC_EXPECT(
"PW_ASSERT\(kContextStackSizeBytes >= "
"attributes.stack_size_bytes\(\)\);");
[[maybe_unused]] constexpr auto options =
pw::GetThreadOptions(context_1024, kThread1025);
#endif // PW_NC_TEST
}
TEST(ThreadCreationGetOptionsTest, GetOptionsExtStackFails) {
[[maybe_unused]] pw::ThreadContext<> context; // external stack
#if PW_NC_TEST(NoStack)
PW_NC_EXPECT("No stack was provided!");
std::ignore = pw::GetThreadOptions<kThread1024>(context);
#endif // PW_NC_TEST
}
#if PW_THREAD_JOINING_ENABLED
TEST_F(ThreadCreationTest, ThreadContext) {
pw::Thread(context_1024, kThread1024, TestThread()).join();
pw::Thread(pw::GetThreadOptions<kThread1023>(context_1024), TestThread())
.join();
pw::Thread(pw::GetThreadOptions(context_1024, kThread1023), TestThread())
.join();
pw::Thread(pw::GetThreadOptions<kThread1024>(context_1024), TestThread())
.join();
pw::Thread(pw::GetThreadOptions(context_1024, kThread1024), TestThread())
.join();
}
pw::DefaultThreadContext default_context;
TEST_F(ThreadCreationTest, DefaultThreadContext) {
pw::Thread(default_context, pw::ThreadAttrs(), TestThread()).join();
static constexpr pw::ThreadAttrs kDefault;
pw::Thread(pw::GetThreadOptions<kDefault>(default_context), TestThread())
.join();
pw::Thread(pw::GetThreadOptions(default_context, pw::ThreadAttrs()),
TestThread())
.join();
}
TEST_F(ThreadCreationTest, ThreadContextExternalStack) {
pw::ThreadContext<> context;
pw::Thread(context, kThreadExternal, TestThread()).join();
pw::Thread(pw::GetThreadOptions<kThreadExternal>(context), TestThread())
.join();
pw::Thread(pw::GetThreadOptions(context, kThreadExternal), TestThread())
.join();
}
TEST_F(ThreadCreationTest,
ThreadContextWithStackButAttrsWithExternallyAllocatedStack) {
// This ThreadContext allocates space for a stack, but it is not used.
pw::ThreadContext<128> context;
pw::Thread(pw::GetThreadOptions<kThreadExternal>(context), TestThread())
.join();
pw::Thread(pw::GetThreadOptions(context, kThreadExternal), TestThread())
.join();
}
TEST_F(ThreadCreationTest, ThreadContextMinimumSizedExternalStack) {
pw::ThreadContext<> context; // external stack
pw::Thread(context, kThreadExternalMin, TestThread()).join();
pw::Thread(pw::GetThreadOptions<kThreadExternalMin>(context), TestThread())
.join();
pw::Thread(pw::GetThreadOptions(context, kThreadExternalMin), TestThread())
.join();
}
pw::ThreadContextFor<kThread1024> context_for;
TEST_F(ThreadCreationTest, ThreadContextFor) {
pw::Thread(context_for, TestThread()).join();
pw::Thread(pw::GetThreadOptions(context_for), TestThread()).join();
}
TEST_F(ThreadCreationTest, ThreadContextForExternalStack) {
pw::ThreadContextFor<kThreadExternal> context;
pw::Thread(context, TestThread()).join();
pw::Thread(pw::GetThreadOptions(context), TestThread()).join();
}
pw::ThreadContextFor<kThread0> context_for_min_stack;
TEST_F(ThreadCreationTest, ThreadContextForMinimumSizedStack) {
pw::Thread(context_for_min_stack, TestThread()).join();
pw::Thread(pw::GetThreadOptions(context_for_min_stack), TestThread()).join();
}
#endif // PW_THREAD_JOINING_ENABLED
#else // thread creation is not supported
[[maybe_unused]] constexpr pw::ThreadAttrs kAttrs;
#if PW_NC_TEST(CreationUnsupported_ThreadContext)
PW_NC_EXPECT("Generic thread creation is not yet supported");
pw::ThreadContext<> context;
#elif PW_NC_TEST(CreationUnsupported_ThreadContextFor)
PW_NC_EXPECT("Generic thread creation is not yet supported");
pw::ThreadContextFor<kAttrs> context_for_attrs;
#elif PW_NC_TEST(CreationUnsupported_ThreadStack)
PW_NC_EXPECT("Generic thread creation is not yet supported");
pw::ThreadStack<1000> stack;
#endif // PW_NC_TEST
#endif // PW_THREAD_GENERIC_CREATION_IS_SUPPORTED
} // namespace