blob: 9a243e3931f768f279a89dffbda43ac4d1c3b3c8 [file] [log] [blame]
// Copyright 2016 Google Inc. All Rights Reserved.
//
// 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
//
// http://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 <map>
#include <string>
#include "nb/scoped_ptr.h"
#include "nb/test_thread.h"
#include "nb/thread_local_object.h"
#include "starboard/atomic.h"
#include "starboard/common/mutex.h"
#include "starboard/thread.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace nb {
namespace {
// Simple class that counts the number of instances alive.
struct CountsInstances {
CountsInstances() { s_instances_.increment(); }
~CountsInstances() { s_instances_.decrement(); }
static starboard::atomic_int32_t s_instances_;
static int NumInstances() { return s_instances_.load(); }
static void ResetNumInstances() { s_instances_.exchange(0); }
};
starboard::atomic_int32_t CountsInstances::s_instances_(0);
// A simple thread that just creates the an object from the supplied
// ThreadLocalObject<T> and then exits.
template <typename TYPE>
class CreateThreadLocalObjectThenExit : public TestThread {
public:
explicit CreateThreadLocalObjectThenExit(
ThreadLocalObject<TYPE>* tlo) : tlo_(tlo) {}
virtual void Run() {
// volatile as a defensive measure to prevent compiler from optimizing this
// statement out.
volatile TYPE* val = tlo_->GetOrCreate();
}
ThreadLocalObject<TYPE>* tlo_;
};
// A simple thread that just deletes the object supplied on a thread and then
// exists.
template <typename TYPE>
class DestroyTypeOnThread : public TestThread {
public:
explicit DestroyTypeOnThread(TYPE* ptr)
: ptr_(ptr) {}
virtual void Run() {
ptr_.reset(NULL); // Destroys the object.
}
private:
nb::scoped_ptr<TYPE> ptr_;
};
// A simple struct that wraps an integer, and cannot be default constructed.
struct IntegerWrapper {
explicit IntegerWrapper(int value) : value(value) {}
int value;
};
// Tests the expectation that a ThreadLocalObject can be simply used by
// the main thread.
TEST(ThreadLocalObject, MainThread) {
ThreadLocalObject<bool> tlo_bool;
EXPECT_TRUE(NULL == tlo_bool.GetIfExists());
bool* the_bool = tlo_bool.GetOrCreate();
EXPECT_TRUE(the_bool != NULL);
EXPECT_FALSE(*the_bool);
*the_bool = true;
EXPECT_TRUE(*(tlo_bool.GetIfExists()));
}
// The same as |MainThread|, except with |tlo_bool| initialized to |true|
// rather than |false|, via passing |true| to |GetOrCreate|.
TEST(ThreadLocalObject, MainThreadWithArg) {
ThreadLocalObject<bool> tlo_bool;
EXPECT_TRUE(NULL == tlo_bool.GetIfExists());
bool* the_bool = tlo_bool.GetOrCreate(true);
EXPECT_TRUE(the_bool != NULL);
EXPECT_TRUE(*the_bool);
*the_bool = false;
EXPECT_FALSE(*(tlo_bool.GetIfExists()));
}
// Tests the expectation that a ThreadLocalObject can be used on
// complex objects type (i.e. non pod types).
TEST(ThreadLocalObject, MainThreadComplexObject) {
typedef std::map<std::string, int> Map;
ThreadLocalObject<Map> map_tlo;
EXPECT_FALSE(map_tlo.GetIfExists());
ASSERT_TRUE(map_tlo.GetOrCreate());
Map* map = map_tlo.GetIfExists();
const Map* const_map = map_tlo.GetIfExists();
ASSERT_TRUE(map);
ASSERT_TRUE(const_map);
// If the object is properly constructed then this find operation
// should succeed.
(*map)["my string"] = 15;
ASSERT_EQ(15, (*map)["my string"]);
}
// The complex object analog of |MainThreadWithArg|.
TEST(ThreadLocalObject, MainThreadComplexObjectWithArg) {
ThreadLocalObject<IntegerWrapper> integer_wrapper_tlo;
EXPECT_FALSE(integer_wrapper_tlo.GetIfExists());
ASSERT_TRUE(integer_wrapper_tlo.GetOrCreate(14));
IntegerWrapper* integer_wrapper = integer_wrapper_tlo.GetIfExists();
const IntegerWrapper* const_integer_wrapper =
integer_wrapper_tlo.GetIfExists();
ASSERT_TRUE(integer_wrapper);
ASSERT_TRUE(const_integer_wrapper);
integer_wrapper->value = 15;
ASSERT_EQ(15, integer_wrapper->value);
}
// Tests that when a ThreadLocalObject is destroyed on the main thread that
// the pointers it contained are also destroyed.
TEST(ThreadLocalObject, DestroysObjectOnTLODestruction) {
CountsInstances::ResetNumInstances();
typedef ThreadLocalObject<CountsInstances> TLO;
// Create the TLO object and then immediately destroy it.
nb::scoped_ptr<TLO> tlo_ptr(new TLO);
tlo_ptr->GetOrCreate(); // Instantiate the internal object.
EXPECT_EQ(1, CountsInstances::NumInstances());
tlo_ptr.reset(NULL); // Should destroy all outstanding allocs.
// Now the TLO is destroyed and therefore the destructor should run on the
// internal object.
EXPECT_EQ(0, CountsInstances::NumInstances());
CountsInstances::ResetNumInstances();
}
// Tests the expectation that the object can be released and that the pointer
// won't be deleted when the ThreadLocalObject that created it is destroyed.
TEST(ThreadLocalObject, ReleasesObject) {
CountsInstances::ResetNumInstances();
typedef ThreadLocalObject<CountsInstances> TLO;
nb::scoped_ptr<TLO> tlo_ptr(new TLO);
// Instantiate the internal object.
tlo_ptr->GetOrCreate();
// Now release the pointer into the container.
nb::scoped_ptr<CountsInstances> last_ref(tlo_ptr->Release());
// Destroying the TLO should not trigger the destruction of the object,
// because it was released.
tlo_ptr.reset(NULL);
// 1 instance left, which is held in last_ref.
EXPECT_EQ(1, CountsInstances::NumInstances());
last_ref.reset(NULL); // Now the object should be destroyed and the
// instance count drops to 0.
EXPECT_EQ(0, CountsInstances::NumInstances());
CountsInstances::ResetNumInstances();
}
// Tests the expectation that a thread that creates an object from
// the ThreadLocalObject store will automatically be destroyed by the
// thread joining.
TEST(ThreadLocalObject, ThreadJoinDestroysObject) {
CountsInstances::ResetNumInstances();
typedef ThreadLocalObject<CountsInstances> TLO;
nb::scoped_ptr<TLO> tlo(new TLO);
{
TestThread* thread =
new CreateThreadLocalObjectThenExit<CountsInstances>(tlo.get());
thread->Start();
thread->Join();
// Once the thread joins, the object should be deleted and the instance
// counter falls to 0.
EXPECT_EQ(0, CountsInstances::NumInstances());
delete thread;
}
tlo.reset(NULL); // Now TLO destructor runs.
EXPECT_EQ(0, CountsInstances::NumInstances());
CountsInstances::ResetNumInstances();
}
// Tests the expectation that objects created on the main thread are not
// leaked.
TEST(ThreadLocalObject, NoLeaksOnMainThread) {
CountsInstances::ResetNumInstances();
ThreadLocalObject<CountsInstances>* tlo =
new ThreadLocalObject<CountsInstances>;
tlo->EnableDestructionByAnyThread();
// Creates the object on the main thread. This is important because the
// main thread will never join and therefore at-exit functions won't get
// run.
CountsInstances* main_thread_object = tlo->GetOrCreate();
EXPECT_EQ(1, CountsInstances::NumInstances());
// Thread will simply create the thread local object (CountsInstances)
// and then return.
nb::scoped_ptr<TestThread> thread_ptr(
new CreateThreadLocalObjectThenExit<CountsInstances>(tlo));
thread_ptr->Start(); // Object is now created.
thread_ptr->Join(); // ...then destroyed.
thread_ptr.reset(NULL);
// Only main_thread_object should be alive now, therefore the count is 1.
EXPECT_EQ(1, CountsInstances::NumInstances());
// We COULD destroy the TLO on the main thread, but to be even fancier lets
// create a thread that will destroy the object on a back ground thread.
// The end result is that the TLO entry should be cleared out.
thread_ptr.reset(
new DestroyTypeOnThread<ThreadLocalObject<CountsInstances> >(tlo));
thread_ptr->Start();
thread_ptr->Join();
thread_ptr.reset(NULL);
// Now we expect that number of instances to be 0.
EXPECT_EQ(0, CountsInstances::NumInstances());
CountsInstances::ResetNumInstances();
}
} // anonymous namespace
} // nb namespace