blob: 135a98569e7f0694be6d50dbea14eb8fa236599e [file] [log] [blame]
// Copyright 2012 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.
//
// Coverage client unittests.
#include "syzygy/agent/coverage/coverage.h"
#include "base/file_util.h"
#include "base/files/file_enumerator.h"
#include "base/files/scoped_temp_dir.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "syzygy/common/indexed_frequency_data.h"
#include "syzygy/trace/common/unittest_util.h"
#include "syzygy/trace/parse/unittest_util.h"
namespace agent {
namespace coverage {
namespace {
using ::common::IndexedFrequencyData;
using testing::_;
using testing::StrictMockParseEventHandler;
using trace::parser::Parser;
// This is the static basic-block frequency array that our coverage
// instrumentation will point to.
const uint32 kBasicBlockCount = 2;
uint8 bb_seen_array[kBasicBlockCount] = {};
// Force ourselves to have coverage data identical to that which would be
// injected by the coverage instrumentation transform.
IndexedFrequencyData coverage_data = {
::common::kBasicBlockCoverageAgentId,
::common::kBasicBlockFrequencyDataVersion,
bb_seen_array,
kBasicBlockCount,
1U, // num_columns.
1U, // frequency_size.
0U, // initialization_attempted.
IndexedFrequencyData::COVERAGE
};
MATCHER_P(ModuleAtAddress, module, "") {
return arg->module_base_addr == module;
}
MATCHER_P3(CoverageDataMatches, module, bb_count, bb_freqs, "") {
if (arg->module_base_addr != module)
return false;
if (arg->frequency_size != 1)
return false;
if (arg->num_entries != bb_count)
return false;
return ::memcmp(bb_freqs, arg->frequency_data, bb_count) == 0;
}
class CoverageClientTest : public testing::Test {
public:
CoverageClientTest()
: module_(NULL) {
}
virtual void SetUp() OVERRIDE {
testing::Test::SetUp();
coverage_data.initialization_attempted = 0U;
coverage_data.frequency_data = bb_seen_array;
::memset(bb_seen_array, 0, sizeof(bb_seen_array));
// Call trace files will be stuffed here.
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
service_.SetEnvironment();
}
virtual void TearDown() OVERRIDE {
UnloadDll();
// Stop the call trace service.
service_.Stop();
}
void StartService() {
service_.Start(temp_dir_.path());
}
void StopService() {
service_.Stop();
}
void ReplayLogs(size_t files_expected) {
// Stop the service if it's running.
ASSERT_NO_FATAL_FAILURE(StopService());
Parser parser;
ASSERT_TRUE(parser.Init(&handler_));
// Queue up the trace file(s) we engendered.
base::FileEnumerator enumerator(temp_dir_.path(),
false,
base::FileEnumerator::FILES);
size_t num_files = 0;
while (true) {
base::FilePath trace_file = enumerator.Next();
if (trace_file.empty())
break;
ASSERT_TRUE(parser.OpenTraceFile(trace_file));
++num_files;
}
EXPECT_EQ(files_expected, num_files);
if (num_files > 0)
ASSERT_TRUE(parser.Consume());
}
void LoadDll() {
ASSERT_TRUE(module_ == NULL);
ASSERT_TRUE(_indirect_penter_dllmain_ == NULL);
static const wchar_t kCallTraceDll[] = L"coverage_client.dll";
ASSERT_EQ(NULL, ::GetModuleHandle(kCallTraceDll));
module_ = ::LoadLibrary(kCallTraceDll);
ASSERT_TRUE(module_ != NULL);
_indirect_penter_dllmain_ =
::GetProcAddress(module_, "_indirect_penter_dllmain");
ASSERT_TRUE(_indirect_penter_dllmain_ != NULL);
}
void UnloadDll() {
if (module_ != NULL) {
ASSERT_TRUE(::FreeLibrary(module_));
module_ = NULL;
_indirect_penter_dllmain_ = NULL;
}
}
static BOOL WINAPI IndirectDllMain(HMODULE module,
DWORD reason,
LPVOID reserved);
static BOOL WINAPI DllMainThunk(HMODULE module,
DWORD reason,
LPVOID reserved);
protected:
// The directory where trace file output will be written.
base::ScopedTempDir temp_dir_;
// The handler to which the trace file parser will delegate events.
StrictMockParseEventHandler handler_;
// Our call trace service process instance.
testing::CallTraceService service_;
private:
HMODULE module_;
static FARPROC _indirect_penter_dllmain_;
};
FARPROC CoverageClientTest::_indirect_penter_dllmain_ = NULL;
BOOL WINAPI CoverageClientTest::IndirectDllMain(HMODULE module,
DWORD reason,
LPVOID reserved) {
return TRUE;
}
BOOL __declspec(naked) WINAPI CoverageClientTest::DllMainThunk(
HMODULE module, DWORD reason, LPVOID reserved) {
__asm {
push offset coverage_data
push IndirectDllMain
jmp _indirect_penter_dllmain_
}
}
void VisitBlock(size_t i) {
EXPECT_GT(coverage_data.num_entries, i);
static_cast<uint8*>(coverage_data.frequency_data)[i] = 1;
}
} // namespace
TEST_F(CoverageClientTest, NoServerNoCrash) {
ASSERT_NO_FATAL_FAILURE(LoadDll());
void* data = coverage_data.frequency_data;
EXPECT_TRUE(DllMainThunk(::GetModuleHandle(NULL), DLL_PROCESS_ATTACH, NULL));
// There should be no allocation.
ASSERT_EQ(data, coverage_data.frequency_data);
// Visiting blocks should not fail.
VisitBlock(0);
VisitBlock(1);
// Unload the DLL and stop the service.
ASSERT_NO_FATAL_FAILURE(UnloadDll());
// Replay the log. There should be none as we didn't initialize the client.
ASSERT_NO_FATAL_FAILURE(ReplayLogs(0));
}
TEST_F(CoverageClientTest, VisitOneBB) {
ASSERT_NO_FATAL_FAILURE(StartService());
ASSERT_NO_FATAL_FAILURE(LoadDll());
HMODULE self = ::GetModuleHandle(NULL);
DWORD process_id = ::GetCurrentProcessId();
DWORD thread_id = ::GetCurrentThreadId();
void* data = coverage_data.frequency_data;
EXPECT_TRUE(DllMainThunk(self, DLL_PROCESS_ATTACH, NULL));
// There should have been an allocation.
ASSERT_NE(data, coverage_data.frequency_data);
data = coverage_data.frequency_data;
// Calling the entry thunk repeatedly should not fail, and should not cause
// a reallocation.
EXPECT_TRUE(DllMainThunk(self, DLL_PROCESS_ATTACH, NULL));
ASSERT_EQ(data, coverage_data.frequency_data);
VisitBlock(0);
// Unload the DLL and stop the service.
ASSERT_NO_FATAL_FAILURE(UnloadDll());
const uint8 kExpectedCoverageData[kBasicBlockCount] = { 1, 0 };
// Set up expectations for what should be in the trace.
EXPECT_CALL(handler_, OnProcessStarted(_, process_id, _));
EXPECT_CALL(handler_, OnProcessAttach(_,
process_id,
thread_id,
ModuleAtAddress(self)));
EXPECT_CALL(handler_, OnIndexedFrequency(
_,
process_id,
thread_id,
CoverageDataMatches(self, kBasicBlockCount, kExpectedCoverageData)));
EXPECT_CALL(handler_, OnProcessEnded(_, process_id));
// Replay the log.
ASSERT_NO_FATAL_FAILURE(ReplayLogs(1));
}
} // namespace coverage
} // namespace agent