| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'package:fuchsia_modular/agent.dart'; |
| import 'package:fuchsia_logger/logger.dart'; |
| import 'package:fidl/fidl.dart'; |
| import 'package:fidl_fuchsia_modular_testing/fidl_async.dart'; |
| import 'package:fidl_fuchsia_sys/fidl_async.dart' as fidl_sys; |
| import 'package:fuchsia_modular/src/agent/internal/_agent_impl.dart'; // ignore: implementation_imports |
| import 'package:fuchsia_services/src/internal/_startup_context_impl.dart'; // ignore: implementation_imports |
| import 'package:fuchsia_modular/src/lifecycle/internal/_lifecycle_impl.dart'; // ignore: implementation_imports |
| |
| /// A function which is called when a new agent that is being launched. |
| typedef OnNewAgent = void Function(Agent agent); |
| |
| /// A helper class for managing the intercepting of agents inside the |
| /// [TestHarness]. |
| /// |
| /// When agents which are registered to be mocked are launchedm the [OnNewAgent] |
| /// function will be executed allowing developers to expose services. |
| /// |
| /// ``` |
| /// AgentInterceptor(testHarness.onNewComponent) |
| /// .mockAgent(agentUrl, (agent) { |
| /// agent.exposeService(myService); |
| /// }); |
| /// |
| /// connectToAgentService(agentUrl, myServiceProxy, |
| /// componentContextProxy, await getComponentContext(harness)); |
| /// ``` |
| class AgentInterceptor { |
| final _registeredAgents = <String, OnNewAgent>{}; |
| final _mockedAgents = <String, _MockedAgent>{}; |
| |
| /// Creates an instance of this which will listen to the [onNewComponentStream]. |
| AgentInterceptor( |
| Stream<TestHarness$OnNewComponent$Response> onNewComponentStream) |
| : assert(onNewComponentStream != null) { |
| onNewComponentStream.listen(_handleResponse); |
| } |
| |
| /// Register an [agentUrl] to be mocked. |
| /// |
| /// If a component with the component url which matches [agentUrl] is |
| /// registered to be interecepted by the test harness [onNewAgent] will be |
| /// called when that component is first launched. The [onNewAgent] method will |
| /// be called with an injected [Agent] object. This method can be treated like |
| /// a normal main method in a non mocked agent. |
| void mockAgent(String agentUrl, OnNewAgent onNewAgent) { |
| ArgumentError.checkNotNull(agentUrl, 'agentUrl'); |
| ArgumentError.checkNotNull(onNewAgent, 'onNewAgent'); |
| |
| if (agentUrl.isEmpty) { |
| throw ArgumentError('agentUrl must not be empty'); |
| } |
| |
| if (_registeredAgents.containsKey(agentUrl)) { |
| throw Exception( |
| 'Attempting to add [$agentUrl] twice. Agent urls must be unique'); |
| } |
| _registeredAgents[agentUrl] = onNewAgent; |
| } |
| |
| /// This method is called by the listen method when this object is used as the |
| /// handler to the [TestHarnessProxy.onNewComponent] stream. |
| void _handleResponse(TestHarness$OnNewComponent$Response response) { |
| final startupInfo = response.startupInfo; |
| final componentUrl = startupInfo.launchInfo.url; |
| if (_registeredAgents.containsKey(componentUrl)) { |
| final mockedAgent = _MockedAgent( |
| startupInfo: startupInfo, |
| interceptedComponentRequest: response.interceptedComponent, |
| ); |
| _mockedAgents[componentUrl] = mockedAgent; |
| _registeredAgents[componentUrl](mockedAgent.agent); |
| } else { |
| log.info( |
| 'Skipping launched component [$componentUrl] because it was not registered'); |
| } |
| } |
| } |
| |
| /// A helper class which helps manage the lifecyle of a mocked agent |
| class _MockedAgent { |
| /// The intercepted component. This object can be used to control the |
| /// launched component. |
| final InterceptedComponentProxy interceptedComponent = |
| InterceptedComponentProxy(); |
| |
| /// The instance of the [Agent] which is running in this environment |
| AgentImpl agent; |
| |
| /// The startup context for this environment |
| StartupContextImpl context; |
| |
| /// The lifecycle service for this environment |
| LifecycleImpl lifecycle; |
| |
| _MockedAgent({ |
| fidl_sys.StartupInfo startupInfo, |
| InterfaceHandle<InterceptedComponent> interceptedComponentRequest, |
| }) { |
| context = StartupContextImpl.from(startupInfo); |
| agent = AgentImpl(startupContext: context); |
| lifecycle = LifecycleImpl(context: context) |
| ..addTerminateListener(() async { |
| interceptedComponent.ctrl.close(); |
| }); |
| |
| interceptedComponent.ctrl.bind(interceptedComponentRequest); |
| } |
| } |