diff options
Diffstat (limited to '')
-rw-r--r-- | testing/mozbase/mozdevice/tests/conftest.py | 236 | ||||
-rw-r--r-- | testing/mozbase/mozdevice/tests/manifest.ini | 6 | ||||
-rw-r--r-- | testing/mozbase/mozdevice/tests/test_chown.py | 67 | ||||
-rw-r--r-- | testing/mozbase/mozdevice/tests/test_escape_command_line.py | 21 | ||||
-rw-r--r-- | testing/mozbase/mozdevice/tests/test_is_app_installed.py | 38 | ||||
-rw-r--r-- | testing/mozbase/mozdevice/tests/test_socket_connection.py | 124 |
6 files changed, 492 insertions, 0 deletions
diff --git a/testing/mozbase/mozdevice/tests/conftest.py b/testing/mozbase/mozdevice/tests/conftest.py new file mode 100644 index 0000000000..831090a428 --- /dev/null +++ b/testing/mozbase/mozdevice/tests/conftest.py @@ -0,0 +1,236 @@ +import sys +from random import randint, seed +from unittest.mock import patch + +import mozdevice +import pytest +from six import StringIO + +# set up required module-level variables/objects +seed(1488590) + + +def random_tcp_port(): + """Returns a pseudo-random integer generated from a seed. + + :returns: int: pseudo-randomly generated integer + """ + return randint(8000, 12000) + + +@pytest.fixture(autouse=True) +def mock_command_output(monkeypatch): + """Monkeypatches the ADBDevice.command_output() method call. + + Instead of calling the concrete method implemented in adb.py::ADBDevice, + this method simply returns a string representation of the command that was + received. + + As an exception, if the command begins with "forward tcp:0 ", this method + returns a mock port number. + + :param object monkeypatch: pytest provided fixture for mocking. + """ + + def command_output_wrapper(object, cmd, timeout): + """Actual monkeypatch implementation of the command_output method call. + + :param object object: placeholder object representing ADBDevice + :param str cmd: command to be executed + :param timeout: unused parameter to represent timeout threshold + :returns: string - string representation of command to be executed + int - mock port number (only used when cmd begins with "forward tcp:0 ") + """ + + if cmd[0] == "forward" and cmd[1] == "tcp:0": + return 7777 + + print(str(cmd)) + return str(cmd) + + monkeypatch.setattr(mozdevice.ADBDevice, "command_output", command_output_wrapper) + + +@pytest.fixture(autouse=True) +def mock_shell_output(monkeypatch): + """Monkeypatches the ADBDevice.shell_output() method call. + + Instead of returning the output of an adb call, this method will + return appropriate string output. Content of the string output is + in line with the calling method's expectations. + + :param object monkeypatch: pytest provided fixture for mocking. + """ + + def shell_output_wrapper( + object, cmd, env=None, cwd=None, timeout=None, enable_run_as=False + ): + """Actual monkeypatch implementation of the shell_output method call. + + :param object object: placeholder object representing ADBDevice + :param str cmd: command to be executed + :param env: contains the environment variable + :type env: dict or None + :param cwd: The directory from which to execute. + :type cwd: str or None + :param timeout: unused parameter tp represent timeout threshold + :param enable_run_as: bool determining if run_as <app> is to be used + :returns: string - string representation of a simulated call to adb + """ + if "pm list package error" in cmd: + return "Error: Could not access the Package Manager" + elif "pm list package none" in cmd: + return "" + elif "pm list package" in cmd: + apps = ["org.mozilla.fennec", "org.mozilla.geckoview_example"] + return ("package:{}\n" * len(apps)).format(*apps) + else: + print(str(cmd)) + return str(cmd) + + monkeypatch.setattr(mozdevice.ADBDevice, "shell_output", shell_output_wrapper) + + +@pytest.fixture(autouse=True) +def mock_is_path_internal_storage(monkeypatch): + """Monkeypatches the ADBDevice.is_path_internal_storage() method call. + + Instead of returning the outcome of whether the path provided is + internal storage or external, this will always return True. + + :param object monkeypatch: pytest provided fixture for mocking. + """ + + def is_path_internal_storage_wrapper(object, path, timeout=None): + """Actual monkeypatch implementation of the is_path_internal_storage() call. + + :param str path: The path to test. + :param timeout: The maximum time in + seconds for any spawned adb process to complete before + throwing an ADBTimeoutError. This timeout is per adb call. The + total time spent may exceed this value. If it is not + specified, the value set in the ADBDevice constructor is used. + :returns: boolean + + :raises: * ADBTimeoutError + * ADBError + """ + if "internal_storage" in path: + return True + return False + + monkeypatch.setattr( + mozdevice.ADBDevice, + "is_path_internal_storage", + is_path_internal_storage_wrapper, + ) + + +@pytest.fixture(autouse=True) +def mock_enable_run_as_for_path(monkeypatch): + """Monkeypatches the ADBDevice.enable_run_as_for_path(path) method. + + Always return True + + :param object monkeypatch: pytest provided fixture for mocking. + """ + + def enable_run_as_for_path_wrapper(object, path): + """Actual monkeypatch implementation of the enable_run_as_for_path() call. + + :param str path: The path to test. + :returns: boolean + """ + return True + + monkeypatch.setattr( + mozdevice.ADBDevice, "enable_run_as_for_path", enable_run_as_for_path_wrapper + ) + + +@pytest.fixture(autouse=True) +def mock_shell_bool(monkeypatch): + """Monkeypatches the ADBDevice.shell_bool() method call. + + Instead of returning the output of an adb call, this method will + return appropriate string output. Content of the string output is + in line with the calling method's expectations. + + :param object monkeypatch: pytest provided fixture for mocking. + """ + + def shell_bool_wrapper( + object, cmd, env=None, cwd=None, timeout=None, enable_run_as=False + ): + """Actual monkeypatch implementation of the shell_bool method call. + + :param object object: placeholder object representing ADBDevice + :param str cmd: command to be executed + :param env: contains the environment variable + :type env: dict or None + :param cwd: The directory from which to execute. + :type cwd: str or None + :param timeout: unused parameter tp represent timeout threshold + :param enable_run_as: bool determining if run_as <app> is to be used + :returns: string - string representation of a simulated call to adb + """ + print(cmd) + return str(cmd) + + monkeypatch.setattr(mozdevice.ADBDevice, "shell_bool", shell_bool_wrapper) + + +@pytest.fixture(autouse=True) +def mock_adb_object(): + """Patches the __init__ method call when instantiating ADBDevice. + + ADBDevice normally requires instantiated objects in order to execute + its commands. + + With a pytest-mock patch, we are able to mock the initialization of + the ADBDevice object. By yielding the instantiated mock object, + unit tests can be run that call methods that require an instantiated + object. + + :yields: ADBDevice - mock instance of ADBDevice object + """ + with patch.object(mozdevice.ADBDevice, "__init__", lambda self: None): + yield mozdevice.ADBDevice() + + +@pytest.fixture +def redirect_stdout_and_assert(): + """Redirects the stdout pipe temporarily to a StringIO stream. + + This is useful to assert on methods that do not return + a value, such as most ADBDevice methods. + + The original stdout pipe is preserved throughout the process. + + :returns: _wrapper method + """ + + def _wrapper(func, **kwargs): + """Implements the stdout sleight-of-hand. + + After preserving the original sys.stdout, it is switched + to use cStringIO.StringIO. + + Method with no return value is called, and the stdout + pipe is switched back to the original sys.stdout. + + The expected outcome is received as part of the kwargs. + This is asserted against a sanitized output from the method + under test. + + :param object func: method under test + :param dict kwargs: dictionary of function parameters + """ + original_stdout = sys.stdout + sys.stdout = testing_stdout = StringIO() + expected_text = kwargs.pop("text") + func(**kwargs) + sys.stdout = original_stdout + assert expected_text in testing_stdout.getvalue().rstrip() + + return _wrapper diff --git a/testing/mozbase/mozdevice/tests/manifest.ini b/testing/mozbase/mozdevice/tests/manifest.ini new file mode 100644 index 0000000000..208fd06608 --- /dev/null +++ b/testing/mozbase/mozdevice/tests/manifest.ini @@ -0,0 +1,6 @@ +[DEFAULT] +subsuite = mozbase +[test_socket_connection.py] +[test_is_app_installed.py] +[test_chown.py] +[test_escape_command_line.py] diff --git a/testing/mozbase/mozdevice/tests/test_chown.py b/testing/mozbase/mozdevice/tests/test_chown.py new file mode 100644 index 0000000000..1bbfcc5d8e --- /dev/null +++ b/testing/mozbase/mozdevice/tests/test_chown.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python + +import logging +from unittest.mock import patch + +import mozunit +import pytest + + +@pytest.mark.parametrize("boolean_value", [True, False]) +def test_set_chown_r_attribute( + mock_adb_object, redirect_stdout_and_assert, boolean_value +): + mock_adb_object._chown_R = boolean_value + assert mock_adb_object._chown_R == boolean_value + + +def test_chown_path_internal(mock_adb_object, redirect_stdout_and_assert): + """Tests whether attempt to chown internal path is ignored""" + with patch.object(logging, "getLogger") as mock_log: + mock_adb_object._logger = mock_log + + testing_parameters = { + "owner": "someuser", + "path": "internal_storage", + } + expected = "Ignoring attempt to chown external storage" + mock_adb_object.chown(**testing_parameters) + assert "".join(mock_adb_object._logger.method_calls[0][1]) != "" + assert "".join(mock_adb_object._logger.method_calls[0][1]) == expected + + +def test_chown_one_path(mock_adb_object, redirect_stdout_and_assert): + """Tests the path where only one path is provided.""" + # set up mock logging and self._chown_R attribute. + with patch.object(logging, "getLogger") as mock_log: + mock_adb_object._logger = mock_log + mock_adb_object._chown_R = True + + testing_parameters = { + "owner": "someuser", + "path": "/system", + } + command = "chown {owner} {path}".format(**testing_parameters) + testing_parameters["text"] = command + redirect_stdout_and_assert(mock_adb_object.chown, **testing_parameters) + + +def test_chown_one_path_with_group(mock_adb_object, redirect_stdout_and_assert): + """Tests the path where group is provided.""" + # set up mock logging and self._chown_R attribute. + with patch.object(logging, "getLogger") as mock_log: + mock_adb_object._logger = mock_log + mock_adb_object._chown_R = True + + testing_parameters = { + "owner": "someuser", + "path": "/system", + "group": "group_2", + } + command = "chown {owner}.{group} {path}".format(**testing_parameters) + testing_parameters["text"] = command + redirect_stdout_and_assert(mock_adb_object.chown, **testing_parameters) + + +if __name__ == "__main__": + mozunit.main() diff --git a/testing/mozbase/mozdevice/tests/test_escape_command_line.py b/testing/mozbase/mozdevice/tests/test_escape_command_line.py new file mode 100644 index 0000000000..112dd936c5 --- /dev/null +++ b/testing/mozbase/mozdevice/tests/test_escape_command_line.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python + +import mozunit + + +def test_escape_command_line(mock_adb_object, redirect_stdout_and_assert): + """Test _escape_command_line.""" + cases = { + # expected output : test input + "adb shell ls -l": ["adb", "shell", "ls", "-l"], + "adb shell 'ls -l'": ["adb", "shell", "ls -l"], + "-e 'if (true)'": ["-e", "if (true)"], + "-e 'if (x === \"hello\")'": ["-e", 'if (x === "hello")'], + "-e 'if (x === '\"'\"'hello'\"'\"')'": ["-e", "if (x === 'hello')"], + } + for expected, input in cases.items(): + assert mock_adb_object._escape_command_line(input) == expected + + +if __name__ == "__main__": + mozunit.main() diff --git a/testing/mozbase/mozdevice/tests/test_is_app_installed.py b/testing/mozbase/mozdevice/tests/test_is_app_installed.py new file mode 100644 index 0000000000..a51836bc02 --- /dev/null +++ b/testing/mozbase/mozdevice/tests/test_is_app_installed.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python + +import mozunit +import pytest +from mozdevice import ADBError + + +def test_is_app_installed(mock_adb_object): + """Tests that is_app_installed returns True if app is installed.""" + assert mock_adb_object.is_app_installed("org.mozilla.geckoview_example") + + +def test_is_app_installed_not_installed(mock_adb_object): + """Tests that is_app_installed returns False if provided app_name + does not resolve.""" + assert not mock_adb_object.is_app_installed("some_random_name") + + +def test_is_app_installed_partial_name(mock_adb_object): + """Tests that is_app_installed returns False if provided app_name + is only a partial match.""" + assert not mock_adb_object.is_app_installed("fennec") + + +def test_is_app_installed_package_manager_error(mock_adb_object): + """Tests that is_app_installed is able to raise an exception.""" + with pytest.raises(ADBError): + mock_adb_object.is_app_installed("error") + + +def test_is_app_installed_no_installed_package_found(mock_adb_object): + """Tests that is_app_installed is able to handle scenario + where no installed packages are found.""" + assert not mock_adb_object.is_app_installed("none") + + +if __name__ == "__main__": + mozunit.main() diff --git a/testing/mozbase/mozdevice/tests/test_socket_connection.py b/testing/mozbase/mozdevice/tests/test_socket_connection.py new file mode 100644 index 0000000000..1182737546 --- /dev/null +++ b/testing/mozbase/mozdevice/tests/test_socket_connection.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python + +import mozunit +import pytest +from conftest import random_tcp_port + + +@pytest.fixture(params=["tcp:{}".format(random_tcp_port()) for _ in range(5)]) +def select_test_port(request): + """Generate a list of ports to be used for testing.""" + yield request.param + + +def test_list_socket_connections_reverse(mock_adb_object): + assert [("['reverse',", "'--list']")] == mock_adb_object.list_socket_connections( + "reverse" + ) + + +def test_list_socket_connections_forward(mock_adb_object): + assert [("['forward',", "'--list']")] == mock_adb_object.list_socket_connections( + "forward" + ) + + +def test_create_socket_connection_reverse( + mock_adb_object, select_test_port, redirect_stdout_and_assert +): + _expected = "['reverse', '{0}', '{0}']".format(select_test_port) + redirect_stdout_and_assert( + mock_adb_object.create_socket_connection, + direction="reverse", + local=select_test_port, + remote=select_test_port, + text=_expected, + ) + + +def test_create_socket_connection_forward( + mock_adb_object, select_test_port, redirect_stdout_and_assert +): + _expected = "['forward', '{0}', '{0}']".format(select_test_port) + redirect_stdout_and_assert( + mock_adb_object.create_socket_connection, + direction="forward", + local=select_test_port, + remote=select_test_port, + text=_expected, + ) + + +def test_create_socket_connection_forward_adb_assigned_port( + mock_adb_object, select_test_port +): + result = mock_adb_object.create_socket_connection( + direction="forward", local="tcp:0", remote=select_test_port + ) + assert isinstance(result, int) and result == 7777 + + +def test_remove_socket_connections_reverse(mock_adb_object, redirect_stdout_and_assert): + _expected = "['reverse', '--remove-all']" + redirect_stdout_and_assert( + mock_adb_object.remove_socket_connections, direction="reverse", text=_expected + ) + + +def test_remove_socket_connections_forward(mock_adb_object, redirect_stdout_and_assert): + _expected = "['forward', '--remove-all']" + redirect_stdout_and_assert( + mock_adb_object.remove_socket_connections, direction="forward", text=_expected + ) + + +def test_legacy_forward(mock_adb_object, select_test_port, redirect_stdout_and_assert): + _expected = "['forward', '{0}', '{0}']".format(select_test_port) + redirect_stdout_and_assert( + mock_adb_object.forward, + local=select_test_port, + remote=select_test_port, + text=_expected, + ) + + +def test_legacy_forward_adb_assigned_port(mock_adb_object, select_test_port): + result = mock_adb_object.forward(local="tcp:0", remote=select_test_port) + assert isinstance(result, int) and result == 7777 + + +def test_legacy_reverse(mock_adb_object, select_test_port, redirect_stdout_and_assert): + _expected = "['reverse', '{0}', '{0}']".format(select_test_port) + redirect_stdout_and_assert( + mock_adb_object.reverse, + local=select_test_port, + remote=select_test_port, + text=_expected, + ) + + +def test_validate_port_invalid_prefix(mock_adb_object): + with pytest.raises(ValueError): + mock_adb_object._validate_port("{}".format("invalid"), is_local=True) + + +@pytest.mark.xfail +def test_validate_port_non_numerical_port_identifier(mock_adb_object): + with pytest.raises(AttributeError): + mock_adb_object._validate_port( + "{}".format("tcp:this:is:not:a:number"), is_local=True + ) + + +def test_validate_port_identifier_length_short(mock_adb_object): + with pytest.raises(ValueError): + mock_adb_object._validate_port("{}".format("tcp"), is_local=True) + + +def test_validate_direction(mock_adb_object): + with pytest.raises(ValueError): + mock_adb_object._validate_direction("{}".format("bad direction")) + + +if __name__ == "__main__": + mozunit.main() |