diff --git a/mlos_bench/mlos_bench/config/schemas/services/mock-service-subschema.json b/mlos_bench/mlos_bench/config/schemas/services/mock-service-subschema.json new file mode 100644 index 00000000000..228cdd49590 --- /dev/null +++ b/mlos_bench/mlos_bench/config/schemas/services/mock-service-subschema.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/microsoft/MLOS/main/mlos_bench/mlos_bench/config/schemas/services/mock-service-subschema.json", + "title": "mlos_bench Mock Service config", + "description": "config for an mlos_bench Mock Service", + "type": "object", + "properties": { + "class": { + "enum": [ + "mlos_bench.tests.services.mock_service.MockServiceBase", + "mlos_bench.tests.services.mock_service.MockServiceChild" + ] + }, + "config": { + "type": "object", + "$comment": "simple class for testing - no config properties accepted atm", + "unevaluatedProperties": false + } + }, + "required": ["class"] +} diff --git a/mlos_bench/mlos_bench/config/schemas/services/service-schema.json b/mlos_bench/mlos_bench/config/schemas/services/service-schema.json index ecf55478e35..f21a177ad4b 100644 --- a/mlos_bench/mlos_bench/config/schemas/services/service-schema.json +++ b/mlos_bench/mlos_bench/config/schemas/services/service-schema.json @@ -19,6 +19,9 @@ { "$ref": "./local/temp-dir-context-service-subschema.json" }, + { + "$ref": "./mock-service-subschema.json" + }, { "$ref": "./local/mock/mock-local-exec-service-subschema.json" }, diff --git a/mlos_bench/mlos_bench/tests/services/mock_service.py b/mlos_bench/mlos_bench/tests/services/mock_service.py new file mode 100644 index 00000000000..835738015ba --- /dev/null +++ b/mlos_bench/mlos_bench/tests/services/mock_service.py @@ -0,0 +1,61 @@ +# +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# +""" +Basic MockService for testing. + +See Also: test_service_method_registering.py +""" + +from typing import Callable, Dict, List, Optional, Protocol, Union, runtime_checkable + +from mlos_bench.services.base_service import Service + + +@runtime_checkable +class SupportsSomeMethod(Protocol): + """Protocol for some_method""" + + def some_method(self) -> str: + """some_method""" + + def some_other_method(self) -> str: + """some_other_method""" + + +class MockServiceBase(Service, SupportsSomeMethod): + """A base service class for testing.""" + + def __init__( + self, + config: Optional[dict] = None, + global_config: Optional[dict] = None, + parent: Optional[Service] = None, + methods: Optional[Union[Dict[str, Callable], List[Callable]]] = None) -> None: + super().__init__( + config, + global_config, + parent, + self.merge_methods(methods, [ + self.some_method, + self.some_other_method, + ])) + + def some_method(self) -> str: + """some_method""" + return f"{self}: base.some_method" + + def some_other_method(self) -> str: + """some_other_method""" + return f"{self}: base.some_other_method" + + +class MockServiceChild(MockServiceBase, SupportsSomeMethod): + """A child service class for testing.""" + + # Intentionally includes no constructor. + + def some_method(self) -> str: + """some_method""" + return f"{self}: child.some_method" diff --git a/mlos_bench/mlos_bench/tests/services/test_service_method_registering.py b/mlos_bench/mlos_bench/tests/services/test_service_method_registering.py new file mode 100644 index 00000000000..5583fa2b1dd --- /dev/null +++ b/mlos_bench/mlos_bench/tests/services/test_service_method_registering.py @@ -0,0 +1,37 @@ +# +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# +""" +Unit tests for Service method registering. +""" + +from mlos_bench.services.base_service import Service + +from mlos_bench.tests.services.mock_service import SupportsSomeMethod, MockServiceBase, MockServiceChild + + +def test_service_method_register_without_constructor() -> None: + """ + Test registering a method without a constructor. + """ + some_base_service = MockServiceBase() + some_child_service = MockServiceChild() + + # create a mixin service that registers the base service methods + mixin_service = Service() + mixin_service.register(some_base_service.export()) + + # pylint complains if we try to just assert this directly + # somehow having it in a different scope makes a difference + if isinstance(mixin_service, SupportsSomeMethod): + assert mixin_service.some_method() == f"{some_base_service}: base.some_method" + assert mixin_service.some_other_method() == f"{some_base_service}: base.some_other_method" + + # register the child service + mixin_service.register(some_child_service.export()) + # check that the inheritance works as expected + assert mixin_service.some_method() == f"{some_child_service}: child.some_method" + assert mixin_service.some_other_method() == f"{some_child_service}: base.some_other_method" + else: + assert False