1from __future__ import print_function
2import lldb
3import unittest
4import os
5import json
6import stat
7import sys
8from textwrap import dedent
9import lldbsuite.test.lldbutil
10from lldbsuite.test.lldbtest import *
11from lldbsuite.test.decorators import *
12from lldbsuite.test.gdbclientutils import *
13
14
15@skipIfRemote
16@skipIfWindows
17class TestQemuLaunch(TestBase):
18    NO_DEBUG_INFO_TESTCASE = True
19
20    def set_emulator_setting(self, name, value):
21        self.runCmd("settings set -- platform.plugin.qemu-user.%s %s" %
22                (name, value))
23
24    def setUp(self):
25        super().setUp()
26        emulator = self.getBuildArtifact("qemu.py")
27        with os.fdopen(os.open(emulator, os.O_WRONLY|os.O_CREAT, stat.S_IRWXU),
28                "w") as e:
29
30            e.write(dedent("""\
31                    #! {exec!s}
32
33                    import runpy
34                    import sys
35
36                    sys.path = {path!r}
37                    runpy.run_path({source!r}, run_name='__main__')
38                    """).format(exec=sys.executable, path=sys.path,
39                        source=self.getSourcePath("qemu.py")))
40
41        self.set_emulator_setting("architecture", self.getArchitecture())
42        self.set_emulator_setting("emulator-path", emulator)
43
44    def _create_target(self):
45        self.build()
46        exe = self.getBuildArtifact()
47
48        # Create a target using our platform
49        error = lldb.SBError()
50        target = self.dbg.CreateTarget(exe, '', 'qemu-user', False, error)
51        self.assertSuccess(error)
52        self.assertEqual(target.GetPlatform().GetName(), "qemu-user")
53        return target
54
55    def _run_and_get_state(self, target=None, info=None):
56        if target is None:
57            target = self._create_target()
58
59        if info is None:
60            info = target.GetLaunchInfo()
61
62        # "Launch" the process. Our fake qemu implementation will pretend it
63        # immediately exited.
64        info.SetArguments(["dump:" + self.getBuildArtifact("state.log")], True)
65        error = lldb.SBError()
66        process = target.Launch(info, error)
67        self.assertSuccess(error)
68        self.assertIsNotNone(process)
69        self.assertState(process.GetState(), lldb.eStateExited)
70        self.assertEqual(process.GetExitStatus(), 0x47)
71
72        # Verify the qemu invocation parameters.
73        with open(self.getBuildArtifact("state.log")) as s:
74            return json.load(s)
75
76    def test_basic_launch(self):
77        state = self._run_and_get_state()
78
79        self.assertEqual(state["program"], self.getBuildArtifact())
80        self.assertEqual(state["args"],
81                ["dump:" + self.getBuildArtifact("state.log")])
82
83    def test_stdio_pty(self):
84        target = self._create_target()
85
86        info = target.GetLaunchInfo()
87        info.SetArguments([
88            "stdin:stdin",
89            "stdout:STDOUT CONTENT\n",
90            "stderr:STDERR CONTENT\n",
91            "dump:" + self.getBuildArtifact("state.log"),
92            ], False)
93
94        listener = lldb.SBListener("test_stdio")
95        info.SetListener(listener)
96
97        self.dbg.SetAsync(True)
98        error = lldb.SBError()
99        process = target.Launch(info, error)
100        self.assertSuccess(error)
101        lldbutil.expect_state_changes(self, listener, process,
102                [lldb.eStateRunning])
103
104        process.PutSTDIN("STDIN CONTENT\n")
105
106        lldbutil.expect_state_changes(self, listener, process,
107                [lldb.eStateExited])
108
109        # Echoed stdin, stdout and stderr. With a pty we cannot split standard
110        # output and error.
111        self.assertEqual(process.GetSTDOUT(1000),
112                "STDIN CONTENT\r\nSTDOUT CONTENT\r\nSTDERR CONTENT\r\n")
113        with open(self.getBuildArtifact("state.log")) as s:
114            state = json.load(s)
115        self.assertEqual(state["stdin"], "STDIN CONTENT\n")
116
117    def test_stdio_redirect(self):
118        self.build()
119        exe = self.getBuildArtifact()
120
121        # Create a target using our platform
122        error = lldb.SBError()
123        target = self.dbg.CreateTarget(exe, '', 'qemu-user', False, error)
124        self.assertSuccess(error)
125
126        info = lldb.SBLaunchInfo([
127            "stdin:stdin",
128            "stdout:STDOUT CONTENT",
129            "stderr:STDERR CONTENT",
130            "dump:" + self.getBuildArtifact("state.log"),
131            ])
132
133        info.AddOpenFileAction(0, self.getBuildArtifact("stdin.txt"),
134                True, False)
135        info.AddOpenFileAction(1, self.getBuildArtifact("stdout.txt"),
136                False, True)
137        info.AddOpenFileAction(2, self.getBuildArtifact("stderr.txt"),
138                False, True)
139
140        with open(self.getBuildArtifact("stdin.txt"), "w") as f:
141            f.write("STDIN CONTENT")
142
143        process = target.Launch(info, error)
144        self.assertSuccess(error)
145        self.assertState(process.GetState(), lldb.eStateExited)
146
147        with open(self.getBuildArtifact("stdout.txt")) as f:
148            self.assertEqual(f.read(), "STDOUT CONTENT")
149        with open(self.getBuildArtifact("stderr.txt")) as f:
150            self.assertEqual(f.read(), "STDERR CONTENT")
151        with open(self.getBuildArtifact("state.log")) as s:
152            state = json.load(s)
153        self.assertEqual(state["stdin"], "STDIN CONTENT")
154
155    def test_find_in_PATH(self):
156        emulator = self.getBuildArtifact("qemu-" + self.getArchitecture())
157        os.rename(self.getBuildArtifact("qemu.py"), emulator)
158        self.set_emulator_setting("emulator-path", "''")
159
160        original_path = os.environ["PATH"]
161        os.environ["PATH"] = (self.getBuildDir() +
162            self.platformContext.shlib_path_separator + original_path)
163        def cleanup():
164            os.environ["PATH"] = original_path
165
166        self.addTearDownHook(cleanup)
167        state = self._run_and_get_state()
168
169        self.assertEqual(state["program"], self.getBuildArtifact())
170        self.assertEqual(state["args"],
171                ["dump:" + self.getBuildArtifact("state.log")])
172
173    def test_bad_emulator_path(self):
174        self.set_emulator_setting("emulator-path",
175                self.getBuildArtifact("nonexistent.file"))
176
177        target = self._create_target()
178        info = lldb.SBLaunchInfo([])
179        error = lldb.SBError()
180        target.Launch(info, error)
181        self.assertTrue(error.Fail())
182        self.assertIn("doesn't exist", error.GetCString())
183
184    def test_extra_args(self):
185        self.set_emulator_setting("emulator-args", "-fake-arg fake-value")
186        state = self._run_and_get_state()
187
188        self.assertEqual(state["fake-arg"], "fake-value")
189
190    def test_env_vars(self):
191        # First clear any global environment to have a clean slate for this test
192        self.runCmd("settings clear target.env-vars")
193        self.runCmd("settings clear target.unset-env-vars")
194
195        def var(i):
196            return "LLDB_TEST_QEMU_VAR%d" % i
197
198        # Set some variables in the host environment.
199        for i in range(4):
200            os.environ[var(i)]="from host"
201        def cleanup():
202            for i in range(4):
203                del os.environ[var(i)]
204        self.addTearDownHook(cleanup)
205
206        # Set some emulator-only variables.
207        self.set_emulator_setting("emulator-env-vars",
208                "%s='emulator only'"%var(4))
209
210        # And through the platform setting.
211        self.set_emulator_setting("target-env-vars",
212                "%s='from platform' %s='from platform'" % (var(1), var(2)))
213
214        target = self._create_target()
215        info = target.GetLaunchInfo()
216        env = info.GetEnvironment()
217
218        # Platform settings should trump host values. Emulator-only variables
219        # should not be visible.
220        self.assertEqual(env.Get(var(0)), "from host")
221        self.assertEqual(env.Get(var(1)), "from platform")
222        self.assertEqual(env.Get(var(2)), "from platform")
223        self.assertEqual(env.Get(var(3)), "from host")
224        self.assertIsNone(env.Get(var(4)))
225
226        # Finally, make some launch_info specific changes.
227        env.Set(var(2), "from target", True)
228        env.Unset(var(3))
229        info.SetEnvironment(env, False)
230
231        # Now check everything. Launch info changes should trump everything, but
232        # only for the target environment -- the emulator should still get the
233        # host values.
234        state = self._run_and_get_state(target, info)
235        for i in range(4):
236            self.assertEqual(state["environ"][var(i)], "from host")
237        self.assertEqual(state["environ"][var(4)], "emulator only")
238        self.assertEqual(state["environ"]["QEMU_SET_ENV"],
239                "%s=from platform,%s=from target" % (var(1), var(2)))
240        self.assertEqual(state["environ"]["QEMU_UNSET_ENV"],
241                "%s,%s,QEMU_SET_ENV,QEMU_UNSET_ENV" % (var(3), var(4)))
242
243    def test_arg0(self):
244        target = self._create_target()
245        self.runCmd("settings set target.arg0 ARG0")
246        state = self._run_and_get_state(target)
247
248        self.assertEqual(state["program"], self.getBuildArtifact())
249        self.assertEqual(state["0"], "ARG0")
250
251    def test_sysroot(self):
252        sysroot = self.getBuildArtifact("sysroot")
253        self.runCmd("platform select qemu-user --sysroot %s" % sysroot)
254        state = self._run_and_get_state()
255        self.assertEqual(state["environ"]["QEMU_LD_PREFIX"], sysroot)
256        self.assertIn("QEMU_LD_PREFIX",
257                state["environ"]["QEMU_UNSET_ENV"].split(","))
258