[zirrcon][zxtest] Migrate elf-tls zxtest

In addition, refactored to utilize more cpp constructs.

Test: elf-tls-test

ZX-3717 #done

Change-Id: I1790623cb83d636fae4c41fbff593b387b099960
diff --git a/zircon/system/utest/core/elf-tls/BUILD.gn b/zircon/system/utest/core/elf-tls/BUILD.gn
index 39eb10b..536d2d2 100644
--- a/zircon/system/utest/core/elf-tls/BUILD.gn
+++ b/zircon/system/utest/core/elf-tls/BUILD.gn
@@ -9,7 +9,7 @@
   ]
   deps = [
     "$zx/system/ulib/fdio",
-    "$zx/system/ulib/unittest",
     "$zx/system/ulib/zircon",
+    "$zx/system/ulib/zxtest",
   ]
 }
diff --git a/zircon/system/utest/core/elf-tls/tls.cpp b/zircon/system/utest/core/elf-tls/tls.cpp
index c1ffd1c..4bba335 100644
--- a/zircon/system/utest/core/elf-tls/tls.cpp
+++ b/zircon/system/utest/core/elf-tls/tls.cpp
@@ -8,23 +8,18 @@
 #include <stdint.h>
 #include <threads.h>
 
-#include <unittest/unittest.h>
+#include <vector>
+#include <limits>
 
