Adding C++ tests that drive LLDB from multiple threads

- test_breakpoint_callback -- filed llvm.org/pr-16000
- test_listener_resume -- resume a process from a thread waiting on SBListener
- test_listener_event_description -- SBEvent description from SBListener thread
- test_listener_event_process -- query process/thread/stack info from SBListener thread

llvm-svn: 181819
This commit is contained in:
Daniel Malea 2013-05-14 19:13:25 +00:00
parent 04e5d58c8d
commit b2132553d9
11 changed files with 520 additions and 0 deletions

View File

@ -0,0 +1,7 @@
LEVEL = ../../make
CXX_SOURCES := main.cpp
clean: OBJECTS+=*.d.* *.d *.o *.pyc *.dSYM
include $(LEVEL)/Makefile.rules

View File

@ -0,0 +1,73 @@
"""Test the lldb public C++ api breakpoint callbacks. """
import os, re, StringIO
import unittest2
from lldbtest import *
import lldbutil
import subprocess
class SBBreakpointCallbackCase(TestBase):
mydir = os.path.join("api", "multithreaded")
def setUp(self):
TestBase.setUp(self)
self.lib_dir = os.environ["LLDB_LIB_DIR"]
self.inferior = 'inferior_program'
if self.getArchitecture() != "i386":
self.buildProgram('inferior.cpp', self.inferior)
self.addTearDownHook(lambda: os.remove(self.inferior))
@unittest2.expectedFailure # llvm.org/pr-1600: SBBreakpoint.SetCallback() does nothing
@skipIfi386
def test_breakpoint_callback(self):
"""Test the that SBBreakpoint callback is invoked when a breakpoint is hit. """
self.build_and_test('driver.cpp test_breakpoint_callback.cpp',
'test_breakpoint_callback')
@skipIfi386
def test_sb_api_listener_event_description(self):
""" Test the description of an SBListener breakpoint event is valid."""
self.build_and_test('driver.cpp listener_test.cpp test_listener_event_description.cpp',
'test_listener_event_description')
pass
@skipIfi386
def test_sb_api_listener_event_process_state(self):
""" Test that a registered SBListener receives events when a process
changes state.
"""
self.build_and_test('driver.cpp listener_test.cpp test_listener_event_process_state.cpp',
'test_listener_event_process_state')
pass
@skipIfi386
def test_sb_api_listener_resume(self):
""" Test that a process can be resumed from a non-main thread. """
self.build_and_test('driver.cpp listener_test.cpp test_listener_resume.cpp',
'test_listener_resume')
pass
def build_and_test(self, sources, test_name, args = None):
""" Build LLDB test from sources, and run expecting 0 exit code """
self.buildDriver(sources, test_name)
self.addTearDownHook(lambda: os.remove(test_name))
exe = [os.path.join(os.getcwd(), test_name), self.inferior]
if self.TraceOn():
print "Running test %s" % " ".join(exe)
check_call(exe, env={self.dylibPath : self.getLLDBLibraryEnvVal()})
def build_program(self, sources, program):
return self.buildDriver(sources, program)
if __name__ == '__main__':
import atexit
lldb.SBDebugger.Initialize()
atexit.register(lambda: lldb.SBDebugger.Terminate())
unittest2.main()

View File

@ -0,0 +1,67 @@
#ifndef LLDB_TEST_API_COMMON_H
#define LLDB_TEST_API_COMMON_H
#include <condition_variable>
#include <chrono>
#include <exception>
#include <iostream>
#include <mutex>
#include <string>
#include <queue>
#include <unistd.h>
/// Simple exception class with a message
struct Exception : public std::exception
{
std::string s;
Exception(std::string ss) : s(ss) {}
const char* what() const throw() { return s.c_str(); }
};
// Synchronized data structure for listener to send events through
template<typename T>
class multithreaded_queue {
std::condition_variable m_condition;
std::mutex m_mutex;
std::queue<T> m_data;
bool m_notified;
public:
void push(T e) {
std::lock_guard<std::mutex> lock(m_mutex);
m_data.push(e);
m_notified = true;
m_condition.notify_all();
}
T pop(int timeout_seconds, bool &success) {
int count = 0;
while (count < timeout_seconds) {
std::unique_lock<std::mutex> lock(m_mutex);
if (!m_data.empty()) {
m_notified = false;
T ret = m_data.front();
m_data.pop();
success = true;
return ret;
} else if (!m_notified)
m_condition.wait_for(lock, std::chrono::seconds(1));
count ++;
}
success = false;
return T();
}
};
/// Allocates a char buffer with the current working directory on Linux/Darwin
inline char* get_working_dir() {
#ifdef __APPLE__
return getwd(0);
#else
return get_current_dir_name();
#endif
}
#endif // LLDB_TEST_API_COMMON_H

