1"""
2Test DarwinLog "source include debug-level" functionality provided by the
3StructuredDataDarwinLog plugin.
4
5These tests are currently only supported when running against Darwin
6targets.
7"""
8
9
10import lldb
11import platform
12import re
13import sys
14
15from lldbsuite.test.decorators import *
16from lldbsuite.test.lldbtest import *
17from lldbsuite.test import lldbtest_config
18
19
20class DarwinNSLogOutputTestCase(TestBase):
21    NO_DEBUG_INFO_TESTCASE = True
22    mydir = TestBase.compute_mydir(__file__)
23
24    @skipUnlessDarwin
25    @skipIfRemote   # this test is currently written using lldb commands & assumes running on local system
26
27    def setUp(self):
28        # Call super's setUp().
29        TestBase.setUp(self)
30        self.child = None
31        self.child_prompt = '(lldb) '
32        self.strict_sources = False
33
34        # Source filename.
35        self.source = 'main.m'
36
37        # Output filename.
38        self.exe_name = self.getBuildArtifact("a.out")
39        self.d = {'OBJC_SOURCES': self.source, 'EXE': self.exe_name}
40
41        # Locate breakpoint.
42        self.line = line_number(self.source, '// break here')
43
44    def tearDown(self):
45        # Shut down the process if it's still running.
46        if self.child:
47            self.runCmd('process kill')
48            self.expect_prompt()
49            self.runCmd('quit')
50
51        # Let parent clean up
52        super(DarwinNSLogOutputTestCase, self).tearDown()
53
54    def run_lldb_to_breakpoint(self, exe, source_file, line,
55                               settings_commands=None):
56        # Set self.child_prompt, which is "(lldb) ".
57        prompt = self.child_prompt
58
59        # So that the child gets torn down after the test.
60        import pexpect
61        self.child = pexpect.spawnu('%s %s %s' % (lldbtest_config.lldbExec,
62                                                  self.lldbOption, exe))
63        child = self.child
64
65        # Turn on logging for what the child sends back.
66        if self.TraceOn():
67            child.logfile_read = sys.stdout
68
69        # Disable showing of source lines at our breakpoint.
70        # This is necessary for the logging tests, because the very
71        # text we want to match for output from the running inferior
72        # will show up in the source as well.  We don't want the source
73        # output to erroneously make a match with our expected output.
74        self.runCmd("settings set stop-line-count-before 0")
75        self.expect_prompt()
76        self.runCmd("settings set stop-line-count-after 0")
77        self.expect_prompt()
78
79        # Run any test-specific settings commands now.
80        if settings_commands is not None:
81            for setting_command in settings_commands:
82                self.runCmd(setting_command)
83                self.expect_prompt()
84
85        # Set the breakpoint, and run to it.
86        child.sendline('breakpoint set -f %s -l %d' % (source_file, line))
87        child.expect_exact(prompt)
88        child.sendline('run')
89        child.expect_exact(prompt)
90
91        # Ensure we stopped at a breakpoint.
92        self.runCmd("thread list")
93        self.expect(re.compile(r"stop reason = .*breakpoint"))
94
95    def runCmd(self, cmd):
96        if self.child:
97            self.child.sendline(cmd)
98
99    def expect_prompt(self, exactly=True):
100        self.expect(self.child_prompt, exactly=exactly)
101
102    def expect(self, pattern, exactly=False, *args, **kwargs):
103        if exactly:
104            return self.child.expect_exact(pattern, *args, **kwargs)
105        return self.child.expect(pattern, *args, **kwargs)
106
107    def do_test(self, expect_regexes=None, settings_commands=None):
108        """ Run a test. """
109        self.build(dictionary=self.d)
110        self.setTearDownCleanup(dictionary=self.d)
111
112        exe = self.getBuildArtifact(self.exe_name)
113        self.run_lldb_to_breakpoint(exe, self.source, self.line,
114                                    settings_commands=settings_commands)
115        self.expect_prompt()
116
117        # Now go.
118        self.runCmd("process continue")
119        self.expect(expect_regexes)
120
121    def test_nslog_output_is_displayed(self):
122        """Test that NSLog() output shows up in the command-line debugger."""
123        self.do_test(expect_regexes=[
124            re.compile(r"(This is a message from NSLog)"),
125            re.compile(r"Process \d+ exited with status")
126        ])
127        self.assertIsNotNone(self.child.match)
128        self.assertGreater(len(self.child.match.groups()), 0)
129        self.assertEqual(
130            "This is a message from NSLog",
131            self.child.match.group(1))
132
133    def test_nslog_output_is_suppressed_with_env_var(self):
134        """Test that NSLog() output does not show up with the ignore env var."""
135        # This test will only work properly on macOS 10.12+.  Skip it on earlier versions.
136        # This will require some tweaking on iOS.
137        match = re.match(r"^\d+\.(\d+)", platform.mac_ver()[0])
138        if match is None or int(match.group(1)) < 12:
139            self.skipTest("requires macOS 10.12 or higher")
140
141        self.do_test(
142            expect_regexes=[
143                re.compile(r"(This is a message from NSLog)"),
144                re.compile(r"Process \d+ exited with status")
145            ],
146            settings_commands=[
147                "settings set target.env-vars "
148                "\"IDE_DISABLED_OS_ACTIVITY_DT_MODE=1\""
149            ])
150        self.assertIsNotNone(self.child.match)
151        self.assertEqual(len(self.child.match.groups()), 0)
152