blob: e734c2f09ee0bbb551d7190480881c97de29ca6a [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 "nb/memory_scope.h"
#include "starboard/mutex.h"
#include "nb/thread_local_object.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace nb {
namespace {
bool StarboardAllowsMemoryTracking() {
#if defined(STARBOARD_ALLOWS_MEMORY_TRACKING)
return true;
#else
return false;
#endif
}
// This is a memory scope reporter that is compatible
// with the MemoryScopeReporter.
class TestMemoryScopeReporter {
public:
typedef std::vector<NbMemoryScopeInfo*> MemoryScopeVector;
TestMemoryScopeReporter() {
memory_scope_reporter_ = CreateMemoryScopeReporter();
}
NbMemoryScopeReporter* memory_scope_reporter() {
return &memory_scope_reporter_;
}
MemoryScopeVector* stack_thread_local() { return stack_tlo_.GetOrCreate(); }
void OnPushMemoryScope(NbMemoryScopeInfo* memory_scope) {
stack_thread_local()->push_back(memory_scope);
}
void OnPopMemoryScope() {
MemoryScopeVector* stack = stack_thread_local();
if (!stack->empty()) {
stack->pop_back();
} else {
ADD_FAILURE_AT(__FILE__, __LINE__)
<< " stack was empty and could not be popped.";
}
}
private:
static void OnPushMemoryScopeCallback(void* context,
NbMemoryScopeInfo* info) {
TestMemoryScopeReporter* t = static_cast<TestMemoryScopeReporter*>(context);
t->OnPushMemoryScope(info);
}
static void OnPopMemoryScopeCallback(void* context) {
TestMemoryScopeReporter* t = static_cast<TestMemoryScopeReporter*>(context);
t->OnPopMemoryScope();
}
NbMemoryScopeReporter CreateMemoryScopeReporter() {
NbMemoryScopeReporter reporter = {OnPushMemoryScopeCallback,
OnPopMemoryScopeCallback, this};
return reporter;
}
NbMemoryScopeReporter memory_scope_reporter_;
ThreadLocalObject<MemoryScopeVector> stack_tlo_;
};
// A test framework for testing the Pushing & popping memory scopes.
// The key feature here is that reporter is setup on the first test
// instance and torn down after the last test has run.
class MemoryScopeReportingTest : public ::testing::Test {
public:
TestMemoryScopeReporter* test_memory_reporter() { return s_reporter_; }
bool reporting_enabled() const { return s_reporter_enabled_; }
protected:
static void SetUpTestCase() {
if (!s_reporter_) {
s_reporter_ = new TestMemoryScopeReporter;
}
s_reporter_enabled_ =
NbSetMemoryScopeReporter(s_reporter_->memory_scope_reporter());
EXPECT_EQ(StarboardAllowsMemoryTracking(), s_reporter_enabled_)
<< "Expected the memory scope reporter to be enabled whenever "
"starboard memory tracking is allowed.";
}
static void TearDownTestCase() {
// The reporter itself is not deleted because other threads could
// be traversing through it's data structures. It's better just to leave
// the object alive for the purposes of this unit test and set the pointer
// to NULL.
// This is done in order to make the MemoryScopeReport object lock free.
// This increases performance and reduces complexity of design.
NbSetMemoryScopeReporter(NULL);
}
// Per test setup.
virtual void SetUp() {
test_memory_reporter()->stack_thread_local()->clear();
}
static TestMemoryScopeReporter* s_reporter_;
static bool s_reporter_enabled_;
};
TestMemoryScopeReporter* MemoryScopeReportingTest::s_reporter_ = NULL;
bool MemoryScopeReportingTest::s_reporter_enabled_;
///////////////////////////////////////////////////////////////////////////////
// TESTS.
// There are two sets of tests: POSITIVE and NEGATIVE.
// The positive tests are active when STARBOARD_ALLOWS_MEMORY_TRACKING is
// defined and test that memory tracking is enabled.
// NEGATIVE tests ensure that tracking is disabled when
// STARBOARD_ALLOWS_MEMORY_TRACKING is not defined.
// When adding new tests:
// POSITIVE tests are named normally.
// NEGATIVE tests are named with "No" prefixed to the beginning.
// Example:
// TEST_F(MemoryScopeReportingTest, PushPop) <--- POSITIVE test.
// TEST_F(MemoryScopeReportingTest, NoPushPop) <- NEGATIVE test.
// All positive & negative tests are grouped together.
///////////////////////////////////////////////////////////////////////////////
#if defined(STARBOARD_ALLOWS_MEMORY_TRACKING)
// These are POSITIVE tests, which test the expectation that when the define
// STARBOARD_ALLOWS_MEMORY_TRACKING is active that the memory scope reporter
// will receive memory scope notifications.
// Tests the assumption that the SbMemoryAllocate and SbMemoryDeallocate
// will report memory allocations.
TEST_F(MemoryScopeReportingTest, PushPop) {
ASSERT_TRUE(reporting_enabled());
const int line_number = __LINE__;
const char* file_name = __FILE__;
const char* function_name = __FUNCTION__;
NbMemoryScopeInfo info = {0, // Cached value (null).
"Javascript", // Name of the memory scope.
file_name, // Filename that invoked this.
line_number, // Line number.
function_name, // Function name.
true}; // true allows caching.
NbPushMemoryScope(&info);
ASSERT_FALSE(test_memory_reporter()->stack_thread_local()->empty());
NbMemoryScopeInfo* info_ptr =
test_memory_reporter()->stack_thread_local()->front();
EXPECT_EQ(&info, info_ptr);
EXPECT_STREQ(info.file_name_, file_name);
EXPECT_STREQ(info.function_name_, function_name);
EXPECT_EQ(info.line_number_, line_number);
NbPopMemoryScope();
EXPECT_TRUE(test_memory_reporter()->stack_thread_local()->empty());
}
// Tests the expectation that the memory reporting macros will
// push/pop memory regions and will also correctly bind to the
// file, linenumber and also the function name.
TEST_F(MemoryScopeReportingTest, Macros) {
ASSERT_TRUE(reporting_enabled());
// There should be no leftover stack objects.
EXPECT_TRUE(test_memory_reporter()->stack_thread_local()->empty());
{
const int line_before = __LINE__;
TRACK_MEMORY_SCOPE("TestMemoryScope");
const int predicted_line = line_before + 1;
NbMemoryScopeInfo* info_ptr =
test_memory_reporter()->stack_thread_local()->front();
// TRACK_MEMORY_SCOPE is defined to allow caching.
EXPECT_EQ(true, info_ptr->allows_caching_);
// The cached_handle_ is not mutated by TestMemoryScopeReporter so
// therefore it should be the default value of 0.
EXPECT_EQ(0, info_ptr->cached_handle_);
EXPECT_STREQ("TestMemoryScope", info_ptr->memory_scope_name_);
EXPECT_STREQ(__FILE__, info_ptr->file_name_);
EXPECT_EQ(predicted_line, info_ptr->line_number_);
EXPECT_STREQ(__FUNCTION__, info_ptr->function_name_);
}
// Expect that the stack object is now empty again.
EXPECT_TRUE(test_memory_reporter()->stack_thread_local()->empty());
}
#else // !defined(STARBOARD_ALLOWS_MEMORY_TRACKING)
// These are NEGATIVE tests, which test the expectation that when the
// STARBOARD_ALLOWS_MEMORY_TRACKING is undefined that the memory scope reprter
// does not receive memory scope notifications.
// Tests the expectation that push pop does not send notifications to the
// reporter when disabled.
TEST_F(MemoryScopeReportingTest, NoPushPop) {
ASSERT_FALSE(reporting_enabled());
const int line_number = __LINE__;
const char* file_name = __FILE__;
const char* function_name = __FUNCTION__;
NbMemoryScopeInfo info = {0, // Cached value (null).
"Javascript", // Name of the memory scope.
file_name, // Filename that invoked this.
line_number, // Line number.
function_name, // Function name.
true}; // true allows caching.
NbPushMemoryScope(&info);
ASSERT_FALSE(test_memory_reporter()->stack_thread_local()->empty());
NbMemoryScopeInfo* info_ptr =
test_memory_reporter()->stack_thread_local()->front();
EXPECT_EQ(&info, info_ptr);
EXPECT_STREQ(info.file_name_, file_name);
EXPECT_STREQ(info.function_name_, function_name);
EXPECT_EQ(info.line_number_, line_number);
NbPopMemoryScope();
EXPECT_TRUE(test_memory_reporter()->stack_thread_local()->empty());
}
// Tests the expectation that the memory reporting macros are disabled when
// memory tracking is not allowed.
TEST_F(MemoryScopeReportingTest, NoMacros) {
ASSERT_FALSE(reporting_enabled());
// Test that the macros do nothing when memory reporting has been
// disabled.
TRACK_MEMORY_SCOPE("InternalMemoryRegion");
ASSERT_TRUE(test_memory_reporter()->stack_thread_local()->empty())
<< "Memory reporting received notifications when it should be disabled.";
}
#endif
} // namespace.
} // namespace nb.