1from __future__ import print_function
2from __future__ import absolute_import
3
4# System modules
5import os
6import sys
7
8# Third-party modules
9
10# LLDB modules
11import lldb
12from .lldbtest import *
13from . import lldbutil
14
15def source_type(filename):
16    _, extension = os.path.splitext(filename)
17    return {
18        '.c' : 'C_SOURCES',
19        '.cpp' : 'CXX_SOURCES',
20        '.cxx' : 'CXX_SOURCES',
21        '.cc' : 'CXX_SOURCES',
22        '.m' : 'OBJC_SOURCES',
23        '.mm' : 'OBJCXX_SOURCES'
24    }.get(extension, None)
25
26class CommandParser:
27    def __init__(self):
28        self.breakpoints = []
29
30    def parse_one_command(self, line):
31        parts = line.split('//%')
32
33        command = None
34        new_breakpoint = True
35
36        if len(parts) == 2:
37            command = parts[1].strip() # take off whitespace
38            new_breakpoint = parts[0].strip() != ""
39
40        return (command, new_breakpoint)
41
42    def parse_source_files(self, source_files):
43        for source_file in source_files:
44            file_handle = open(source_file)
45            lines = file_handle.readlines()
46            line_number = 0
47            current_breakpoint = None # non-NULL means we're looking through whitespace to find additional commands
48            for line in lines:
49                line_number = line_number + 1 # 1-based, so we do this first
50                (command, new_breakpoint) = self.parse_one_command(line)
51
52                if new_breakpoint:
53                    current_breakpoint = None
54
55                if command != None:
56                    if current_breakpoint == None:
57                        current_breakpoint = {}
58                        current_breakpoint['file_name'] = source_file
59                        current_breakpoint['line_number'] = line_number
60                        current_breakpoint['command'] = command
61                        self.breakpoints.append(current_breakpoint)
62                    else:
63                        current_breakpoint['command'] = current_breakpoint['command'] + "\n" + command
64
65    def set_breakpoints(self, target):
66        for breakpoint in self.breakpoints:
67            breakpoint['breakpoint'] = target.BreakpointCreateByLocation(breakpoint['file_name'], breakpoint['line_number'])
68
69    def handle_breakpoint(self, test, breakpoint_id):
70        for breakpoint in self.breakpoints:
71            if breakpoint['breakpoint'].GetID() == breakpoint_id:
72                test.execute_user_command(breakpoint['command'])
73                return
74
75class InlineTest(TestBase):
76    # Internal implementation
77
78    def getRerunArgs(self):
79        # The -N option says to NOT run a if it matches the option argument, so
80        # if we are using dSYM we say to NOT run dwarf (-N dwarf) and vice versa.
81        if self.using_dsym is None:
82            # The test was skipped altogether.
83            return ""
84        elif self.using_dsym:
85            return "-N dwarf %s" % (self.mydir)
86        else:
87            return "-N dsym %s" % (self.mydir)
88
89    def BuildMakefile(self):
90        if os.path.exists("Makefile"):
91            return
92
93        categories = {}
94
95        for f in os.listdir(os.getcwd()):
96            t = source_type(f)
97            if t:
98                if t in list(categories.keys()):
99                    categories[t].append(f)
100                else:
101                    categories[t] = [f]
102
103        makefile = open("Makefile", 'w+')
104
105        level = os.sep.join([".."] * len(self.mydir.split(os.sep))) + os.sep + "make"
106
107        makefile.write("LEVEL = " + level + "\n")
108
109        for t in list(categories.keys()):
110            line = t + " := " + " ".join(categories[t])
111            makefile.write(line + "\n")
112
113        if ('OBJCXX_SOURCES' in list(categories.keys())) or ('OBJC_SOURCES' in list(categories.keys())):
114            makefile.write("LDFLAGS = $(CFLAGS) -lobjc -framework Foundation\n")
115
116        if ('CXX_SOURCES' in list(categories.keys())):
117            makefile.write("CXXFLAGS += -std=c++11\n")
118
119        makefile.write("include $(LEVEL)/Makefile.rules\n")
120        makefile.flush()
121        makefile.close()
122
123
124    @skipUnlessDarwin
125    def __test_with_dsym(self):
126        self.using_dsym = True
127        self.BuildMakefile()
128        self.buildDsym()
129        self.do_test()
130
131    def __test_with_dwarf(self):
132        self.using_dsym = False
133        self.BuildMakefile()
134        self.buildDwarf()
135        self.do_test()
136
137    def __test_with_dwo(self):
138        self.using_dsym = False
139        self.BuildMakefile()
140        self.buildDwo()
141        self.do_test()
142
143    def execute_user_command(self, __command):
144        exec(__command, globals(), locals())
145
146    def do_test(self):
147        exe_name = "a.out"
148        exe = os.path.join(os.getcwd(), exe_name)
149        source_files = [ f for f in os.listdir(os.getcwd()) if source_type(f) ]
150        target = self.dbg.CreateTarget(exe)
151
152        parser = CommandParser()
153        parser.parse_source_files(source_files)
154        parser.set_breakpoints(target)
155
156        process = target.LaunchSimple(None, None, os.getcwd())
157
158        while lldbutil.get_stopped_thread(process, lldb.eStopReasonBreakpoint):
159            thread = lldbutil.get_stopped_thread(process, lldb.eStopReasonBreakpoint)
160            breakpoint_id = thread.GetStopReasonDataAtIndex (0)
161            parser.handle_breakpoint(self, breakpoint_id)
162            process.Continue()
163
164
165    # Utilities for testcases
166
167    def check_expression (self, expression, expected_result, use_summary = True):
168        value = self.frame().EvaluateExpression (expression)
169        self.assertTrue(value.IsValid(), expression+"returned a valid value")
170        if self.TraceOn():
171            print(value.GetSummary())
172            print(value.GetValue())
173        if use_summary:
174            answer = value.GetSummary()
175        else:
176            answer = value.GetValue()
177        report_str = "%s expected: %s got: %s"%(expression, expected_result, answer)
178        self.assertTrue(answer == expected_result, report_str)
179
180def ApplyDecoratorsToFunction(func, decorators):
181    tmp = func
182    if type(decorators) == list:
183        for decorator in decorators:
184            tmp = decorator(tmp)
185    elif hasattr(decorators, '__call__'):
186        tmp = decorators(tmp)
187    return tmp
188
189
190def MakeInlineTest(__file, __globals, decorators=None):
191    # Derive the test name from the current file name
192    file_basename = os.path.basename(__file)
193    InlineTest.mydir = TestBase.compute_mydir(__file)
194
195    test_name, _ = os.path.splitext(file_basename)
196    # Build the test case
197    test = type(test_name, (InlineTest,), {'using_dsym': None})
198    test.name = test_name
199
200    test.test_with_dsym = ApplyDecoratorsToFunction(test._InlineTest__test_with_dsym, decorators)
201    test.test_with_dwarf = ApplyDecoratorsToFunction(test._InlineTest__test_with_dwarf, decorators)
202    test.test_with_dwo = ApplyDecoratorsToFunction(test._InlineTest__test_with_dwo, decorators)
203
204    # Add the test case to the globals, and hide InlineTest
205    __globals.update({test_name : test})
206
207    return test
208
209