예제 #1
0
def _process_wrapper(return_queue: multiprocessing.Queue,
                     error_queue: multiprocessing.Queue,
                     logging_queue: multiprocessing.Queue, process_id: str,
                     extension_package_import_paths: Sequence[str],
                     call_spec: CallSpec) -> None:
    """Executes the provided function in a parallel process."""
    gdm_logger.initialize_child_process_logging(logging_queue)
    short_description = f"{call_spec.function.__name__} in process {os.getpid()}"
    logger = gdm_logger.get_logger()
    logger.debug(f"{short_description}: starting execution of {call_spec}...")
    # The state of the main process (such as registered extension packages) is not
    # copied over when using "forkserver" or "spawn" as the process start method.
    for import_path in extension_package_import_paths:
        try:
            package_registrar.register(importlib.import_module(import_path))
        except (ImportError, errors.PackageRegistrationError) as e:
            logger.debug(
                f"{short_description}: failed to import and register GDM "
                f"extension package with import path {import_path}. "
                f"Error: {e!r}. Proceeding despite the failure.")

    manager_inst = manager.Manager()
    try:
        return_value = call_spec.function(manager_inst, *call_spec.args,
                                          **call_spec.kwargs)
        logger.debug(f"{short_description}: execution succeeded. "
                     f"Return value: {return_value}.")
        return_queue.put((process_id, return_value))
    except Exception as e:  # pylint: disable=broad-except
        logger.warning(
            f"{short_description}: execution raised an error: {e!r}.")
        error_queue.put((process_id, (type(e).__name__, str(e))))
    finally:
        manager_inst.close()
예제 #2
0
 def test_register_package_without_version(self):
     """Test registering a package without __version__ attribute."""
     del self.mock_package.__version__
     error_regex = r"Expected __version__ to be a string, found None"
     with self.assertRaisesRegex(errors.PackageRegistrationError,
                                 error_regex):
         package_registrar.register(self.mock_package)
예제 #3
0
 def test_register_package_without_required_functions(self):
     """Test registering a package which does not define required functions."""
     del self.mock_package.export_extensions  # Missing function.
     self.mock_package.download_key = None  # Not a function.
     regex = r"Package must define functions.*download_key.*export_extensions"
     with self.assertRaisesRegex(errors.PackageRegistrationError, regex):
         package_registrar.register(self.mock_package)
예제 #4
0
 def test_register_invalid_key_types(self):
     """Test registering a package which exports invalid key types."""
     self.mock_package.export_extensions.return_value = {
         "keys": [GoodKey, 1, None]
     }
     regex = r"Keys must be data_types\.KeyInfo instances"
     with self.assertRaisesRegex(errors.PackageRegistrationError, regex):
         package_registrar.register(self.mock_package)
예제 #5
0
 def test_register_duplicate_device_class(self):
     """Test registering a duplicate device class."""
     self.mock_package.export_extensions.return_value = {
         "primary_devices": [GoodPrimaryDevice]
     }
     regex = r"Device types.*some_primary_device.*are already defined in GDM"
     with self.assertRaisesRegex(errors.PackageRegistrationError, regex):
         package_registrar.register(self.mock_package)
예제 #6
0
 def test_register_mismatching_key_package_name(self):
     """Test registering a package with keys which have mismatching package."""
     self.mock_package.export_extensions.return_value = {
         "keys": [BadKeyMismatchingPackage]
     }
     regex = (
         r"KeyInfo\.package attribute must match the name of the package "
         r"\('my_extension_package'\)")
     with self.assertRaisesRegex(errors.PackageRegistrationError, regex):
         package_registrar.register(self.mock_package)
예제 #7
0
 def test_register_capability_with_duplicate_name(self):
     """Test registering a capability with a duplicate name."""
     self.mock_package.export_extensions.return_value = {
         "capability_interfaces": [CapabilityWithSameNameBase]
     }
     regex = (r"New capabilities .*CapabilityWithSameNameBase.* have "
              r"same names \(\[.*good_capability.*\]\) as "
              r"existing capabilities .*GoodCapabilityBase.*")
     with self.assertRaisesRegex(errors.PackageRegistrationError, regex):
         package_registrar.register(self.mock_package)