View File

@ -0,0 +1,38 @@
/// LLDB C API Test Driver
#include <algorithm>
#include <iostream>
#include <iterator>
#include <string>
#include <vector>
#include "lldb-headers.h"
#include "common.h"
using namespace std;
using namespace lldb;
void test(SBDebugger &dbg, std::vector<string> args);
int main(int argc, char** argv) {
int code = 0;
SBDebugger::Initialize();
SBDebugger dbg = SBDebugger::Create();
try {
if (!dbg.IsValid())
throw Exception("invalid debugger");
vector<string> args(argv + 1, argv + argc);
test(dbg, args);
} catch (Exception &e) {
cout << "ERROR: " << e.what() << endl;
code = 1;
}
SBDebugger::Destroy(dbg);
return code;
}

View File

@ -0,0 +1,17 @@
#include <iostream>
using namespace std;
int next() {
static int i = 0;
cout << "incrementing " << i << endl;
return ++i;
}
int main() {
int i = 0;
while (i < 5)
i = next();
return 0;
}

View File

@ -0,0 +1,74 @@
// LLDB test snippet that registers a listener with a process that hits
// a breakpoint.
#include <atomic>
#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include "lldb-headers.h"
#include "common.h"
using namespace lldb;
using namespace std;
void listener_func();
void check_listener(SBDebugger &dbg);
// Listener thread and related variables
atomic<bool> g_done;
SBListener g_listener("test-listener");
thread g_listener_thread;
void shutdown_listener() {
g_done.store(true);
if (g_listener_thread.joinable())
g_listener_thread.join();
}
void test(SBDebugger &dbg, std::vector<string> args) {
try {
g_done.store(false);
SBTarget target = dbg.CreateTarget(args.at(0).c_str());
if (!target.IsValid()) throw Exception("invalid target");
SBBreakpoint breakpoint = target.BreakpointCreateByName("next");
if (!breakpoint.IsValid()) throw Exception("invalid breakpoint");
std::unique_ptr<char> working_dir(get_working_dir());
SBError error;
SBProcess process = target.Launch(g_listener,
0, 0, 0, 0, 0,
working_dir.get(),
0,
false,
error);
if (!error.Success())
throw Exception("Error launching process.");
/* FIXME: the approach below deadlocks
SBProcess process = target.LaunchSimple(0, 0, working_dir.get());
// get debugger listener (which is attached to process by default)
g_listener = dbg.GetListener();
*/
// FIXME: because a listener is attached to the process at launch-time,
// registering the listener below results in two listeners being attached,
// which is not supported by LLDB.
// register listener
// process.GetBroadcaster().AddListener(g_listener,
// SBProcess::eBroadcastBitStateChanged);
// start listener thread
g_listener_thread = thread(listener_func);
check_listener(dbg);
} catch (Exception &e) {
shutdown_listener();
throw e;
}
shutdown_listener();
}

View File

@ -0,0 +1,11 @@
#ifndef LLDB_HEADERS_H
#define LLDB_HEADERS_H
#ifdef __APPLE__
#include <LLDB/LLDB.h>
#else
#include "lldb/API/LLDB.h"
#endif
#endif // LLDB_HEADERS_H

View File

@ -0,0 +1,48 @@
// LLDB C++ API Test: verify that the function registered with
// SBBreakpoint.SetCallback() is invoked when a breakpoint is hit.
#include <mutex>
#include <iostream>
#include <vector>
#include <string>
#include "lldb-headers.h"
#include "common.h"
using namespace std;
using namespace lldb;
mutex g_mutex;
condition_variable g_condition;
int g_breakpoint_hit_count = 0;
bool BPCallback (void *baton,
SBProcess &process,
SBThread &thread,
SBBreakpointLocation &location) {
lock_guard<mutex> lock(g_mutex);
g_breakpoint_hit_count += 1;
g_condition.notify_all();
return true;
}
void test(SBDebugger &dbg, vector<string> args) {
SBTarget target = dbg.CreateTarget(args.at(0).c_str());
if (!target.IsValid()) throw Exception("invalid target");
SBBreakpoint breakpoint = target.BreakpointCreateByName("next");
if (!breakpoint.IsValid()) throw Exception("invalid breakpoint");
breakpoint.SetCallback(BPCallback, 0);
std::unique_ptr<char> working_dir = get_working_dir();
SBProcess process = target.LaunchSimple(0, 0, working_dir.get());
{
unique_lock<mutex> lock(g_mutex);
g_condition.wait_for(lock, chrono::seconds(5));
if (g_breakpoint_hit_count != 1)
throw Exception("Breakpoint hit count expected to be 1");
}
}

