blob: e6d0f5d5f80f12f3700b170b5a965839d928ef84 [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 'dart:async';
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_modular/src/lifecycle/internal/_lifecycle_impl.dart'; // ignore: implementation_imports
import 'test_harness_fixtures.dart';
/// 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);
/// });
///
/// deprecatedConnectToAgentService(agentUrl, myServiceProxy,
/// componentContextProxy, await getComponentContext(harness));
/// ```
class AgentInterceptor {
final _registeredAgents = <String, OnNewAgent>{};
final _mockedAgents = <String, _MockedAgent>{};
late StreamSubscription<TestHarness$OnNewComponent$Response>
_streamSubscription;
/// Creates an instance of this which will listen to the [onNewComponentStream].
AgentInterceptor(
Stream<TestHarness$OnNewComponent$Response> onNewComponentStream) {
_streamSubscription = onNewComponentStream.listen(_handleResponse);
}
/// Dispose of the interceptor.
///
/// This method will cancel the onNewComponent stream subscription.
/// The object is no longer valid after this method is called.
void dispose() {
_streamSubscription.cancel();
}
/// 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 lifecycle service for this environment
LifecycleImpl? lifecycle;
_MockedAgent({
required fidl_sys.StartupInfo startupInfo,
required InterfaceHandle<InterceptedComponent> interceptedComponentRequest,
}) {
final outgoing = createComponentContext(startupInfo).outgoing;
agent = AgentImpl()..serve(outgoing);
// Note: we want to have a exitHandler which does not call exit here
// because this mocked agent is running inside the test process and
// calling fuchsia.exit will cause the test process to close.
lifecycle = LifecycleImpl(outgoing: outgoing, exitHandler: (_) {})
..addTerminateListener(() async {
interceptedComponent.ctrl.close();
});
interceptedComponent.ctrl.bind(interceptedComponentRequest);
}
}