-static thread_local bool u1 = true;
-static thread_local uint8_t u8 = UINT8_MAX;
-static thread_local uint16_t u16 = UINT16_MAX;
-static thread_local uint32_t u32 = UINT32_MAX;
-static thread_local uint64_t u64 = UINT64_MAX;
-static thread_local uintptr_t uptr = UINTPTR_MAX;
-static thread_local int8_t i8 = INT8_MAX;
-static thread_local int16_t i16 = INT16_MAX;
-static thread_local int32_t i32 = INT32_MAX;
-static thread_local int64_t i64 = INT64_MAX;
-static thread_local intptr_t iptr = INTPTR_MAX;
-static thread_local float f32 = FLT_MAX;
-static thread_local double f64 = DBL_MAX;
-static thread_local void* ptr = &ptr;
-static thread_local struct {
+#include <zxtest/zxtest.h>
+
+namespace {
+
+constexpr char kMainThreadError[] = "MainThread: Unexpected initialized value";
+constexpr char kBackgroundThreadError[] = "BackgroundThread: Unexpected initialized value";
+
+
+struct Bits {
     uint64_t bits0 : 9;
     uint64_t bits1 : 9;
     uint64_t bits2 : 9;
@@ -32,7 +27,7 @@
     uint64_t bits4 : 9;
     uint64_t bits5 : 9;
     uint64_t bits6 : 9;
-    double f64;
+    double   f64;
     uint64_t bits7 : 9;
     uint64_t bits8 : 9;
     uint64_t bits9 : 9;
@@ -40,7 +35,23 @@
     uint64_t bits11 : 9;
     uint64_t bits12 : 9;
     uint64_t bits13 : 9;
-} bits = {
+};
+
+thread_local bool u1 = true;
+thread_local uint8_t u8 = std::numeric_limits<uint8_t>::max();
+thread_local uint16_t u16 = std::numeric_limits<uint16_t>::max();
+thread_local uint32_t u32 = std::numeric_limits<uint32_t>::max();
+thread_local uint64_t u64 = std::numeric_limits<uint64_t>::max();
+thread_local uintptr_t uptr = std::numeric_limits<uintptr_t>::max();
+thread_local int8_t i8 = std::numeric_limits<int8_t>::max();
+thread_local int16_t i16 = std::numeric_limits<int16_t>::max();
+thread_local int32_t i32 = std::numeric_limits<int32_t>::max();
+thread_local int64_t i64 = std::numeric_limits<int64_t>::max();
+thread_local intptr_t iptr = std::numeric_limits<intptr_t>::max();
+thread_local float f32 = std::numeric_limits<float>::max();
+thread_local double f64 = std::numeric_limits<double>::max();
+thread_local void* ptr = &ptr;
+thread_local Bits bits = {
     0x1ffu,
     0x1ffu,
     0x1ffu,
@@ -48,7 +59,7 @@
     0x1ffu,
     0x1ffu,
     0x1ffu,
-    DBL_MAX,
+    std::numeric_limits<double>::max(),
     0x1ffu,
     0x1ffu,
     0x1ffu,
@@ -57,120 +68,340 @@
     0x1ffu,
     0x1ffu,
 };
+
+struct BasicInitializerInfo {
+    bool u1;
+    uint8_t u8;
+    uint16_t u16;
+    uint32_t u32;
+    uint64_t u64;
+    uintptr_t uptr;
+    int8_t i8;
+    int16_t i16;
+    int32_t i32;
+    int64_t i64;
+    intptr_t iptr;
+    float f32;
+    double f64;
+    void* ptr_addr;
+    void* ptr_val;
+    Bits bits;
+};
+
+int GetBasicInitializers(void* arg) {
+    auto info = reinterpret_cast<BasicInitializerInfo*>(arg);
+    info->u1 = u1;
+    info->u8 = u8;
+    info->u16 = u16;
+    info->u32 = u32;
+    info->u64 = u64;
+    info->uptr = uptr;
+    info->i8 = i8;
+    info->i16 = i16;
+    info->i32 = i32;
+    info->i64 = i64;
+    info->iptr = iptr;
+    info->f32 = f32;
+    info->f64 = f64;
+    info->ptr_addr = &ptr;
+    info->ptr_val = ptr;
+    info->bits.bits0 = bits.bits0;
+    info->bits.bits1 = bits.bits1;
+    info->bits.bits2 = bits.bits2;
+    info->bits.bits3 = bits.bits3;
+    info->bits.bits4 = bits.bits4;
+    info->bits.bits5 = bits.bits5;
+    info->bits.bits6 = bits.bits7;
+    info->bits.bits7 = bits.bits7;
+    info->bits.bits8 = bits.bits8;
+    info->bits.bits9 = bits.bits9;
+    info->bits.bits10 = bits.bits10;
+    info->bits.bits11 = bits.bits11;
+    info->bits.bits12 = bits.bits12;
+    info->bits.bits13 = bits.bits13;
+    return 0;
+}
+
+void VerifyBasicInitializers(const BasicInitializerInfo* info, const char* error_message) {
+    ASSERT_EQ(info->u1, true, "%s", error_message);
+    ASSERT_EQ(info->u8, std::numeric_limits<uint8_t>::max(), "%s", error_message);
+    ASSERT_EQ(info->u16, std::numeric_limits<uint16_t>::max(), "%s", error_message);
+    ASSERT_EQ(info->u32, std::numeric_limits<uint32_t>::max(), "%s", error_message);
+    ASSERT_EQ(info->u64, std::numeric_limits<uint64_t>::max(), "%s", error_message);
+    ASSERT_EQ(info->uptr, std::numeric_limits<uintptr_t>::max(), "%s", error_message);
+    ASSERT_EQ(info->i8, std::numeric_limits<int8_t>::max(), "%s", error_message);
+    ASSERT_EQ(info->i16, std::numeric_limits<int16_t>::max(), "%s", error_message);
+    ASSERT_EQ(info->i32, std::numeric_limits<int32_t>::max(), "%s", error_message);
+    ASSERT_EQ(info->i64, std::numeric_limits<int64_t>::max(), "%s", error_message);
+    ASSERT_EQ(info->iptr, std::numeric_limits<intptr_t>::max(), "%s", error_message);
+    ASSERT_EQ(info->f32, std::numeric_limits<float>::max(), "%s", error_message);
+    ASSERT_EQ(info->f64, std::numeric_limits<double>::max(), "%s", error_message);
+    ASSERT_EQ(info->ptr_addr, info->ptr_val, "%s", error_message);
+    ASSERT_EQ(info->bits.bits0, 0x1ffu, "%s", error_message);
+    ASSERT_EQ(info->bits.bits1, 0x1ffu, "%s", error_message);
+    ASSERT_EQ(info->bits.bits2, 0x1ffu, "%s", error_message);
+    ASSERT_EQ(info->bits.bits3, 0x1ffu, "%s", error_message);
+    ASSERT_EQ(info->bits.bits4, 0x1ffu, "%s", error_message);
+    ASSERT_EQ(info->bits.bits5, 0x1ffu, "%s", error_message);
+    ASSERT_EQ(info->bits.bits6, 0x1ffu, "%s", error_message);
+    ASSERT_EQ(info->bits.bits7, 0x1ffu, "%s", error_message);
+    ASSERT_EQ(info->bits.bits8, 0x1ffu, "%s", error_message);
+    ASSERT_EQ(info->bits.bits9, 0x1ffu, "%s", error_message);
+    ASSERT_EQ(info->bits.bits10, 0x1ffu, "%s", error_message);
+    ASSERT_EQ(info->bits.bits11, 0x1ffu, "%s", error_message);
+    ASSERT_EQ(info->bits.bits12, 0x1ffu, "%s", error_message);
+    ASSERT_EQ(info->bits.bits13, 0x1ffu, "%s", error_message);
+}
+
+constexpr char kThreadNameBasicInitializer[]= "GetInitializers";
+
+TEST(ExecutableTlsTest, BasicInitalizersInThread) {
+    thrd_t thread;
+    auto info = std::make_unique<BasicInitializerInfo>();
+
+    int ret = thrd_create_with_name(&thread, &GetBasicInitializers, info.get(), kThreadNameBasicInitializer);
+    ASSERT_EQ(ret, thrd_success, "unable to create GetInitializers thread");
+
+    ret = thrd_join(thread, nullptr);
+    ASSERT_EQ(ret, thrd_success, "unable to join GetInitializers thread");
+    VerifyBasicInitializers(info.get(), kBackgroundThreadError);
+}
+
+TEST(ExecutableTlsTest, BasicInitalizersInMain) {
+    auto info = std::make_unique<BasicInitializerInfo>();
+
+    ASSERT_EQ(GetBasicInitializers(info.get()), 0);
+    VerifyBasicInitializers(info.get(), kMainThreadError);
+}
+
+constexpr int kArraySize = 1024;
+
 #define BYTES_4 0xffu, 0xffu, 0xffu, 0xffu,
 #define BYTES_16 BYTES_4 BYTES_4 BYTES_4 BYTES_4
 #define BYTES_64 BYTES_16 BYTES_16 BYTES_16 BYTES_16
 #define BYTES_256 BYTES_64 BYTES_64 BYTES_64 BYTES_64
 #define BYTES_1024 BYTES_256 BYTES_256 BYTES_256 BYTES_256
-static thread_local uint8_t array[1024] = { BYTES_1024 };
-static thread_local struct Ctor {
-    Ctor() : x_(UINT64_MAX) {}
-    uint64_t x_;
-} ctor;
-static thread_local uint8_t big_array[1 << 20];
+
+thread_local uint8_t array[kArraySize] = { BYTES_1024 };
+
+struct ArrayInfo {
+    uint8_t array[kArraySize];
+};
+
+int GetArray(void* arg) {
+    auto info = reinterpret_cast<ArrayInfo*>(arg);
+    memcpy(info->array, array, kArraySize);
+    return 0;
+}
+
+constexpr char kThreadNameArrayInitializer[]= "ArrayInitializers";
+
+TEST(ExecutableTlsTest, ArrayInitializerInThread) {
+    thrd_t thread;
+    auto info = std::make_unique<ArrayInfo>();
+    int ret = thrd_create_with_name(&thread, &GetArray, info.get(), kThreadNameArrayInitializer);
+    ASSERT_EQ(ret, thrd_success, "unable to create GetArray thread");
+    ret = thrd_join(thread, nullptr);
+    ASSERT_EQ(ret, thrd_success, "unable to join GetArray thread");
+    for (const auto byte : info->array) {
+        ASSERT_EQ(byte, std::numeric_limits<uint8_t>::max(), "%s", kBackgroundThreadError);
+    }
+}
+
+TEST(ExecutableTlsTest, ArrayInitializerInMain) {
+    auto info = std::make_unique<ArrayInfo>();
+    ASSERT_EQ(GetArray(info.get()), 0);
+    for (const auto byte : info->array) {
+        ASSERT_EQ(byte, std::numeric_limits<uint8_t>::max(), "%s", kMainThreadError);
+    }
+}
+
+
+constexpr int kBitArraySize = 1 << 20;
+thread_local uint8_t big_array[kBitArraySize];
+
+struct BigArrayInfo {
+    uint8_t big_array[kBitArraySize];
+};
+
+int GetBigArray(void* arg) {
+    auto info = reinterpret_cast<BigArrayInfo*>(arg);
+    memcpy(info->big_array, big_array, kBitArraySize);
+    return 0;
+}
+
+void VerifyBigArray(BigArrayInfo const* const info, const char *error_message) {
+    uint8_t sum = 0u;
+    for (const auto byte : info->big_array) {
+        sum |= byte;
+    }
+    ASSERT_EQ(sum, 0u, "%s", error_message);
+}
+
+constexpr char kThreadNameBigArray[]= "GetBigArray";
+
+TEST(ExecutableTlsTest, BigArrayInitializerInThread) {
+    thrd_t thread;
+    auto info = std::make_unique<BigArrayInfo>();
+
+    int ret = thrd_create_with_name(&thread, &GetBigArray, info.get(), kThreadNameBigArray);
+    ASSERT_EQ(ret, thrd_success, "unable to create GetBigArray thread");
+
+    ret = thrd_join(thread, nullptr);
+    ASSERT_EQ(ret, thrd_success, "unable to join GetBigArray thread");
+    VerifyBigArray(info.get(), kBackgroundThreadError);
+}
+
+TEST(ExecutableTlsTest, BigArrayInitializerInMain) {
+    auto info = std::make_unique<BigArrayInfo>();
+
+    ASSERT_EQ(GetBigArray(info.get()), 0);
+    VerifyBigArray(info.get(), kMainThreadError);
+}
+
+struct Ctor {
+   Ctor() : x(std::numeric_limits<uint64_t>::max()) {}
+   uint64_t x;
+};
+
+thread_local Ctor ctor;
+
+struct CtorInfo {
+    Ctor ctor;
+};
+
+int GetCtor(void* arg) {
+    auto info = reinterpret_cast<CtorInfo*>(arg);
+    info->ctor = ctor;
+    return 0;
+}
+
+constexpr char kThreadNameStructureInitializer[]= "StructureInitializer";
+
+TEST(ExecutableTlsTest, StructureInitializerInThread) {
+    thrd_t thread;
+    auto info = std::make_unique<CtorInfo>();
+    int ret = thrd_create_with_name(&thread, &GetCtor, info.get(), kThreadNameStructureInitializer);
+    ASSERT_EQ(ret, thrd_success, "unable to create GetCtor thread");
+    ret = thrd_join(thread, nullptr);
+    ASSERT_EQ(ret, thrd_success, "unable to join GetCtor thread");
+    ASSERT_EQ(info->ctor.x, std::numeric_limits<uint64_t>::max(), "%s", kBackgroundThreadError);
+}
+
+TEST(ExecutableTlsTest, StructureInitalizierInMain) {
+    auto info = std::make_unique<CtorInfo>();
+    ASSERT_EQ(GetCtor(info.get()), 0);
+    ASSERT_EQ(info->ctor.x, std::numeric_limits<uint64_t>::max(), "%s", kMainThreadError);
+}
+
+
 
 __attribute__((aligned(0x1000))) thread_local int aligned_var = 123;
 
-bool CheckInitializers() {
-    BEGIN_TEST;
+struct AlignmentInfo {
+    int* aligned_var_addr;
+    int  aligned_var_value;
+};
 
-    ASSERT_EQ(u1, true, "unexpected initialized value");
-    ASSERT_EQ(u8, UINT8_MAX, "unexpected initialized value");
-    ASSERT_EQ(u16, UINT16_MAX, "unexpected initialized value");
-    ASSERT_EQ(u32, UINT32_MAX, "unexpected initialized value");
-    ASSERT_EQ(u64, UINT64_MAX, "unexpected initialized value");
-    ASSERT_EQ(uptr, UINTPTR_MAX, "unexpected initialized value");
-    ASSERT_EQ(i8, INT8_MAX, "unexpected initialized value");
-    ASSERT_EQ(i16, INT16_MAX, "unexpected initialized value");
-    ASSERT_EQ(i32, INT32_MAX, "unexpected initialized value");
-    ASSERT_EQ(i64, INT64_MAX, "unexpected initialized value");
-    ASSERT_EQ(iptr, INTPTR_MAX, "unexpected initialized value");
-    ASSERT_EQ(f32, FLT_MAX, "unexpected initialized value");
-    ASSERT_EQ(f64, DBL_MAX, "unexpected initialized value");
-    ASSERT_EQ(ptr, &ptr, "unexpected initialized value");
-
-    ASSERT_EQ(bits.bits0, 0x1ffu, "unexpected initialized value");
-    ASSERT_EQ(bits.bits1, 0x1ffu, "unexpected initialized value");
-    ASSERT_EQ(bits.bits2, 0x1ffu, "unexpected initialized value");
-    ASSERT_EQ(bits.bits3, 0x1ffu, "unexpected initialized value");
-    ASSERT_EQ(bits.bits4, 0x1ffu, "unexpected initialized value");
-    ASSERT_EQ(bits.bits5, 0x1ffu, "unexpected initialized value");
-    ASSERT_EQ(bits.bits6, 0x1ffu, "unexpected initialized value");
-    ASSERT_EQ(bits.f64, DBL_MAX, "unexpected initialized value");
-    ASSERT_EQ(bits.bits7, 0x1ffu, "unexpected initialized value");
-    ASSERT_EQ(bits.bits8, 0x1ffu, "unexpected initialized value");
-    ASSERT_EQ(bits.bits9, 0x1ffu, "unexpected initialized value");
-    ASSERT_EQ(bits.bits10, 0x1ffu, "unexpected initialized value");
-    ASSERT_EQ(bits.bits11, 0x1ffu, "unexpected initialized value");
-    ASSERT_EQ(bits.bits12, 0x1ffu, "unexpected initialized value");
-    ASSERT_EQ(bits.bits13, 0x1ffu, "unexpected initialized value");
-
-    for (auto& byte : array)
-        ASSERT_EQ(byte, UINT8_MAX, "unexpected initialized value");
-
-    ASSERT_EQ(ctor.x_, UINT64_MAX, "unexpected initialized value");
-
-    uint8_t sum = 0u;
-    for (auto& byte : big_array)
-        sum |= byte;
-    ASSERT_EQ(sum, 0u, "unexpected initialized value");
-
+int GetAlignment(void* arg) {
+    auto info = reinterpret_cast<AlignmentInfo*>(arg);
     // TODO(ZX-1646): Make this work on ARM64.
-    EXPECT_EQ((uintptr_t)&aligned_var % 0x1000, 0);
-    EXPECT_EQ(aligned_var, 123);
-
-    END_TEST;
+    info->aligned_var_value = aligned_var;
+    info->aligned_var_addr = &aligned_var;
+    return 0;
 }
 
-bool TestArraySpam(uintptr_t idx) {
-    BEGIN_TEST;
+void VerifyAlignment(AlignmentInfo const* const info, const char *error_message) {
+    // TODO(ZX-1646): Make this work on ARM64.
+    EXPECT_EQ(reinterpret_cast<uintptr_t>(info->aligned_var_addr) % 0x1000, 0, "%s", error_message);
+    EXPECT_EQ(info->aligned_var_value, 123, "%s", error_message);
+}
 
-    for (uintptr_t iteration = 0; iteration < 100; ++iteration) {
-        auto starting_value = static_cast<uint8_t>(idx + iteration);
-        auto value = starting_value;
+constexpr char kThreadNameGetAlignment[]= "GetInitializers";
+
+TEST(ExecutableTlsTest, AlignmentInitializerInThread) {
+    thrd_t thread;
+    auto info = std::make_unique<AlignmentInfo>();
+
+    int ret = thrd_create_with_name(&thread, &GetAlignment, info.get(), kThreadNameGetAlignment);
+    ASSERT_EQ(ret, thrd_success, "unable to create GetAlignment thread");
+
+    ret = thrd_join(thread, nullptr);
+    ASSERT_EQ(ret, thrd_success, "unable to join GetAlignment thread");
+    VerifyAlignment(info.get(), kBackgroundThreadError);
+}
+
+TEST(ExecutableTlsTest, AlignmentInitializierInMain) {
+    auto info = std::make_unique<AlignmentInfo>();
+
+    ASSERT_EQ(GetAlignment(info.get()), 0);
+    VerifyAlignment(info.get(), kMainThreadError);
+}
+
+
+struct ArraySpamInfo {
+    uint8_t index;
+    bool    failure;
+    uint8_t failure_offset;
+    uint8_t actual_value;
+    uint8_t expected_value;
+};
+
+int TestArraySpam(void* arg) {
+    auto info = reinterpret_cast<ArraySpamInfo*>(arg);
+    for (uint8_t iteration = 0; iteration < 100; ++iteration) {
+        uint8_t starting_value = static_cast<uint8_t>(info->index + iteration);
+        uint8_t value = starting_value;
         for (auto& byte : array) {
             byte = value;
             ++value;
         }
         sched_yield();
         value = starting_value;
-        for (auto& byte : array) {
-            ASSERT_EQ(byte, value, "unexpected value read back!");
+        uint8_t failure_offset = 0;
+        for (const auto byte : array) {
+            if (byte != value) {
+                info->failure = true;
+                info->actual_value = byte;
+                info->expected_value = value;
+                info->failure_offset = failure_offset;
+                return 0;
+            }
             ++value;
+            ++failure_offset;
         }
     }
+    info->failure = false;
 
-    END_TEST;
-}
-
-int TestThread(void* arg) {
-    auto idx = reinterpret_cast<uintptr_t>(arg);
-
-    CheckInitializers();
-    TestArraySpam(idx);
     return 0;
 }
 
-bool ExecutableTlsTest() {
-    BEGIN_TEST;
+constexpr char kThreadNameArraySpam[]= "TestArraySpam";
 
-    constexpr uintptr_t thread_count = 64u;
-    thrd_t threads[thread_count];
-    for (uintptr_t idx = 0u; idx < thread_count; ++idx) {
-        auto arg = reinterpret_cast<void*>(idx);
-        int ret = thrd_create_with_name(&threads[idx], &TestThread, arg, "elf tls test");
-        ASSERT_EQ(ret, thrd_success, "unable to create test thread");
+TEST(ExecutableTlsTest, ArrayInitializerSpamThread) {
+    constexpr int kThreadCount = 64;
+    std::vector<thrd_t> threads(kThreadCount);
+    std::vector<ArraySpamInfo> info(kThreadCount);
+
+    for (uint8_t index = 0; index < kThreadCount; ++index) {
+        info[index].index = index;
+        int ret = thrd_create_with_name(&threads[index], &TestArraySpam, &info[index], kThreadNameArraySpam);
+        ASSERT_EQ(ret, thrd_success, "unable to create TestArraySpamInfo thread");
     }
-    for (uintptr_t idx = 0u; idx < thread_count; ++idx) {
-        int ret = thrd_join(threads[idx], nullptr);
-        ASSERT_EQ(ret, thrd_success, "unable to join test thread");
+    for (uint8_t index = 0; index < kThreadCount; ++index) {
+        int ret = thrd_join(threads[index], nullptr);
+        ASSERT_EQ(ret, thrd_success, "unable to join TestArraySpamInfo thread");
+        ASSERT_FALSE(info[index].failure, "%s: thread=%d ExpectedValue=%d ActualValue=%d FailureOffset=%d",
+                     kBackgroundThreadError, index, info[index].expected_value, info[index].actual_value,
+                     info[index].failure_offset);
     }
-
-    TestThread(nullptr);
-
-    END_TEST;
 }
 
-BEGIN_TEST_CASE(elf_tls_tests)
-RUN_TEST(ExecutableTlsTest)
-END_TEST_CASE(elf_tls_tests)
+TEST(ExecutableTlsTest, ArrayInitializierSpamMain) {
+    auto info = std::make_unique<ArraySpamInfo>();
+    ASSERT_EQ(TestArraySpam(info.get()), 0);
+    ASSERT_FALSE(info->failure, "%s: ExpectedValue=%d ActualValue=%d FailureOffset=%d",
+                 kMainThreadError, info->expected_value, info->actual_value, info->failure_offset);
+}
+
+} // namespace