View File

@ -0,0 +1,64 @@
// LLDB C++ API Test: verify the event description that is received by an
// SBListener object registered with a process with a breakpoint.
#include <atomic>
#include <array>
#include <iostream>
#include <string>
#include <thread>
#include "lldb-headers.h"
#include "common.h"
using namespace lldb;
using namespace std;
// listener thread control
extern atomic<bool> g_done;
multithreaded_queue<string> g_event_descriptions;
extern SBListener g_listener;
void listener_func() {
while (!g_done) {
SBEvent event;
bool got_event = g_listener.WaitForEvent(1, event);
if (got_event) {
if (!event.IsValid())
throw Exception("event is not valid in listener thread");
SBStream description;
event.GetDescription(description);
string str(description.GetData());
g_event_descriptions.push(str);
}
}
}
void check_listener(SBDebugger &dbg) {
array<string, 2> expected_states = {"running", "stopped"};
for(string & state : expected_states) {
bool got_description = false;
string desc = g_event_descriptions.pop(5, got_description);
if (!got_description)
throw Exception("Did not get expected event description");
if (desc.find("state-changed") == desc.npos)
throw Exception("Event description incorrect: missing 'state-changed'");
string state_search_str = "state = " + state;
if (desc.find(state_search_str) == desc.npos)
throw Exception("Event description incorrect: expected state "
+ state
+ " but desc was "
+ desc);
if (desc.find("pid = ") == desc.npos)
throw Exception("Event description incorrect: missing process pid");
}
}

View File

@ -0,0 +1,68 @@
// LLDB C++ API Test: verify the event description as obtained by calling
// SBEvent::GetCStringFromEvent that is received by an
// SBListener object registered with a process with a breakpoint.
#include <atomic>
#include <iostream>
#include <string>
#include <thread>
#include "lldb-headers.h"
#include "common.h"
using namespace lldb;
using namespace std;
// listener thread control
extern atomic<bool> g_done;
multithreaded_queue<string> g_thread_descriptions;
multithreaded_queue<string> g_frame_functions;
extern SBListener g_listener;
void listener_func() {
while (!g_done) {
SBEvent event;
bool got_event = g_listener.WaitForEvent(1, event);
if (got_event) {
if (!event.IsValid())
throw Exception("event is not valid in listener thread");
// send process description
SBProcess process = SBProcess::GetProcessFromEvent(event);
SBStream description;
for (int i = 0; i < process.GetNumThreads(); ++i) {
// send each thread description
description.Clear();
SBThread thread = process.GetThreadAtIndex(i);
thread.GetDescription(description);
g_thread_descriptions.push(description.GetData());
// send each frame function name
uint32_t num_frames = thread.GetNumFrames();
for(int j = 0; j < num_frames; ++j) {
const char* function_name = thread.GetFrameAtIndex(j).GetFunction().GetName();
if (function_name)
g_frame_functions.push(function_name);
}
}
}
}
}
void check_listener(SBDebugger &dbg) {
// check thread description
bool got_description = false;
string desc = g_thread_descriptions.pop(5, got_description);
if (!got_description)
throw Exception("Expected at least one thread description string");
// check at least one frame has a function name
desc = g_frame_functions.pop(5, got_description);
if (!got_description)
throw Exception("Expected at least one frame function name string");
}

View File

@ -0,0 +1,53 @@
// LLDB C++ API Test: verify the event description as obtained by calling
// SBEvent::GetCStringFromEvent that is received by an
// SBListener object registered with a process with a breakpoint.
#include <atomic>
#include <iostream>
#include <string>
#include <thread>
#include "lldb-headers.h"
#include "common.h"
using namespace lldb;
using namespace std;
// listener thread control
extern atomic<bool> g_done;
// used by listener thread to communicate a successful process continue command
// back to the checking thread.
multithreaded_queue<bool> g_process_started;
extern SBListener g_listener;
void listener_func() {
while (!g_done) {
SBEvent event;
bool got_event = g_listener.WaitForEvent(1, event);
if (got_event) {
if (!event.IsValid())
throw Exception("event is not valid in listener thread");
SBProcess process = SBProcess::GetProcessFromEvent(event);
if (process.GetState() == eStateStopped) {
SBError error = process.Continue();
if (!error.Success())
throw Exception(string("Cannot continue process from listener thread: ")
+ error.GetCString());
g_process_started.push(true);
}
}
}
}
void check_listener(SBDebugger &dbg) {
bool got_message = false;
while (!got_message)
g_process_started.pop(5, got_message);
g_done = true;
}