예제 #8
0
 def test_register_duplicate_capability_flavor(self):
     """Test registering a duplicate capability flavor."""
     self.mock_package.export_extensions.return_value = {
         "capability_flavors": [GoodCapabilityDefault]
     }
     regex = (r"New capability flavors .*GoodCapabilityDefault.* have "
              r"same names \(\[.*good_capability_default.*\]\) as "
              r"existing capability flavors .*GoodCapabilityDefault.*")
     with self.assertRaisesRegex(errors.PackageRegistrationError, regex):
         package_registrar.register(self.mock_package)
예제 #9
0
 def test_register_duplicate_comm_type(self):
     """Test registering a duplicate communication type."""
     self.mock_package.export_extensions.return_value = {
         "communication_types": [GoodCommunicationType]
     }
     regex = (
         r"New communication types .*GoodCommunicationType.* have same "
         r"names \(\[.*GoodCommunicationType.*\]\) as existing "
         r"communication types .*GoodCommunicationType.*")
     with self.assertRaisesRegex(errors.PackageRegistrationError, regex):
         package_registrar.register(self.mock_package)
예제 #10
0
 def test_register_nonconformant_device_class(self):
     """Test registering a device class which fails conformance checks."""
     err_template = (
         r"Failed to register package 'my_extension_package' with GDM "
         r"architecture\. The following device class\(es\) are incompliant with "
         r"GDM architecture:\n{cls}\n\t{err}")
     test_cases = (
         (BadPrimaryDeviceNoLogDecorator,
          (r"Public methods without return values must be decorated with "
           r"@decorators\.LogDecorator\(<logger>\)\. "
           r"Incompliant methods: \['factory_reset'\]")),
         (BadPrimaryDeviceSignatureOverride,
          (r"Methods may not fully override signatures inherited from parents\."
           r" Only extending the argument list is allowed\. Incompliant method "
           r"signatures: \[\"Method 'reboot', child signature "
           r".*BadPrimaryDeviceSignatureOverride\.reboot\(self\), inherited "
           r"signature\(s\) \['.*FakeGazooDeviceBase\.reboot\(self, no_wait, "
           r"method\)', '.*PrimaryDeviceBase.reboot\(self, no_wait, "
           r"method\)'\].\"\]")),
         (BadPrimaryDeviceNewPublicMethod,
          (r"New public methods are not allowed, except for health checks\. "
           r"Methods must either be private or, if public, moved into "
           r"capabilities\. Incompliant methods: \['new_method'\]")),
         (BadPrimaryDeviceUncategorizedProperty,
          (r"Public properties must be categorized as either "
           r"@decorators\.DynamicProperty, \.PersistentProperty, "
           r"\.OptionalProperty, or \.CapabilityDecorator\. "
           r"Incompliant properties: \['new_property'\]")),
         (BadPrimaryDeviceMisnamedHealthCheck,
          (r"Health checks must follow the <check_\.\.\.> naming convention\. "
           r"Incompliant health checks: \['misnamed_health_check'\]")),
         (BadPrimaryDeviceMissingClassConstants,
          (r"Class constants \['DEVICE_TYPE', 'COMMUNICATION_TYPE', "
           r"'DETECT_MATCH_CRITERIA', '_OWNER_EMAIL'\] are not set")),
         (BadPrimaryDeviceMisnamedCapability,
          (r"Capability definition\(s\) are invalid\. "
           r"Capability 'wrong_name': RuntimeError\(\"Attempting to define "
           r"capability flavor\(s\) .*EventParserDefault'.* under invalid name "
           r"wrong_name.*expected name: event_parser\.\"\)")),
     )
     for bad_device_class, expected_error_regex in test_cases:
         with self.subTest(device_class=bad_device_class):
             self.mock_package.export_extensions.return_value = {
                 "primary_devices": [bad_device_class]
             }
             error_regex = err_template.format(cls=bad_device_class,
                                               err=expected_error_regex)
             with self.assertRaisesRegex(errors.PackageRegistrationError,
                                         error_regex):
                 package_registrar.register(self.mock_package)
