1from __future__ import absolute_import, print_function
2
3import sys
4from random import randint, seed
5
6import mozdevice
7import pytest
8from unittest.mock import patch
9from six import StringIO
10
11# set up required module-level variables/objects
12seed(1488590)
13
14
15def random_tcp_port():
16    """Returns a pseudo-random integer generated from a seed.
17
18    :returns: int: pseudo-randomly generated integer
19    """
20    return randint(8000, 12000)
21
22
23@pytest.fixture(autouse=True)
24def mock_command_output(monkeypatch):
25    """Monkeypatches the ADBDevice.command_output() method call.
26
27    Instead of calling the concrete method implemented in adb.py::ADBDevice,
28    this method simply returns a string representation of the command that was
29    received.
30
31    As an exception, if the command begins with "forward tcp:0 ", this method
32    returns a mock port number.
33
34    :param object monkeypatch: pytest provided fixture for mocking.
35    """
36
37    def command_output_wrapper(object, cmd, timeout):
38        """Actual monkeypatch implementation of the command_output method call.
39
40        :param object object: placeholder object representing ADBDevice
41        :param str cmd: command to be executed
42        :param timeout: unused parameter to represent timeout threshold
43        :returns: string - string representation of command to be executed
44                  int - mock port number (only used when cmd begins with "forward tcp:0 ")
45        """
46
47        if cmd[0] == "forward" and cmd[1] == "tcp:0":
48            return 7777
49
50        print(str(cmd))
51        return str(cmd)
52
53    monkeypatch.setattr(mozdevice.ADBDevice, "command_output", command_output_wrapper)
54
55
56@pytest.fixture(autouse=True)
57def mock_shell_output(monkeypatch):
58    """Monkeypatches the ADBDevice.shell_output() method call.
59
60    Instead of returning the output of an adb call, this method will
61    return appropriate string output. Content of the string output is
62    in line with the calling method's expectations.
63
64    :param object monkeypatch: pytest provided fixture for mocking.
65    """
66
67    def shell_output_wrapper(
68        object, cmd, env=None, cwd=None, timeout=None, enable_run_as=False
69    ):
70        """Actual monkeypatch implementation of the shell_output method call.
71
72        :param object object: placeholder object representing ADBDevice
73        :param str cmd: command to be executed
74        :param env: contains the environment variable
75        :type env: dict or None
76        :param cwd: The directory from which to execute.
77        :type cwd: str or None
78        :param timeout: unused parameter tp represent timeout threshold
79        :param enable_run_as: bool determining if run_as <app> is to be used
80        :returns: string - string representation of a simulated call to adb
81        """
82        if "pm list package error" in cmd:
83            return "Error: Could not access the Package Manager"
84        elif "pm list package none" in cmd:
85            return ""
86        elif "pm list package" in cmd:
87            apps = ["org.mozilla.fennec", "org.mozilla.geckoview_example"]
88            return ("package:{}\n" * len(apps)).format(*apps)
89        else:
90            print(str(cmd))
91            return str(cmd)
92
93    monkeypatch.setattr(mozdevice.ADBDevice, "shell_output", shell_output_wrapper)
94
95
96@pytest.fixture(autouse=True)
97def mock_is_path_internal_storage(monkeypatch):
98    """Monkeypatches the ADBDevice.is_path_internal_storage() method call.
99
100    Instead of returning the outcome of whether the path provided is
101    internal storage or external, this will always return True.
102
103    :param object monkeypatch: pytest provided fixture for mocking.
104    """
105
106    def is_path_internal_storage_wrapper(object, path, timeout=None):
107        """Actual monkeypatch implementation of the is_path_internal_storage() call.
108
109        :param str path: The path to test.
110        :param timeout: The maximum time in
111            seconds for any spawned adb process to complete before
112            throwing an ADBTimeoutError.  This timeout is per adb call. The
113            total time spent may exceed this value. If it is not
114            specified, the value set in the ADBDevice constructor is used.
115        :returns: boolean
116
117        :raises: * ADBTimeoutError
118                 * ADBError
119        """
120        if "internal_storage" in path:
121            return True
122        return False
123
124    monkeypatch.setattr(
125        mozdevice.ADBDevice,
126        "is_path_internal_storage",
127        is_path_internal_storage_wrapper,
128    )
129
130
131@pytest.fixture(autouse=True)
132def mock_enable_run_as_for_path(monkeypatch):
133    """Monkeypatches the ADBDevice.enable_run_as_for_path(path) method.
134
135    Always return True
136
137    :param object monkeypatch: pytest provided fixture for mocking.
138    """
139
140    def enable_run_as_for_path_wrapper(object, path):
141        """Actual monkeypatch implementation of the enable_run_as_for_path() call.
142
143        :param str path: The path to test.
144        :returns: boolean
145        """
146        return True
147
148    monkeypatch.setattr(
149        mozdevice.ADBDevice, "enable_run_as_for_path", enable_run_as_for_path_wrapper
150    )
151
152
153@pytest.fixture(autouse=True)
154def mock_shell_bool(monkeypatch):
155    """Monkeypatches the ADBDevice.shell_bool() method call.
156
157    Instead of returning the output of an adb call, this method will
158    return appropriate string output. Content of the string output is
159    in line with the calling method's expectations.
160
161    :param object monkeypatch: pytest provided fixture for mocking.
162    """
163
164    def shell_bool_wrapper(
165        object, cmd, env=None, cwd=None, timeout=None, enable_run_as=False
166    ):
167        """Actual monkeypatch implementation of the shell_bool method call.
168
169        :param object object: placeholder object representing ADBDevice
170        :param str cmd: command to be executed
171        :param env: contains the environment variable
172        :type env: dict or None
173        :param cwd: The directory from which to execute.
174        :type cwd: str or None
175        :param timeout: unused parameter tp represent timeout threshold
176        :param enable_run_as: bool determining if run_as <app> is to be used
177        :returns: string - string representation of a simulated call to adb
178        """
179        print(cmd)
180        return str(cmd)
181
182    monkeypatch.setattr(mozdevice.ADBDevice, "shell_bool", shell_bool_wrapper)
183
184
185@pytest.fixture(autouse=True)
186def mock_adb_object():
187    """Patches the __init__ method call when instantiating ADBDevice.
188
189    ADBDevice normally requires instantiated objects in order to execute
190    its commands.
191
192    With a pytest-mock patch, we are able to mock the initialization of
193    the ADBDevice object. By yielding the instantiated mock object,
194    unit tests can be run that call methods that require an instantiated
195    object.
196
197    :yields: ADBDevice - mock instance of ADBDevice object
198    """
199    with patch.object(mozdevice.ADBDevice, "__init__", lambda self: None):
200        yield mozdevice.ADBDevice()
201
202
203@pytest.fixture
204def redirect_stdout_and_assert():
205    """Redirects the stdout pipe temporarily to a StringIO stream.
206
207    This is useful to assert on methods that do not return
208    a value, such as most ADBDevice methods.
209
210    The original stdout pipe is preserved throughout the process.
211
212    :returns: _wrapper method
213    """
214
215    def _wrapper(func, **kwargs):
216        """Implements the stdout sleight-of-hand.
217
218        After preserving the original sys.stdout, it is switched
219        to use cStringIO.StringIO.
220
221        Method with no return value is called, and the stdout
222        pipe is switched back to the original sys.stdout.
223
224        The expected outcome is received as part of the kwargs.
225        This is asserted against a sanitized output from the method
226        under test.
227
228        :param object func: method under test
229        :param dict kwargs: dictionary of function parameters
230        """
231        original_stdout = sys.stdout
232        sys.stdout = testing_stdout = StringIO()
233        expected_text = kwargs.pop("text")
234        func(**kwargs)
235        sys.stdout = original_stdout
236        assert expected_text in testing_stdout.getvalue().rstrip()
237
238    return _wrapper
239