| //===-- TestCase.cpp --------------------------------------------*- C++ -*-===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "TestCase.h" |
| #include "Results.h" |
| #include "Xcode.h" |
| |
| using namespace lldb_perf; |
| |
| TestCase::TestCase() |
| : m_debugger(), m_target(), m_process(), m_thread(), m_listener(), |
| m_verbose(false), m_step(0) { |
| SBDebugger::Initialize(); |
| SBHostOS::ThreadCreated("<lldb-tester.app.main>"); |
| m_debugger = SBDebugger::Create(false); |
| m_listener = m_debugger.GetListener(); |
| m_listener.StartListeningForEventClass( |
| m_debugger, SBProcess::GetBroadcasterClass(), |
| SBProcess::eBroadcastBitStateChanged | SBProcess::eBroadcastBitInterrupt); |
| } |
| |
| static std::string GetShortOptionString(struct option *long_options) { |
| std::string option_string; |
| for (int i = 0; long_options[i].name != NULL; ++i) { |
| if (long_options[i].flag == NULL) { |
| option_string.push_back((char)long_options[i].val); |
| switch (long_options[i].has_arg) { |
| default: |
| case no_argument: |
| break; |
| case required_argument: |
| option_string.push_back(':'); |
| break; |
| case optional_argument: |
| option_string.append(2, ':'); |
| break; |
| } |
| } |
| } |
| return option_string; |
| } |
| |
| bool TestCase::Setup(int &argc, const char **&argv) { |
| bool done = false; |
| |
| struct option *long_options = GetLongOptions(); |
| |
| if (long_options) { |
| std::string short_option_string(GetShortOptionString(long_options)); |
| |
| #if __GLIBC__ |
| optind = 0; |
| #else |
| optreset = 1; |
| optind = 1; |
| #endif |
| while (!done) { |
| int long_options_index = -1; |
| const int short_option = ::getopt_long_only( |
| argc, const_cast<char **>(argv), short_option_string.c_str(), |
| long_options, &long_options_index); |
| |
| switch (short_option) { |
| case 0: |
| // Already handled |
| break; |
| |
| case -1: |
| done = true; |
| break; |
| |
| default: |
| done = !ParseOption(short_option, optarg); |
| break; |
| } |
| } |
| argc -= optind; |
| argv += optind; |
| } |
| |
| return false; |
| } |
| |
| bool TestCase::Launch(lldb::SBLaunchInfo &launch_info) { |
| lldb::SBError error; |
| m_process = m_target.Launch(launch_info, error); |
| if (!error.Success()) |
| fprintf(stderr, "error: %s\n", error.GetCString()); |
| if (m_process.IsValid()) |
| return true; |
| return false; |
| } |
| |
| bool TestCase::Launch(std::initializer_list<const char *> args) { |
| std::vector<const char *> args_vect(args); |
| args_vect.push_back(NULL); |
| lldb::SBLaunchInfo launch_info((const char **)&args_vect[0]); |
| return Launch(launch_info); |
| } |
| |
| void TestCase::SetVerbose(bool b) { m_verbose = b; } |
| |
| bool TestCase::GetVerbose() { return m_verbose; } |
| |
| void TestCase::Loop() { |
| while (true) { |
| bool call_test_step = false; |
| if (m_process.IsValid()) { |
| SBEvent evt; |
| m_listener.WaitForEvent(UINT32_MAX, evt); |
| StateType state = SBProcess::GetStateFromEvent(evt); |
| if (m_verbose) |
| printf("event = %s\n", SBDebugger::StateAsCString(state)); |
| if (SBProcess::GetRestartedFromEvent(evt)) { |
| if (m_verbose) { |
| const uint32_t num_threads = m_process.GetNumThreads(); |
| for (auto thread_index = 0; thread_index < num_threads; |
| thread_index++) { |
| SBThread thread(m_process.GetThreadAtIndex(thread_index)); |
| SBFrame frame(thread.GetFrameAtIndex(0)); |
| SBStream strm; |
| strm.RedirectToFileHandle(stdout, false); |
| frame.GetDescription(strm); |
| } |
| puts("restarted"); |
| } |
| call_test_step = false; |
| } else { |
| switch (state) { |
| case eStateInvalid: |
| case eStateDetached: |
| case eStateCrashed: |
| case eStateUnloaded: |
| break; |
| case eStateExited: |
| return; |
| case eStateConnected: |
| case eStateAttaching: |
| case eStateLaunching: |
| case eStateRunning: |
| case eStateStepping: |
| call_test_step = false; |
| break; |
| |
| case eStateStopped: |
| case eStateSuspended: { |
| call_test_step = true; |
| bool fatal = false; |
| bool selected_thread = false; |
| const uint32_t num_threads = m_process.GetNumThreads(); |
| for (auto thread_index = 0; thread_index < num_threads; |
| thread_index++) { |
| SBThread thread(m_process.GetThreadAtIndex(thread_index)); |
| SBFrame frame(thread.GetFrameAtIndex(0)); |
| SBStream strm; |
| strm.RedirectToFileHandle(stdout, false); |
| frame.GetDescription(strm); |
| bool select_thread = false; |
| StopReason stop_reason = thread.GetStopReason(); |
| if (m_verbose) |
| printf("tid = 0x%llx pc = 0x%llx ", thread.GetThreadID(), |
| frame.GetPC()); |
| switch (stop_reason) { |
| case eStopReasonNone: |
| if (m_verbose) |
| printf("none\n"); |
| break; |
| |
| case eStopReasonTrace: |
| select_thread = true; |
| if (m_verbose) |
| printf("trace\n"); |
| break; |
| |
| case eStopReasonPlanComplete: |
| select_thread = true; |
| if (m_verbose) |
| printf("plan complete\n"); |
| break; |
| case eStopReasonThreadExiting: |
| if (m_verbose) |
| printf("thread exiting\n"); |
| break; |
| case eStopReasonExec: |
| if (m_verbose) |
| printf("exec\n"); |
| break; |
| case eStopReasonInvalid: |
| if (m_verbose) |
| printf("invalid\n"); |
| break; |
| case eStopReasonException: |
| select_thread = true; |
| if (m_verbose) |
| printf("exception\n"); |
| fatal = true; |
| break; |
| case eStopReasonBreakpoint: |
| select_thread = true; |
| if (m_verbose) |
| printf("breakpoint id = %lld.%lld\n", |
| thread.GetStopReasonDataAtIndex(0), |
| thread.GetStopReasonDataAtIndex(1)); |
| break; |
| case eStopReasonWatchpoint: |
| select_thread = true; |
| if (m_verbose) |
| printf("watchpoint id = %lld\n", |
| thread.GetStopReasonDataAtIndex(0)); |
| break; |
| case eStopReasonSignal: |
| select_thread = true; |
| if (m_verbose) |
| printf("signal %d\n", (int)thread.GetStopReasonDataAtIndex(0)); |
| break; |
| } |
| if (select_thread && !selected_thread) { |
| m_thread = thread; |
| selected_thread = m_process.SetSelectedThread(thread); |
| } |
| } |
| if (fatal) { |
| if (m_verbose) |
| Xcode::RunCommand(m_debugger, "bt all", true); |
| exit(1); |
| } |
| } break; |
| } |
| } |
| } else { |
| call_test_step = true; |
| } |
| |
| if (call_test_step) { |
| do_the_call: |
| if (m_verbose) |
| printf("RUNNING STEP %d\n", m_step); |
| ActionWanted action; |
| TestStep(m_step, action); |
| m_step++; |
| SBError err; |
| switch (action.type) { |
| case ActionWanted::Type::eNone: |
| // Just exit and wait for the next event |
| break; |
| case ActionWanted::Type::eContinue: |
| err = m_process.Continue(); |
| break; |
| case ActionWanted::Type::eStepOut: |
| if (action.thread.IsValid() == false) { |
| if (m_verbose) { |
| Xcode::RunCommand(m_debugger, "bt all", true); |
| printf("error: invalid thread for step out on step %d\n", m_step); |
| } |
| exit(501); |
| } |
| m_process.SetSelectedThread(action.thread); |
| action.thread.StepOut(); |
| break; |
| case ActionWanted::Type::eStepOver: |
| if (action.thread.IsValid() == false) { |
| if (m_verbose) { |
| Xcode::RunCommand(m_debugger, "bt all", true); |
| printf("error: invalid thread for step over %d\n", m_step); |
| } |
| exit(500); |
| } |
| m_process.SetSelectedThread(action.thread); |
| action.thread.StepOver(); |
| break; |
| case ActionWanted::Type::eRelaunch: |
| if (m_process.IsValid()) { |
| m_process.Kill(); |
| m_process.Clear(); |
| } |
| Launch(action.launch_info); |
| break; |
| case ActionWanted::Type::eKill: |
| if (m_verbose) |
| printf("kill\n"); |
| m_process.Kill(); |
| return; |
| case ActionWanted::Type::eCallNext: |
| goto do_the_call; |
| break; |
| } |
| } |
| } |
| |
| if (GetVerbose()) |
| printf("I am gonna die at step %d\n", m_step); |
| } |
| |
| int TestCase::Run(TestCase &test, int argc, const char **argv) { |
| if (test.Setup(argc, argv)) { |
| test.Loop(); |
| Results results; |
| test.WriteResults(results); |
| return RUN_SUCCESS; |
| } else |
| return RUN_SETUP_ERROR; |
| } |