예제 #11
0
 def test_register_not_a_class(self):
     """Test registering an object which is not a class."""
     test_cases = [
         "auxiliary_devices", "primary_devices", "virtual_devices",
         "communication_types", "capability_interfaces",
         "capability_flavors"
     ]
     for extension in test_cases:
         with self.subTest(extension=extension):
             self.mock_package.export_extensions.return_value = {
                 extension: [None, "foo"]
             }
             with self.assertRaisesRegex(errors.PackageRegistrationError,
                                         r"must be class objects"):
                 package_registrar.register(self.mock_package)
예제 #12
0
    def test_register_invalid_manager_cli_mixin_type(self):
        """Test registering a package with a bad Manager CLI mixin type."""
        test_cases = [
            1,  # Not a class object.
            int,  # Class object, but does not inherit from FireManager.
        ]

        for manager_cli_mixin in test_cases:
            with self.subTest(manager_cli_mixin=manager_cli_mixin):
                self.mock_package.export_extensions.return_value = {
                    "manager_cli_mixin": manager_cli_mixin,
                }
                regex = "FireManager mixin class is invalid"
                with self.assertRaisesRegex(errors.PackageRegistrationError,
                                            regex):
                    package_registrar.register(self.mock_package)
예제 #13
0
 def test_register_duplicate_detect_query(self):
     """Test registering a duplicate detect query."""
     duplicate_query_dict = {
         GoodQueryKey.some_valid_query: good_detection_query
     }
     self.mock_package.export_extensions.return_value = {
         "detect_criteria":
         immutabledict.immutabledict(
             {GoodCommunicationType.__name__: duplicate_query_dict})
     }
     regex = re.escape(
         r"Detection queries {} for communication type {!r} are already "
         "defined in GDM.".format(list(duplicate_query_dict.keys()),
                                  GoodCommunicationType.__name__))
     with self.assertRaisesRegex(errors.PackageRegistrationError, regex):
         package_registrar.register(self.mock_package)
예제 #14
0
 def test_register_detect_query_for_unknown_comm_type(self):
     """Test registering a detect query comm type not known to GDM."""
     valid_query_dict = {
         GoodQueryKey.some_valid_query: good_detection_query
     }
     self.mock_package.export_extensions.return_value = {
         "detect_criteria":
         immutabledict.immutabledict(
             {"SomeUnknownCommunicationType": valid_query_dict})
     }
     regex = (
         r"Unable to register detection criteria for communication type "
         "{!r} as it has not been exported by the package.".format(
             "SomeUnknownCommunicationType"))
     with self.assertRaisesRegex(errors.PackageRegistrationError, regex):
         package_registrar.register(self.mock_package)
예제 #15
0
 def test_register_detection_query_invalid_key_type(self):
     """Test registering detection query with invalid key type."""
     invalid_query_dict = {
         "foo_query": good_detection_query  # Invalid key type (str)
     }
     self.mock_package.export_extensions.return_value = {
         "detect_criteria":
         immutabledict.immutabledict(
             {GoodCommunicationType.__name__: invalid_query_dict})
     }
     regex = (r"Unable to register query {} for communication type {!r}. "
              "Detection query keys must be {} instances.".format(
                  "foo_query", GoodCommunicationType.__name__,
                  detect_criteria.QueryEnum.__name__))
     with self.assertRaisesRegex(errors.PackageRegistrationError, regex):
         package_registrar.register(self.mock_package)
예제 #16
0
 def test_register_abstract_class(self):
     """Test registering an abstract class."""
     test_cases = [("auxiliary_devices", [AbstractAuxiliaryDevice]),
                   ("primary_devices", [AbstractPrimaryDevice]),
                   ("virtual_devices", [AbstractVirtualDevice]),
                   ("communication_types", [AbstractCommunicationType]),
                   ("capability_flavors", [AbstractCapabilityFlavorDefault])
                   ]
     for extension, extension_classes in test_cases:
         with self.subTest(extension=extension,
                           extension_classes=extension_classes):
             self.mock_package.export_extensions.return_value = {
                 extension: extension_classes
             }
             with self.assertRaisesRegex(errors.PackageRegistrationError,
                                         r"must not be abstract"):
                 package_registrar.register(self.mock_package)
