blob: 5723faa424eee38672621a38dca2c97d92ab5f06 [file] [log] [blame]
// 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);
}
}