blob: afa95d630a851bdba770e297ad945207ffa370af [file] [log] [blame]
// Copyright 2022 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 'dart:typed_data';
import 'dart:math';
import 'package:collection/collection.dart';
import 'package:fidl/fidl.dart' as fidl;
import 'package:fidl_fuchsia_component/fidl_async.dart' as fcomponent;
import 'package:fidl_fuchsia_component_config/fidl_async.dart' as fconfig;
import 'package:fidl_fuchsia_component_decl/fidl_async.dart' as fdecl;
import 'package:fidl_fuchsia_diagnostics_types/fidl_async.dart' as fdiagtypes;
import 'package:fidl_fuchsia_component_test/fidl_async.dart' as ftest;
import 'package:fidl_fuchsia_io/fidl_async.dart' as fio;
import 'package:fuchsia_logger/logger.dart';
import 'package:fuchsia_services/services.dart' as services;
import 'package:zircon/zircon.dart';
import 'error.dart';
import 'local_component_handles.dart';
import 'internal/local_component.dart';
import 'internal/local_component_runner.dart';
/// The default name of the child component collection that contains built
/// topologies.
const defaultCollectionName = 'realm_builder';
const realmBuilderServerChildName = 'realm_builder_server';
fconfig.ValueSpec _singleValue(fconfig.SingleValue value) {
return fconfig.ValueSpec(value: fconfig.Value.withSingle(value));
}
fconfig.ValueSpec _vectorValue(fconfig.VectorValue value) {
return fconfig.ValueSpec(value: fconfig.Value.withVector(value));
}
/// The properties for a child being added to a realm
class ChildOptions {
fdecl.StartupMode _startup;
String? environment;
fdecl.OnTerminate _onTerminate;
ChildOptions()
: _startup = fdecl.StartupMode.lazy,
_onTerminate = fdecl.OnTerminate.none;
fdecl.StartupMode get startup => _startup;
void eager() {
_startup = fdecl.StartupMode.eager;
}
fdecl.OnTerminate get onTerminate => _onTerminate;
void rebootOnTerminate() {
_onTerminate = fdecl.OnTerminate.reboot;
}
/// Convert the current value into the FIDL table-backed ChildOptions, via its
/// `const` constructor.
ftest.ChildOptions toFidlType() {
return ftest.ChildOptions(
startup: _startup,
environment: environment,
onTerminate: _onTerminate,
);
}
@override
String toString() {
return 'ChildOptions(startup = $_startup, environment = $environment, '
'onTerminate = $onTerminate)';
}
}
/// Provides convenience functions for using the instance. Components v2 only.
class ScopedInstanceFactory {
fcomponent.RealmProxy? realmProxy;
final String _collectionName;
ScopedInstanceFactory(this._collectionName);
String get collectionName => _collectionName;
Future<ScopedInstance> newInstance(String url) {
final id = Random().nextInt(1 << 32);
final childName = 'auto-${id.toRadixString(16)}';
return newNamedInstance(childName, url);
}
Future<ScopedInstance> newNamedInstance(String childName, String url) async {
late final fcomponent.RealmProxy realmProxy;
if (this.realmProxy != null) {
realmProxy = this.realmProxy!;
} else {
realmProxy = fcomponent.RealmProxy();
await (services.Incoming.fromSvcPath()..connectToService(realmProxy))
.close();
}
final collectionRef = fdecl.CollectionRef(name: _collectionName);
final childDecl = fdecl.Child(
name: childName,
url: url,
startup: fdecl.StartupMode.lazy,
);
final childArgs = fcomponent.CreateChildArgs();
await realmProxy.createChild(
collectionRef,
childDecl,
childArgs,
);
final childRef = fdecl.ChildRef(
name: childName,
collection: _collectionName,
);
final exposedDir = fio.DirectoryProxy();
await realmProxy.openExposedDir(
childRef,
exposedDir.ctrl.request(),
);
return ScopedInstance._(
realmProxy,
childName,
_collectionName,
exposedDir,
);
}
}
/// Provides convenience functions for using the instance. Components v2 only.
class ScopedInstance {
final fcomponent.RealmProxy _realm;
final String _childName;
final String _collectionName;
final fio.DirectoryProxy _exposedDir;
const ScopedInstance._(
this._realm,
this._childName,
this._collectionName,
this._exposedDir,
);
static Future<ScopedInstance> create({
required String collectionName,
required String url,
String? childName,
}) {
final factory = ScopedInstanceFactory(collectionName);
if (childName == null) {
return factory.newInstance(url);
} else {
return factory.newNamedInstance(childName, url);
}
}
/// Returns a reference to the component's read-only exposed directory.
fio.DirectoryProxy get exposedDir => _exposedDir;
String get childName => _childName;
String get collectionName => _collectionName;
/// Connect to exposed fuchsia.component.Binder protocol of instance, thus
/// triggering it to start.
///
/// Note: This will only work if the component exposes this protocol in its
/// manifest.
fcomponent.BinderProxy connectToBinder() {
try {
return connectToProtocolAtExposedDir(fcomponent.BinderProxy());
} on Exception catch (err) {
log.severe('failed to connect to fuchsia.component.Binder: $err');
rethrow;
}
}
/// Connect to an instance of a FIDL protocol hosted in the component's
/// exposed directory, based on the type of the given proxy. The given
/// proxy is returned.
T connectToProtocolAtExposedDir<T extends fidl.AsyncProxy>(T proxy) {
services.Incoming.withDirectory(exposedDir).connectToService(proxy);
return proxy;
}
/// Connects to an instance of a FIDL protocol hosted in the component's
/// exposed directory using the given [serverEnd]. Any proxy of the same
/// protocol type is also required, in order to get the protocol name.
void connectRequestToProtocolAtExposedDir<P>(
fidl.InterfaceRequest<P> serverEnd, fidl.AsyncProxy<P> anyProxy) {
final ctrl = anyProxy.ctrl;
final protocolName = ctrl.$serviceName ?? ctrl.$interfaceName!;
connectToNamedProtocolAtExposedDir(protocolName, serverEnd.passChannel()!);
}
/// Connects to an instance of a FIDL protocol called [protocolName] hosted in
/// the component's exposed directory, using the given [serverEnd].
void connectToNamedProtocolAtExposedDir(
String protocolName, Channel serverEnd) {
services.Incoming.withDirectory(exposedDir)
.connectToServiceByNameWithChannel(protocolName, serverEnd);
}
/// Connects to an instance of a FIDL protocol called [protocolName] hosted at
/// the absolute path from the exposed directory.
T connectToProtocolAtPath<T extends fidl.AsyncProxy>(
T proxy, String protocolPath) {
var serverEnd = proxy.ctrl.request().passChannel();
exposedDir.open(
fio.OpenFlags.rightReadable | fio.OpenFlags.rightWritable,
fio.modeTypeService,
protocolPath,
fidl.InterfaceRequest<fio.Node>(serverEnd),
);
return proxy;
}
/// Connects to an instance of a FIDL protocol hosted at the protocol name,
/// in the given directory.
T connectToProtocolInDirPath<T extends fidl.AsyncProxy>(
T proxy,
String dirPath,
) {
return connectToProtocolAtPath(
proxy,
'$dirPath/${proxy.ctrl.$serviceName}',
);
}
/// Call [close()] before the [ScopedInstance] goes out of scope (since
/// Dart doesn't support destructors or RAII destruction when an object
/// goes out of scope).
///
/// This will ensure that the message goes out to the realm.
///
/// Note: `destroyChild()`s returned Future may not complete immediately.
/// The process of destroying a component and its children includes various
/// confirmations, with time-outs in case a component doesn't stop cleanly.
/// This `close()` method sends the `destroyChild()` request and returns
/// without waiting for confirmation. It returns `void` instead of the
/// [Future] so callers are discouraged from awaiting the close(), which can
/// be the source of non-deterministic delays and/or timeouts.
void close() {
_realm.destroyChild(fdecl.ChildRef(
name: childName,
collection: collectionName,
));
}
}
enum RefType {
capability,
child,
collection,
debug,
framework,
parent,
self,
}
extension RefEnumToName on RefType {
String enumName() {
final segments = toString().split('.');
return segments.isEmpty ? '' : segments.last;
}
}
class Ref {
final RefType type;
String? name;
List<String>? scope;
Ref.from(Ref o)
: type = o.type,
name = o.name,
scope = o.scope?.toList();
Ref.capability(this.name) : type = RefType.capability;
Ref.collection(this.name) : type = RefType.collection;
Ref.debug() : type = RefType.debug;
Ref.framework() : type = RefType.framework;
Ref.parent() : type = RefType.parent;
Ref.self() : type = RefType.self;
Ref.child(ChildRef childRef)
: type = RefType.child,
name = childRef.name,
scope = childRef.scope;
Ref.childFromSubRealm(SubRealmBuilder subRealm)
: type = RefType.child,
name = subRealm.realmPath.isEmpty ? null : subRealm.realmPath.last,
scope = subRealm.realmPath.toList()..removeLast() {
if (name == null) {
throw Exception(
'API bug: It should not be possible to call fromSubRealmBuilder '
'with a top-level SubRealmBuilder');
}
}
void checkScope(List<String> realmScope) {
if (scope != null && !ListEquality().equals(scope, realmScope)) {
throw RefUsedInWrongRealmException(this, realmScope.join('/'));
}
}
@override
bool operator ==(Object o) =>
o is Ref &&
type == o.type &&
name == o.name &&
ListEquality().equals(scope, o.scope);
@override
int get hashCode => type.hashCode + name.hashCode + scope.hashCode;
/// Convert the current value into the FIDL table-backed ChildOptions, via its
/// `const` constructor.
fdecl.Ref toFidlType() {
switch (type) {
case RefType.capability:
return fdecl.Ref.withCapability(fdecl.CapabilityRef(name: name!));
case RefType.child:
return fdecl.Ref.withChild(fdecl.ChildRef(name: name!));
case RefType.collection:
return fdecl.Ref.withCollection(fdecl.CollectionRef(name: name!));
case RefType.debug:
return fdecl.Ref.withDebug(fdecl.DebugRef());
case RefType.framework:
return fdecl.Ref.withFramework(fdecl.FrameworkRef());
case RefType.parent:
return fdecl.Ref.withParent(fdecl.ParentRef());
case RefType.self:
return fdecl.Ref.withSelf(fdecl.SelfRef());
}
}
@override
String toString() {
return '${type.enumName()} Ref(name=${name == null ? 'null' : '"$name"'}, '
'scope=$scope)';
}
}
class ChildRef {
String name;
List<String>? scope;
ChildRef(this.name, [this.scope]);
void checkScope(List<String> realmScope) {
if (scope != null && !ListEquality().equals(scope, realmScope)) {
throw RefUsedInWrongRealmException(
Ref.child(this),
realmScope.join('/'),
);
}
}
ChildRef.fromSubRealmBuilder(SubRealmBuilder input)
: name = input.realmPath.isEmpty ? '' : input.realmPath.last,
scope = input.realmPath.toList()..removeLast() {
if (name == '') {
throw Exception(
'API bug: It should not be possible to call fromSubRealmBuilder '
'with a top-level SubRealmBuilder');
}
}
ChildRef.fromChildRef(ChildRef input)
: name = input.name,
scope = input.scope?.toList();
@override
String toString() {
return 'ChildRef(name = $name, scope = $scope)';
}
}
extension DependencyTypeEnumToName on fdecl.DependencyType {
String enumName() {
final segments = toString().split('.');
return segments.isEmpty ? '' : segments.last;
}
}
abstract class Capability {
String name;
/// The name the targets will see the capability as.
String? as;
Capability(this.name, [this.as]);
ftest.Capability toFidlType();
String _capabilityToString(String? additionalFields) {
return '$runtimeType(name = $name, '
'as = $as${additionalFields != null ? ', $additionalFields' : ''})';
}
}
/// A protocol capability, which may be routed between components. Created by
/// [Capability.protocol()].
class ProtocolCapability extends Capability {
fdecl.DependencyType type;
/// The path at which this protocol capability will be provided or used. Only
/// relevant if the route's source or target is a legacy or local component,
/// as these are the only components that realm builder will generate a modern
/// component manifest for.
String? path;
ProtocolCapability(
name, {
as,
this.type = fdecl.DependencyType.strong,
this.path,
}) : super(name, as);
ProtocolCapability.from(ProtocolCapability o)
: type = o.type,
path = o.path,
super(o.name, o.as);
/// Marks any offers involved in this route as "weak", which will cause this
/// route to be ignored when determining shutdown ordering.
void weak() {
type = fdecl.DependencyType.weak;
}
@override
ftest.Capability toFidlType() {
return ftest.Capability.withProtocol(ftest.Protocol(
name: name,
as: as,
type: type,
path: path,
));
}
@override
bool operator ==(Object o) =>
o is ProtocolCapability &&
name == o.name &&
as == o.as &&
type == o.type &&
path == o.path;
@override
int get hashCode =>
name.hashCode + as.hashCode + type.hashCode + path.hashCode;
@override
String toString() {
return _capabilityToString('type = ${type.enumName()}, path = $path');
}
}
/// A directory capability, which may be routed between components. Created by
/// [Capability.directory()].
class DirectoryCapability extends Capability {
fdecl.DependencyType type;
/// The rights the target will be allowed to use when accessing the directory.
fio.Operations? rights;
/// The sub-directory of the directory that the target will be given access
/// to.
String? subdir;
/// The path at which this directory will be provided or used. Only relevant
/// if the route's source or target is a local component.
String? path;
DirectoryCapability(
name, {
as,
this.type = fdecl.DependencyType.strong,
this.rights,
this.subdir,
this.path,
}) : super(name, as);
DirectoryCapability.from(DirectoryCapability o)
: type = o.type,
path = o.path,
super(o.name, o.as);
/// Marks any offers involved in this route as "weak", which will cause this
/// route to be ignored when determining shutdown ordering.
void weak() {
type = fdecl.DependencyType.weak;
}
@override
ftest.Capability toFidlType() {
return ftest.Capability.withDirectory(ftest.Directory(
name: name,
as: as,
type: type,
rights: rights,
subdir: subdir,
path: path,
));
}
@override
bool operator ==(Object o) =>
o is DirectoryCapability &&
name == o.name &&
as == o.as &&
type == o.type &&
rights == o.rights &&
subdir == o.subdir &&
path == o.path;
@override
int get hashCode =>
name.hashCode +
as.hashCode +
type.hashCode +
rights.hashCode +
subdir.hashCode +
path.hashCode;
@override
String toString() {
return _capabilityToString(
'type = ${type.enumName()}, rights = $rights, subdir = $subdir, '
'path = $path');
}
}
/// A storage capability, which may be routed between components. Created by
/// [Capability.storage()].
class StorageCapability extends Capability {
/// The path at which this storage capability will be provided or used. Only
/// relevant if the route's source or target is a legacy or local component,
/// as these are the only components that realm builder will generate a modern
/// component manifest for.
String? path;
StorageCapability(name, {as, this.path}) : super(name, as);
StorageCapability.from(StorageCapability o)
: path = o.path,
super(o.name, o.as);
@override
ftest.Capability toFidlType() {
return ftest.Capability.withStorage(
ftest.Storage(name: name, as: as, path: path));
}
@override
bool operator ==(Object o) =>
o is StorageCapability && name == o.name && as == o.as && path == o.path;
@override
int get hashCode => name.hashCode + as.hashCode + path.hashCode;
@override
String toString() {
return _capabilityToString('path = $path');
}
}
/// A service capability, which may be routed between components. Created by
/// [Capability.service()].
class ServiceCapability extends Capability {
/// The path at which this service capability will be provided or used. Only
/// relevant if the route's source or target is a legacy or local component,
/// as these are the only components that realm builder will generate a modern
/// component manifest for.
String? path;
ServiceCapability(
name, {
as,
this.path,
}) : super(name, as);
ServiceCapability.from(ServiceCapability o)
: path = o.path,
super(o.name, o.as);
@override
ftest.Capability toFidlType() {
return ftest.Capability.withService(ftest.Service(
name: name,
as: as,
path: path,
));
}
@override
bool operator ==(Object o) =>
o is ServiceCapability && name == o.name && as == o.as && path == o.path;
@override
int get hashCode => name.hashCode + as.hashCode + path.hashCode;
@override
String toString() {
return _capabilityToString('path = $path');
}
}
/// A route of one or more capabilities from one point in the realm to one or
/// more targets.
class Route {
final List<Capability> _capabilities;
Ref? _from;
final List<Ref> _to;
Route()
: _capabilities = [],
_to = [];
Route.from(Route o)
: _capabilities = o._capabilities.toList(),
_from = o._from,
_to = o._to.toList();
List<Capability> getCapabilities() => _capabilities;
Ref? getFrom() => _from;
List<Ref> getTo() => _to;
void capability(Capability capability) {
_capabilities.add(capability);
}
/// Adds a source to this route. Must be called exactly once. Will panic if
/// called a second time.
void from(Ref from) {
if (_from != null) {
throw Exception('from is already set for this route');
}
_from = from;
}
void to(Ref to) {
_to.add(to);
}
@override
bool operator ==(Object o) =>
o is Route &&
ListEquality().equals(_capabilities, o._capabilities) &&
_from == o._from &&
ListEquality().equals(_to, o._to);
@override
int get hashCode => _capabilities.hashCode + _from.hashCode + _to.hashCode;
}
// [START mock_interface_dart]
typedef OnRun = Future<void> Function(
LocalComponentHandles handles,
Completer onStop,
);
// [END mock_interface_dart]
typedef OnKill = Future<void> Function(LocalComponentHandles handles);
typedef OnOnPublishDiagnostics = Stream<fdiagtypes.ComponentDiagnostics>
Function(LocalComponentHandles handles);
typedef OnStop = Future<void> Function(LocalComponentHandles handles);
/// A running instance of a created realm. Important: When a RealmInstance is no
/// longer needed, the root [ScopedInstance] must be closed--by calling
/// root.close()--to ensure the child component is not leaked. When closed, the
/// realm is destroyed, along with any components that were in the realm.
class RealmInstance {
/// The root component of this realm instance, which can be used to access
/// exposed capabilities from the realm.
final ScopedInstance root;
const RealmInstance(this.root);
}
class SubRealmBuilder {
ftest.RealmProxy realmProxy;
List<String> realmPath;
// Required for builder API but not yet implemented or used.
final _localComponentRunnerBuilder = LocalComponentRunnerBuilder();
SubRealmBuilder({
required this.realmProxy,
required this.realmPath,
});
/// Adds a child realm and returns a [SubRealmBuilder]. Capabilities can be
/// routed between parent [RealmBuilder] and the sub-realm, and then routed
/// to/from children of the sub-realm.
Future<SubRealmBuilder> addChildRealm(String name,
[ChildOptions? options]) async {
final childRealm = ftest.RealmProxy();
await realmProxy.addChildRealm(
name,
(options ?? ChildOptions()).toFidlType(),
childRealm.ctrl.request(),
);
final childPath = realmPath.toList()..add(name);
return SubRealmBuilder(
realmProxy: childRealm,
realmPath: childPath,
);
}
/// Adds a local component to the realm. the [onRun] callback will be called
/// when the component starts. Other [ComponentController] callbacks are
/// optional.
///
/// The [ComponentController] binding must be closed to indicate to Component
/// Manager that the [LocalComponent] has stopped. If [onStop] is not
/// provided, the default implementation will automatically close the
/// [ComponentController] binding. If [onRun] completes before [onStop] (if
/// called) the [ComponentController] binding will be closed automatically. If
/// [onStop] is provided, the caller can close the [ComponentController]
/// binding by calling [LocalComponentHandles.close()].
///
/// [onRun] is given the [LocalComponentHandles] (to access its namespace,
/// outgoing directory, and [ComponentController]), and a [Completer] that
/// is completed if [ComponentController.stop()] is called. The caller can
/// await the `onStopCompleter` allow the component to continue to remain
/// active until the `stop()` is received.
Future<ChildRef> addLocalChild(
String name, {
required OnRun onRun,
OnKill? onKill,
OnOnPublishDiagnostics? onOnPublishDiagnostics,
OnStop? onStop,
ChildOptions? options,
}) async {
await realmProxy.addLocalChild(
name,
(options ?? ChildOptions()).toFidlType(),
);
final childPath = realmPath + [name];
_localComponentRunnerBuilder.registerLocalComponent(
LocalComponent(
childPath.join('/'),
onRun,
onKill,
onOnPublishDiagnostics,
onStop,
),
);
return ChildRef(name, realmPath);
}
/// Adds a new component to the realm by URL.
Future<ChildRef> addChild(String name, String url,
[ChildOptions? options]) async {
await realmProxy.addChild(
name,
url,
(options ?? ChildOptions()).toFidlType(),
);
return ChildRef(name, realmPath);
}
/// Adds a new legacy component to the realm.
Future<ChildRef> addLegacyChild(String name, String legacyUrl,
[ChildOptions? options]) async {
await realmProxy.addLegacyChild(
name,
legacyUrl,
(options ?? ChildOptions()).toFidlType(),
);
return ChildRef(name, realmPath);
}
/// Adds a new component to the realm with the given component declaration
Future<ChildRef> addChildFromDecl(String name, fdecl.Component decl,
[ChildOptions? options]) async {
await realmProxy.addChildFromDecl(
name,
decl,
(options ?? ChildOptions()).toFidlType(),
);
return ChildRef(name, realmPath);
}
/// Returns a copy the decl for a child in this realm
Future<fdecl.Component> getComponentDecl(ChildRef childRef) {
childRef.checkScope(realmPath);
return realmProxy.getComponentDecl(childRef.name);
}
/// Replaces the decl for a child of this realm
Future<void> replaceComponentDecl(
ChildRef childRef,
fdecl.Component decl,
) {
childRef.checkScope(realmPath);
return realmProxy.replaceComponentDecl(childRef.name, decl);
}
/// Returns a copy the decl for a child in this realm
Future<fdecl.Component> getRealmDecl() {
return realmProxy.getRealmDecl();
}
/// Replaces the decl for this realm
Future<void> replaceRealmDecl(fdecl.Component decl) {
return realmProxy.replaceRealmDecl(decl);
}
/// Replaces a value of a given configuration field
Future<void> setConfigValue(
ChildRef childRef,
String key,
fconfig.ValueSpec value,
) {
childRef.checkScope(realmPath);
return realmProxy.setConfigValue(childRef.name, key, value);
}
/// Replaces a boolean value of a given configuration field
Future<void> setConfigValueBool(
ChildRef childRef,
String key,
// ignore: avoid_positional_boolean_parameters
bool value,
) {
return setConfigValue(
childRef,
key,
_singleValue(fconfig.SingleValue.withBool(value)),
);
}
/// Replaces a uint8 value of a given configuration field
Future<void> setConfigValueUint8(
ChildRef childRef,
String key,
int value,
) {
return setConfigValue(
childRef,
key,
_singleValue(fconfig.SingleValue.withUint8(value)),
);
}
/// Replaces a uint16 value of a given configuration field
Future<void> setConfigValueUint16(
ChildRef childRef,
String key,
int value,
) {
return setConfigValue(
childRef,
key,
_singleValue(fconfig.SingleValue.withUint16(value)),
);
}
/// Replaces a uint32 value of a given configuration field
Future<void> setConfigValueUint32(
ChildRef childRef,
String key,
int value,
) {
return setConfigValue(
childRef,
key,
_singleValue(fconfig.SingleValue.withUint32(value)),
);
}
/// Replaces a uint64 value of a given configuration field
Future<void> setConfigValueUint64(
ChildRef childRef,
String key,
int value,
) {
return setConfigValue(
childRef,
key,
_singleValue(fconfig.SingleValue.withUint64(value)),
);
}
/// Replaces a int8 value of a given configuration field
Future<void> setConfigValueInt8(
ChildRef childRef,
String key,
int value,
) {
return setConfigValue(
childRef,
key,
_singleValue(fconfig.SingleValue.withInt8(value)),
);
}
/// Replaces a int16 value of a given configuration field
Future<void> setConfigValueInt16(
ChildRef childRef,
String key,
int value,
) {
return setConfigValue(
childRef,
key,
_singleValue(fconfig.SingleValue.withInt16(value)),
);
}
/// Replaces a int32 value of a given configuration field
Future<void> setConfigValueInt32(
ChildRef childRef,
String key,
int value,
) {
return setConfigValue(
childRef,
key,
_singleValue(fconfig.SingleValue.withInt32(value)),
);
}
/// Replaces a int64 value of a given configuration field
Future<void> setConfigValueInt64(
ChildRef childRef,
String key,
int value,
) {
return setConfigValue(
childRef,
key,
_singleValue(fconfig.SingleValue.withInt64(value)),
);
}
/// Replaces a string value of a given configuration field
Future<void> setConfigValueString(
ChildRef childRef,
String key,
String value,
) {
return setConfigValue(
childRef,
key,
_singleValue(fconfig.SingleValue.withString$(value)),
);
}
/// Replaces a boolean vector value of a given configuration field
Future<void> setConfigValueBoolVector(
ChildRef childRef,
String key,
List<bool> value,
) {
return setConfigValue(
childRef,
key,
_vectorValue(fconfig.VectorValue.withBoolVector(value)),
);
}
/// Replaces a uint8 vector value of a given configuration field
Future<void> setConfigValueUint8Vector(
ChildRef childRef,
String key,
Uint8List value,
) {
return setConfigValue(
childRef,
key,
_vectorValue(fconfig.VectorValue.withUint8Vector(value)),
);
}
/// Replaces a uint16 vector value of a given configuration field
Future<void> setConfigValueUint16Vector(
ChildRef childRef,
String key,
Uint16List value,
) {
return setConfigValue(
childRef,
key,
_vectorValue(fconfig.VectorValue.withUint16Vector(value)),
);
}
/// Replaces a uint32 vector value of a given configuration field
Future<void> setConfigValueUint32Vector(
ChildRef childRef,
String key,
Uint32List value,
) {
return setConfigValue(
childRef,
key,
_vectorValue(fconfig.VectorValue.withUint32Vector(value)),
);
}
/// Replaces a uint64 vector value of a given configuration field
Future<void> setConfigValueUint64Vector(
ChildRef childRef,
String key,
Uint64List value,
) {
return setConfigValue(
childRef,
key,
_vectorValue(fconfig.VectorValue.withUint64Vector(value)),
);
}
/// Replaces a int8 vector value of a given configuration field
Future<void> setConfigValueInt8Vector(
ChildRef childRef,
String key,
Int8List value,
) {
return setConfigValue(
childRef,
key,
_vectorValue(fconfig.VectorValue.withInt8Vector(value)),
);
}
/// Replaces a int16 vector value of a given configuration field
Future<void> setConfigValueInt16Vector(
ChildRef childRef,
String key,
Int16List value,
) {
return setConfigValue(
childRef,
key,
_vectorValue(fconfig.VectorValue.withInt16Vector(value)),
);
}
/// Replaces a int32 vector value of a given configuration field
Future<void> setConfigValueInt32Vector(
ChildRef childRef,
String key,
Int32List value,
) {
return setConfigValue(
childRef,
key,
_vectorValue(fconfig.VectorValue.withInt32Vector(value)),
);
}
/// Replaces a int64 vector value of a given configuration field
Future<void> setConfigValueInt64Vector(
ChildRef childRef,
String key,
Int64List value,
) {
return setConfigValue(
childRef,
key,
_vectorValue(fconfig.VectorValue.withInt64Vector(value)),
);
}
/// Replaces a string vector value of a given configuration field
Future<void> setConfigValueStringVector(
ChildRef childRef,
String key,
List<String> value,
) {
return setConfigValue(
childRef,
key,
_vectorValue(fconfig.VectorValue.withStringVector(value)),
);
}
/// Adds a route between components within the realm
Future<void> addRoute(Route route) async {
final capabilities = route.getCapabilities();
final from = route.getFrom();
final to = route.getTo();
if (from == null) {
throw MissingSource();
}
final source = from..checkScope(realmPath);
for (final target in to) {
target.checkScope(realmPath);
}
if (capabilities.isNotEmpty) {
await realmProxy.addRoute(
capabilities.map((c) => c.toFidlType()).toList(),
source.toFidlType(),
to.map((Ref ref) => ref.toFidlType()).toList(),
);
}
}
}
/// A class that aids in the building of [RealmBuilder] objects.
///
/// This class is used to create Fuchsia component tests in Dart, using the
/// RealmBuilder framework.
///
/// ```
/// final builder = RealmBuilder();
/// builder.addChild(...);
/// builder.addRoute(...);
/// realmInstance = await builder.build();
/// realmInstance.root.connectToProtocol...
/// ```
class RealmBuilder {
late final SubRealmBuilder _rootRealm;
late final ftest.BuilderProxy _builder;
/// The realm will be launched in the collection named [collectionName].
final String collectionName;
/// Creates a new RealmBuilder.
/// [relativeUrl]: The path to a manifest to load into the realm.
/// [collectionName]: The collection to add the realm to, when launched.
static Future<RealmBuilder> create({
String? relativeUrl,
String collectionName = defaultCollectionName,
}) async {
final componentRealm = fcomponent.RealmProxy();
await (services.Incoming.fromSvcPath()..connectToService(componentRealm))
.close();
final exposedDir = fio.DirectoryProxy();
await componentRealm.openExposedDir(
fdecl.ChildRef(name: realmBuilderServerChildName),
exposedDir.ctrl.request(),
);
final realmBuilderFactory = ftest.RealmBuilderFactoryProxy();
await (services.Incoming.withDirectory(exposedDir)
..connectToService(realmBuilderFactory))
.close();
final pkgDirHandle =
fidl.InterfaceHandle<fio.Directory>(Channel.fromFile('/pkg'));
final realm = ftest.RealmProxy();
final builder = ftest.BuilderProxy();
if (relativeUrl == null) {
await realmBuilderFactory.create(
pkgDirHandle,
realm.ctrl.request(),
builder.ctrl.request(),
);
} else {
// load the manifest
await realmBuilderFactory.createFromRelativeUrl(
pkgDirHandle,
relativeUrl,
realm.ctrl.request(),
builder.ctrl.request(),
);
}
return RealmBuilder._(realm, builder, collectionName);
}
RealmBuilder._(
ftest.RealmProxy realmProxy,
this._builder,
this.collectionName,
) {
_rootRealm = SubRealmBuilder(realmProxy: realmProxy, realmPath: []);
}
SubRealmBuilder get rootRealm => _rootRealm;
ftest.BuilderProxy get builder => _builder;
/// Adds a child realm and returns a [SubRealmBuilder]. Capabilities can be
/// routed between parent [RealmBuilder] and the sub-realm, and then routed
/// to/from children of the sub-realm.
Future<SubRealmBuilder> addChildRealm(String name, [ChildOptions? options]) {
return rootRealm.addChildRealm(
name,
options ?? ChildOptions(),
);
}
/// Adds a new component to the realm by URL.
Future<ChildRef> addLocalChild(
String name, {
required OnRun onRun,
OnKill? onKill,
OnOnPublishDiagnostics? onOnPublishDiagnostics,
OnStop? onStop,
ChildOptions? options,
}) {
return rootRealm.addLocalChild(
name,
onRun: onRun,
onKill: onKill,
onOnPublishDiagnostics: onOnPublishDiagnostics,
onStop: onStop,
options: options ?? ChildOptions(),
);
}
/// Adds a new component to the realm by URL.
Future<ChildRef> addChild(String name, String url, [ChildOptions? options]) {
return rootRealm.addChild(
name,
url,
options ?? ChildOptions(),
);
}
/// Adds a new legacy component to the realm.
Future<ChildRef> addLegacyChild(String name, String legacyUrl,
[ChildOptions? options]) {
return rootRealm.addLegacyChild(
name,
legacyUrl,
options ?? ChildOptions(),
);
}
/// Adds a new component to the realm with the given component declaration
Future<ChildRef> addChildFromDecl(String name, fdecl.Component decl,
[ChildOptions? options]) {
return rootRealm.addChildFromDecl(
name,
decl,
options ?? ChildOptions(),
);
}
/// Returns a copy the decl for a child in this realm
Future<fdecl.Component> getComponentDecl(ChildRef childRef) {
return rootRealm.getComponentDecl(childRef);
}
/// Replaces the decl for a child of this realm
Future<void> replaceComponentDecl(
ChildRef childRef,
fdecl.Component decl,
) {
return rootRealm.replaceComponentDecl(childRef, decl);
}
/// Returns a copy the decl for a child in this realm
Future<fdecl.Component> getRealmDecl() {
return rootRealm.getRealmDecl();
}
/// Replaces the decl for this realm
Future<void> replaceRealmDecl(
fdecl.Component decl,
) {
return rootRealm.replaceRealmDecl(decl);
}
/// Replaces a value of a given configuration field
Future<void> setConfigValue(
ChildRef childRef,
String key,
fconfig.ValueSpec value,
) {
return rootRealm.setConfigValue(childRef, key, value);
}
/// Replaces a boolean value of a given configuration field
Future<void> setConfigValueBool(
ChildRef childRef,
String key,
// ignore: avoid_positional_boolean_parameters
bool value,
) {
return rootRealm.setConfigValueBool(childRef, key, value);
}
/// Replaces a uint8 value of a given configuration field
Future<void> setConfigValueUint8(
ChildRef childRef,
String key,
int value,
) {
return rootRealm.setConfigValueUint8(childRef, key, value);
}
/// Replaces a uint16 value of a given configuration field
Future<void> setConfigValueUint16(
ChildRef childRef,
String key,
int value,
) {
return rootRealm.setConfigValueUint16(childRef, key, value);
}
/// Replaces a uint32 value of a given configuration field
Future<void> setConfigValueUint32(
ChildRef childRef,
String key,
int value,
) {
return rootRealm.setConfigValueUint32(childRef, key, value);
}
/// Replaces a uint64 value of a given configuration field
Future<void> setConfigValueUint64(
ChildRef childRef,
String key,
int value,
) {
return rootRealm.setConfigValueUint64(childRef, key, value);
}
/// Replaces a int8 value of a given configuration field
Future<void> setConfigValueInt8(
ChildRef childRef,
String key,
int value,
) {
return rootRealm.setConfigValueInt8(childRef, key, value);
}
/// Replaces a int16 value of a given configuration field
Future<void> setConfigValueInt16(
ChildRef childRef,
String key,
int value,
) {
return rootRealm.setConfigValueInt16(childRef, key, value);
}
/// Replaces a int32 value of a given configuration field
Future<void> setConfigValueInt32(
ChildRef childRef,
String key,
int value,
) {
return rootRealm.setConfigValueInt32(childRef, key, value);
}
/// Replaces a int64 value of a given configuration field
Future<void> setConfigValueInt64(
ChildRef childRef,
String key,
int value,
) {
return rootRealm.setConfigValueInt64(childRef, key, value);
}
/// Replaces a string value of a given configuration field
Future<void> setConfigValueString(
ChildRef childRef,
String key,
String value,
) {
return rootRealm.setConfigValueString(childRef, key, value);
}
/// Replaces a boolean vector value of a given configuration field
Future<void> setConfigValueBoolVector(
ChildRef childRef,
String key,
List<bool> value,
) {
return rootRealm.setConfigValueBoolVector(childRef, key, value);
}
/// Replaces a uint8 vector value of a given configuration field
Future<void> setConfigValueUint8Vector(
ChildRef childRef,
String key,
Uint8List value,
) {
return rootRealm.setConfigValueUint8Vector(childRef, key, value);
}
/// Replaces a uint16 vector value of a given configuration field
Future<void> setConfigValueUint16Vector(
ChildRef childRef,
String key,
Uint16List value,
) {
return rootRealm.setConfigValueUint16Vector(childRef, key, value);
}
/// Replaces a uint32 vector value of a given configuration field
Future<void> setConfigValueUint32Vector(
ChildRef childRef,
String key,
Uint32List value,
) {
return rootRealm.setConfigValueUint32Vector(childRef, key, value);
}
/// Replaces a uint64 vector value of a given configuration field
Future<void> setConfigValueUint64Vector(
ChildRef childRef,
String key,
Uint64List value,
) {
return rootRealm.setConfigValueUint64Vector(childRef, key, value);
}
/// Replaces a int8 vector value of a given configuration field
Future<void> setConfigValueInt8Vector(
ChildRef childRef,
String key,
Int8List value,
) {
return rootRealm.setConfigValueInt8Vector(childRef, key, value);
}
/// Replaces a int16 vector value of a given configuration field
Future<void> setConfigValueInt16Vector(
ChildRef childRef,
String key,
Int16List value,
) {
return rootRealm.setConfigValueInt16Vector(childRef, key, value);
}
/// Replaces a int32 vector value of a given configuration field
Future<void> setConfigValueInt32Vector(
ChildRef childRef,
String key,
Int32List value,
) {
return rootRealm.setConfigValueInt32Vector(childRef, key, value);
}
/// Replaces a int64 vector value of a given configuration field
Future<void> setConfigValueInt64Vector(
ChildRef childRef,
String key,
Int64List value,
) {
return rootRealm.setConfigValueInt64Vector(childRef, key, value);
}
/// Replaces a string vector value of a given configuration field
Future<void> setConfigValueStringVector(
ChildRef childRef,
String key,
List<String> value,
) {
return rootRealm.setConfigValueStringVector(childRef, key, value);
}
/// Adds a route between components within the realm
Future<void> addRoute(Route route) {
return rootRealm.addRoute(route);
}
/// Returns the [RealmInstance] so the test can interact with its child
/// components.
Future<RealmInstance> build({String? childName}) async {
final rootUrl =
await builder.build(rootRealm._localComponentRunnerBuilder.build());
final root = await ScopedInstance.create(
childName: childName,
collectionName: collectionName,
url: rootUrl,
);
root.connectToBinder();
return RealmInstance(root);
}
}