예제 #17
0
    def test_failed_registration_does_not_change_state(self):
        """Test that failed registration doesn't change known supported classes."""
        self.mock_package.export_extensions.return_value = {
            "primary_devices": [BadPrimaryDeviceNoLogDecorator],
            "capability_interfaces": [GoodCapabilityBase],
            "capability_flavors": [GoodCapabilityDefault]
        }
        primary_devices_before = extensions.primary_devices.copy()
        capability_interfaces_before = extensions.capability_interfaces.copy()
        capability_flavors_before = extensions.capability_flavors.copy()
        capabilities_before = extensions.capabilities.copy()

        with self.assertRaises(errors.PackageRegistrationError):
            package_registrar.register(self.mock_package)
        self.assertEqual(primary_devices_before, extensions.primary_devices)
        self.assertEqual(capability_interfaces_before,
                         extensions.capability_interfaces)
        self.assertEqual(capability_flavors_before,
                         extensions.capability_flavors)
        self.assertEqual(capabilities_before, extensions.capabilities)
예제 #18
0
 def test_register_class_with_incorrect_base_class(self):
     """Test registering a class with unexpected base class."""
     test_cases = [
         ("auxiliary_devices", auxiliary_device.AuxiliaryDevice),
         ("primary_devices", gazoo_device_base.GazooDeviceBase),
         ("virtual_devices", gazoo_device_base.GazooDeviceBase),
         ("communication_types", communication_types.CommunicationType),
         ("capability_interfaces", capability_base.CapabilityBase),
         ("capability_flavors", capability_base.CapabilityBase)
     ]
     for extension, expected_base_class in test_cases:
         with self.subTest(extension=extension,
                           expected_base_class=expected_base_class):
             self.mock_package.export_extensions.return_value = {
                 extension: [ClassNotInheritingFromInterface]
             }
             regex = r"must be subclasses of {}".format(
                 expected_base_class.__name__)
             with self.assertRaisesRegex(errors.PackageRegistrationError,
                                         regex):
                 package_registrar.register(self.mock_package)
예제 #19
0
    def test_register_detection_query_invalid_query(self):
        """Test registering detection query with invalid query type."""
        invalid_query_dict_bad_query_type = {
            GoodQueryKey.some_valid_query: None  # Invalid query type (None)
        }
        self.mock_package.export_extensions.return_value = {
            "detect_criteria":
            immutabledict.immutabledict({
                GoodCommunicationType.__name__:
                invalid_query_dict_bad_query_type
            })
        }
        regex = (r"Unable to register query {} for communication type {!r}. "
                 "Detection queries must be callable".format(
                     GoodQueryKey.some_valid_query,
                     GoodCommunicationType.__name__))

        with self.assertRaisesRegex(errors.PackageRegistrationError, regex):
            package_registrar.register(self.mock_package)

        invalid_query_dict_bad_args = {
            GoodQueryKey.some_valid_query: lambda: None  # Invalid query args
        }
        self.mock_package.export_extensions.return_value = {
            "detect_criteria":
            immutabledict.immutabledict(
                {GoodCommunicationType.__name__: invalid_query_dict_bad_args})
        }

        args = r"\('address', 'detect_logger', 'create_switchboard_func'\)"
        regex = (
            r"Unable to register query {} for communication type {!r}. "
            r"Detection queries must be callable functions which accept 3 "
            r"arguments: {}".format(GoodQueryKey.some_valid_query,
                                    GoodCommunicationType.__name__, args))

        with self.assertRaisesRegex(errors.PackageRegistrationError, regex):
            package_registrar.register(self.mock_package)
예제 #20
0
from gazoo_device import manager
from gazoo_device import mobly_controller
from gazoo_device import package_registrar
from gazoo_device.utility import multiprocessing_utils
from gazoo_device.utility import signal_utils

Manager = manager.Manager
register = package_registrar.register
version = _version.version
__version__ = _version.version

# For Mobly integration
MOBLY_CONTROLLER_CONFIG_NAME = "GazooDevice"
create = mobly_controller.create
destroy = mobly_controller.destroy
get_info = mobly_controller.get_info

multiprocessing_utils.configure_multiprocessing()

# Defend against inadvertent basicConfig, which adds log noise
logging.getLogger().addHandler(logging.NullHandler())

# Ensure that 'finally' clauses and atexit handlers run when killed by SIGTERM.
signal.signal(signal.SIGTERM, signal_utils.handle_sigterm)

# Set up logger
gdm_logger.initialize_logger()

# Register device classes and capabilities built into GDM
package_registrar.register(gazoo_device_controllers)
예제 #21
0
 def test_register_already_known_package(self):
     """Test registering package which has already been registered."""
     error_regex = r"Package 'my_extension_package' has already been registered"
     with self.assertRaisesRegex(errors.PackageRegistrationError,
                                 error_regex):
         package_registrar.register(self.mock_package)
예제 #22
0
    def test_valid_registration(self):
        """Test registering a valid extension dictionary."""
        self.mock_package.export_extensions.return_value = {
            "auxiliary_devices": [GoodAuxiliaryDevice],
            "primary_devices": [GoodPrimaryDevice],
            "virtual_devices": [GoodVirtualDevice],
            "communication_types": [GoodCommunicationType],
            "detect_criteria": {
                GoodCommunicationType.__name__:
                immutabledict.immutabledict(
                    {GoodQueryKey.some_valid_query: good_detection_query})
            },
            "capability_interfaces": [GoodCapabilityBase],
            "capability_flavors": [GoodCapabilityDefault],
            "keys": [GoodKey],
            "manager_cli_mixin": GoodManagerCliMixin,
        }

        package_registrar.register(self.mock_package)
        self.assertEqual(
            extensions.package_info, {
                _TEST_PACKAGE_NAME:
                immutabledict.immutabledict(
                    {
                        "version": self.mock_package.__version__,
                        "key_download_function":
                        self.mock_package.download_key,
                        "import_path": _TEST_PACKAGE_IMPORT_PATH,
                    })
            })
        self.assertEqual(extensions.auxiliary_devices, [GoodAuxiliaryDevice])
        self.assertEqual(extensions.primary_devices, [GoodPrimaryDevice])
        self.assertEqual(extensions.virtual_devices, [GoodVirtualDevice])
        self.assertEqual(
            extensions.communication_types,
            {GoodCommunicationType.__name__: GoodCommunicationType})
        self.assertEqual(
            extensions.detect_criteria, {
                GoodCommunicationType.__name__: {
                    GoodQueryKey.some_valid_query: good_detection_query
                }
            })
        self.assertEqual(
            extensions.capability_interfaces["good_capability_base"],
            GoodCapabilityBase)
        self.assertEqual(
            extensions.capability_flavors["good_capability_default"],
            GoodCapabilityDefault)
        self.assertEqual(extensions.capabilities["good_capability"],
                         "good_capability_base")
        self.assertEqual(extensions.keys, [GoodKey])

        # Test registering a different package which extends detect criteria for
        # communication type registered by the first package.
        self.mock_package.__name__ = "some_parent_package.another_extension_package"
        self.mock_package.export_extensions.return_value = {
            "detect_criteria": {
                GoodCommunicationType.__name__:
                immutabledict.immutabledict({
                    GoodQueryKey.another_valid_query:
                    another_good_detection_query,
                })
            }
        }
        package_registrar.register(self.mock_package)

        self.assertIn(_TEST_PACKAGE_NAME, extensions.package_info)
        self.assertIn("another_extension_package", extensions.package_info)
        self.assertEqual(
            extensions.detect_criteria, {
                GoodCommunicationType.__name__: {
                    GoodQueryKey.some_valid_query: good_detection_query,
                    GoodQueryKey.another_valid_query:
                    another_good_detection_query,
                }
            })