| // 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 'package:fuchsia_component_test/realm_builder.dart'; |
| |
| import 'package:fidl/fidl.dart' as fidl; |
| import 'package:fidl_fidl_examples_routing_echo/fidl_async.dart' as fecho; |
| import 'package:fidl_fuchsia_component/fidl_async.dart' as fcomponent; |
| import 'package:fidl_fuchsia_component_test/fidl_async.dart' as fctest; |
| import 'package:fidl_fuchsia_component_decl/fidl_async.dart' as fdecl; |
| import 'package:fidl_fuchsia_io/fidl_async.dart' as fio; |
| import 'package:fidl_fuchsia_logger/fidl_async.dart' as flogger; |
| |
| import 'package:fuchsia_logger/logger.dart'; |
| import 'package:fuchsia_services/services.dart' as services; |
| |
| import 'package:test/test.dart'; |
| |
| import 'dart:convert' show utf8; |
| import 'dart:typed_data'; |
| |
| const String v1EchoClientUrl = |
| 'fuchsia-pkg://fuchsia.com/dart_realm_builder_unittests#meta/echo_client.cmx'; |
| const String v2EchoClientUrl = '#meta/echo_client.cm'; |
| const String v1EchoServerUrl = |
| 'fuchsia-pkg://fuchsia.com/dart_realm_builder_unittests#meta/echo_server.cmx'; |
| const String v2EchoServerUrl = '#meta/echo_server.cm'; |
| const String v2EchoClientWithBinderUrl = '#meta/echo_client_with_binder.cm'; |
| const String v2EchoClientWithBinderArg = 'Hello Fuchsia!'; |
| const String v2EchoClientStructuredConfigUrl = '#meta/echo_client_sc.cm'; |
| |
| void checkCommonExceptions(Exception err, StackTrace stacktrace) { |
| if (err is fidl.MethodException<fcomponent.Error>) { |
| late final String errorName; |
| for (final name in fcomponent.Error.$valuesMap.keys) { |
| if (err.value == fcomponent.Error.$valuesMap[name]) { |
| errorName = name; |
| break; |
| } |
| } |
| log.warning('fidl.$err: fuchsia.component.Error.$errorName'); |
| } else if (err is fidl.MethodException<fctest.RealmBuilderError>) { |
| late final String errorName; |
| for (final name in fctest.RealmBuilderError.$valuesMap.keys) { |
| if (err.value == fctest.RealmBuilderError.$valuesMap[name]) { |
| errorName = name; |
| break; |
| } |
| } |
| log.warning('fidl.$err: fuchsia.component.test.Error.$errorName'); |
| } else if (err is fidl.MethodException) { |
| log.warning('fidl.MethodException<${err.value.runtimeType}>($err)'); |
| } else if (err is fidl.FidlError) { |
| log.warning('fidl.${err.runtimeType}($err), FidlErrorCode: ${err.code}'); |
| } else { |
| log.warning('caught exception: ${err.runtimeType}($err)'); |
| } |
| log.warning('stacktrace (if available)...\n${stacktrace.toString()}'); |
| } |
| |
| void main() { |
| setupLogger(name: 'fuchsia-component-test-dart-tests'); |
| |
| group('realm builder tests', () { |
| group('RealmBuilder with CFv2 child', () { |
| test('RealmBuilder.create()', () async { |
| final builder = await RealmBuilder.create(); |
| expect( |
| builder.addChild( |
| 'v2EchoServer', |
| v2EchoServerUrl, |
| ChildOptions()..eager(), |
| ), |
| completes, |
| ); |
| }); |
| |
| test('RealmBuilder with legacy (CFv1) child', () async { |
| final builder = await RealmBuilder.create(); |
| expect( |
| builder.addLegacyChild( |
| 'v1EchoServer', |
| v1EchoServerUrl, |
| ChildOptions(), |
| ), |
| completes, |
| ); |
| }); |
| |
| test('RealmBuilder from Component decl', () async { |
| final builder = await RealmBuilder.create(); |
| final decl = fdecl.Component(); |
| expect( |
| builder.addChildFromDecl( |
| 'componentFromDecl', |
| decl, |
| ChildOptions(), |
| ), |
| completes, |
| ); |
| }); |
| }); |
| |
| group('basic RealmBuilder tests', () { |
| test('protocol and directory capabilities', () async { |
| RealmInstance? realmInstance; |
| try { |
| final builder = await RealmBuilder.create(); |
| |
| final v2EchoServer = await builder.addChild( |
| 'v2EchoServer', |
| v2EchoServerUrl, |
| ); |
| |
| await builder.addRoute(Route() |
| ..capability(DirectoryCapability('hub')..rights = fio.rStarDir) |
| ..from(Ref.framework()) |
| ..to(Ref.parent())); |
| |
| await builder.addRoute(Route() |
| ..capability(ProtocolCapability(flogger.LogSink.$serviceName)) |
| ..from(Ref.parent()) |
| ..to(Ref.child(v2EchoServer))); |
| |
| await builder.addRoute(Route() |
| ..capability(ProtocolCapability(fecho.Echo.$serviceName)) |
| ..from(Ref.child(v2EchoServer)) |
| ..to(Ref.parent())); |
| |
| realmInstance = await builder.build(); |
| |
| final echo = realmInstance.root |
| .connectToProtocolAtExposedDir(fecho.EchoProxy()); |
| const testString = 'ECHO...Echo...echo...(echo)...'; |
| |
| final reply = await echo.echoString(testString); |
| |
| expect(testString, reply); |
| } on Exception catch (err, stacktrace) { |
| checkCommonExceptions(err, stacktrace); |
| rethrow; |
| } finally { |
| if (realmInstance != null) { |
| realmInstance.root.close(); |
| } |
| } |
| }); |
| |
| test('connectToNamedProtocol', () async { |
| RealmInstance? realmInstance; |
| try { |
| final builder = await RealmBuilder.create(); |
| |
| final v2EchoServer = await builder.addChild( |
| 'v2EchoServer', |
| v2EchoServerUrl, |
| ); |
| |
| await builder.addRoute(Route() |
| ..capability(ProtocolCapability(flogger.LogSink.$serviceName)) |
| ..from(Ref.parent()) |
| ..to(Ref.child(v2EchoServer))); |
| |
| await builder.addRoute(Route() |
| ..capability(ProtocolCapability(fecho.Echo.$serviceName)) |
| ..from(Ref.child(v2EchoServer)) |
| ..to(Ref.parent())); |
| |
| realmInstance = await builder.build(); |
| |
| final echo = fecho.EchoProxy(); |
| realmInstance.root.connectToNamedProtocolAtExposedDir( |
| fecho.Echo.$serviceName, |
| echo.ctrl.request().passChannel()!, |
| ); |
| |
| const testString = 'ECHO...Echo...echo...(echo)...'; |
| final reply = await echo.echoString(testString); |
| expect(testString, reply); |
| } finally { |
| if (realmInstance != null) { |
| realmInstance.root.close(); |
| } |
| } |
| }); |
| |
| test('connectToProtocol using legacy component', () async { |
| RealmInstance? realmInstance; |
| try { |
| final builder = await RealmBuilder.create(); |
| |
| final v1EchoServer = await builder.addLegacyChild( |
| 'v1EchoServer', |
| v1EchoServerUrl, |
| ); |
| |
| await builder.addRoute(Route() |
| ..capability(ProtocolCapability(flogger.LogSink.$serviceName)) |
| ..from(Ref.parent()) |
| ..to(Ref.child(v1EchoServer))); |
| |
| await builder.addRoute(Route() |
| ..capability(ProtocolCapability(fecho.Echo.$serviceName)) |
| ..from(Ref.child(v1EchoServer)) |
| ..to(Ref.parent())); |
| |
| realmInstance = await builder.build(); |
| |
| final echo = realmInstance.root |
| .connectToProtocolAtExposedDir(fecho.EchoProxy()); |
| |
| const testString = 'ECHO...Echo...echo...(echo)...'; |
| final reply = await echo.echoString(testString); |
| expect(testString, reply); |
| } finally { |
| if (realmInstance != null) { |
| realmInstance.root.close(); |
| } |
| } |
| }); |
| }); |
| |
| test('connect to child in subrealm', () async { |
| RealmInstance? realmInstance; |
| try { |
| const subRealmName = 'sub_realm'; |
| |
| final realmBuilder = await RealmBuilder.create(); |
| final subRealmBuilder = await realmBuilder.addChildRealm(subRealmName); |
| |
| final v2EchoServer = await subRealmBuilder.addChild( |
| 'v2EchoServer', |
| v2EchoServerUrl, |
| ); |
| |
| // Route LogSink from RealmBuilder to the subRealm. |
| await realmBuilder.addRoute(Route() |
| ..capability(ProtocolCapability(flogger.LogSink.$serviceName)) |
| ..from(Ref.parent()) |
| ..to(Ref.childFromSubRealm(subRealmBuilder))); |
| |
| // Route LogSink from the subRealm to the Echo component. |
| await subRealmBuilder.addRoute(Route() |
| ..capability(ProtocolCapability(flogger.LogSink.$serviceName)) |
| ..from(Ref.parent()) |
| ..to(Ref.child(v2EchoServer))); |
| |
| // Route the Echo service from the Echo child component to its parent |
| // (the subRealm). |
| await subRealmBuilder.addRoute(Route() |
| ..capability(ProtocolCapability(fecho.Echo.$serviceName)) |
| ..from(Ref.child(v2EchoServer)) |
| ..to(Ref.parent())); |
| |
| // Route the Echo service from the subRealm child to its parent |
| // (the RealmBuilder). |
| await realmBuilder.addRoute(Route() |
| ..capability(ProtocolCapability(fecho.Echo.$serviceName)) |
| ..from(Ref.childFromSubRealm(subRealmBuilder)) |
| ..to(Ref.parent())); |
| |
| realmInstance = await realmBuilder.build(); |
| |
| final echo = fecho.EchoProxy(); |
| realmInstance.root.connectToNamedProtocolAtExposedDir( |
| fecho.Echo.$serviceName, |
| echo.ctrl.request().passChannel()!, |
| ); |
| |
| const testString = 'ECHO...Echo...echo...(echo)...'; |
| final reply = await echo.echoString(testString); |
| expect(testString, reply); |
| } finally { |
| if (realmInstance != null) { |
| realmInstance.root.close(); |
| } |
| } |
| }); |
| |
| test('storage', () async { |
| RealmInstance? realmInstance; |
| try { |
| final builder = await RealmBuilder.create(); |
| |
| const localStorageClientName = 'localStorageUser'; |
| |
| final localChildCompleter = Completer(); |
| final localStorageClient = await builder.addLocalChild( |
| localStorageClientName, |
| options: ChildOptions()..eager(), |
| onRun: (handles, onStop) async { |
| var dataDir = handles.cloneFromNamespace('data'); |
| final exampleFile = fio.FileProxy(); |
| await dataDir.open( |
| fio.OpenFlags.rightReadable | |
| fio.OpenFlags.rightWritable | |
| fio.OpenFlags.create | |
| fio.OpenFlags.describe, |
| fio.modeTypeFile, |
| 'example_file', |
| fidl.InterfaceRequest<fio.Node>( |
| exampleFile.ctrl.request().passChannel())); |
| |
| const exampleData = 'example data'; |
| final encodedData = utf8.encode(exampleData); |
| await exampleFile.write(utf8.encode(exampleData) as Uint8List); |
| await exampleFile.seek(fio.SeekOrigin.start, 0); |
| final fileContents = |
| utf8.decode(await exampleFile.read(encodedData.length)); |
| |
| if (exampleData != fileContents) { |
| localChildCompleter.completeError( |
| Exception('Wrote "$exampleData" but read back "$fileContents"'), |
| ); |
| return; |
| } |
| localChildCompleter.complete(); // success! |
| }, |
| ); |
| |
| await builder.addRoute(Route() |
| ..capability(StorageCapability('data')..path = '/data') |
| ..from(Ref.parent()) |
| ..to(Ref.child(localStorageClient))); |
| |
| realmInstance = await builder.build(); |
| |
| await localChildCompleter.future; |
| } finally { |
| if (realmInstance != null) { |
| realmInstance.root.close(); |
| } |
| } |
| }); |
| |
| group('API checks', () { |
| test('default ChildOptions', () { |
| final foptions = ChildOptions().toFidlType(); |
| expect(foptions.startup, fdecl.StartupMode.lazy); |
| expect(foptions.environment, isNull); |
| expect(foptions.onTerminate, fdecl.OnTerminate.none); |
| }); |
| |
| test('set ChildOptions', () { |
| const envName = 'someEnv'; |
| final childOptions = ChildOptions() |
| ..eager() |
| ..rebootOnTerminate() |
| ..environment = envName; |
| final foptions = childOptions.toFidlType(); |
| expect(foptions.startup, fdecl.StartupMode.eager); |
| expect(foptions.environment, envName); |
| expect(foptions.onTerminate, fdecl.OnTerminate.reboot); |
| }); |
| |
| test('named ScopedInstance', () async { |
| const collectionName = 'someCollection'; |
| final fac = ScopedInstanceFactory(collectionName); |
| expect(fac.collectionName, collectionName); |
| }); |
| |
| test('ScopedInstance collectionName not found', () async { |
| final fac = ScopedInstanceFactory('badCollectionName'); |
| ScopedInstance? scopedInstance; |
| |
| try { |
| scopedInstance = await fac.newNamedInstance('someChild', 'badUrl'); |
| } on fidl.MethodException<fcomponent.Error> catch (err) { |
| expect(err.value, fcomponent.Error.invalidArguments); |
| } finally { |
| expect(scopedInstance, isNull); |
| } |
| |
| var caught = false; |
| try { |
| scopedInstance = |
| await fac.newNamedInstance('someChild', '#meta/someComponent.cm'); |
| } on fidl.MethodException<fcomponent.Error> catch (err) { |
| expect(err.value, fcomponent.Error.collectionNotFound); |
| caught = true; |
| } finally { |
| expect(caught, true); |
| } |
| |
| expect(scopedInstance, isNull); |
| }); |
| }); |
| |
| group('ScopedInstance checks', () { |
| test('defaults', () async { |
| final builder = await RealmBuilder.create(); |
| final realmInstance = await builder.build(); |
| final scopedInstance = realmInstance.root; |
| expect(scopedInstance.collectionName, defaultCollectionName); |
| expect(scopedInstance.childName, startsWith('auto-')); |
| final nodeInfo = await scopedInstance.exposedDir.describe(); |
| expect(nodeInfo.$tag, fio.NodeInfoTag.directory); |
| scopedInstance.close(); |
| }); |
| }); |
| |
| test('replace realm decl', () async { |
| try { |
| final builder = await RealmBuilder.create(); |
| var rootDecl = await builder.getRealmDecl(); |
| expect(rootDecl, fdecl.Component()); |
| |
| final children = (rootDecl.children != null |
| ? rootDecl.children!.toList() |
| : <fdecl.Child>[]) |
| ..add( |
| fdecl.Child( |
| name: 'example-child', |
| url: 'example://url', |
| startup: fdecl.StartupMode.eager, |
| ), |
| ); |
| |
| rootDecl = rootDecl.$cloneWith(children: fidl.Some(children)); |
| |
| await builder.replaceRealmDecl(rootDecl); |
| expect(rootDecl, await builder.getRealmDecl()); |
| } on Exception catch (err, stacktrace) { |
| checkCommonExceptions(err, stacktrace); |
| rethrow; |
| } |
| }); |
| |
| test('replace component decl', () async { |
| RealmInstance? realmInstance; |
| try { |
| final builder = await RealmBuilder.create(); |
| |
| const echoServerName = 'v2EchoServer'; |
| |
| final v2EchoServer = await builder.addChild( |
| echoServerName, |
| v2EchoServerUrl, |
| ); |
| |
| var decl = await builder.getComponentDecl(v2EchoServer); |
| |
| final exposes = |
| (decl.exposes != null ? decl.exposes!.toList() : <fdecl.Expose>[]) |
| ..add( |
| fdecl.Expose.withProtocol( |
| fdecl.ExposeProtocol( |
| source: fdecl.Ref.withSelf(fdecl.SelfRef()), |
| sourceName: fecho.Echo.$serviceName, |
| target: fdecl.Ref.withParent(fdecl.ParentRef()), |
| targetName: 'renamedEchoService', |
| ), |
| ), |
| ); |
| |
| decl = decl.$cloneWith(exposes: fidl.Some(exposes)); |
| |
| await builder.replaceComponentDecl(v2EchoServer, decl); |
| |
| // Route logging to child |
| await builder.addRoute(Route() |
| ..capability(ProtocolCapability(flogger.LogSink.$serviceName)) |
| ..from(Ref.parent()) |
| ..to(Ref.child(v2EchoServer))); |
| |
| await builder.addRoute(Route() |
| ..capability(ProtocolCapability('renamedEchoService')) |
| ..from(Ref.child(v2EchoServer)) |
| ..to(Ref.parent())); |
| |
| // Start the realmInstance. The EchoServer is not "eager", so it should |
| // not start automatically. |
| realmInstance = await builder.build(); |
| |
| final echo = realmInstance.root.connectToProtocolAtPath( |
| fecho.EchoProxy(), |
| 'renamedEchoService', |
| ); |
| const testString = 'ECHO...Echo...echo...(echo)...'; |
| |
| final reply = await echo.echoString(testString); |
| |
| expect(testString, reply); |
| } on Exception catch (err, stacktrace) { |
| checkCommonExceptions(err, stacktrace); |
| rethrow; |
| } finally { |
| if (realmInstance != null) { |
| realmInstance.root.close(); |
| } |
| } |
| }); |
| |
| test('start by binding', () async { |
| RealmInstance? realmInstance; |
| try { |
| final builder = await RealmBuilder.create(); |
| |
| const echoClientName = 'v2Client'; |
| const echoClientBinder = 'echoClientBinder'; |
| const echoServerName = 'localEchoServer'; |
| |
| // This test leverages the `echo_client` binary, with an augmented |
| // component manifest that exposes `fuchsia.component.Binder`. |
| // |
| // The client is *NOT* started eagerly! |
| // |
| // The `echo_server` will automatically start if a client connects to |
| // its `Echo` service. This test validates that connecting to the |
| // _`echo_client_`'s `Binder` service will start the client, which will |
| // subsequently invoke the `echo_server`. Since the `echo_server` is a |
| // local client, the test can confirm that the echo request was made. |
| final v2Client = await builder.addChild( |
| echoClientName, |
| v2EchoClientWithBinderUrl, |
| ); |
| |
| final echoRequestReceived = Completer<String?>(); |
| |
| final localEchoServer = await builder.addLocalChild( |
| echoServerName, |
| onRun: (handles, onStop) async { |
| LocalEcho(handles, echoRequestReceived: echoRequestReceived); |
| |
| // Keep the component alive until the test is complete and the |
| // realm is closed. |
| await onStop.future; |
| }, |
| ); |
| |
| // Route logging to child |
| await builder.addRoute(Route() |
| ..capability(ProtocolCapability(flogger.LogSink.$serviceName)) |
| ..from(Ref.parent()) |
| ..to(Ref.child(v2Client)) |
| ..to(Ref.child(localEchoServer))); |
| |
| // Route the echo service |
| await builder.addRoute(Route() |
| ..capability(ProtocolCapability(fecho.Echo.$serviceName)) |
| ..from(Ref.child(localEchoServer)) |
| ..to(Ref.child(v2Client))); |
| |
| // Route the child's Binder service to parent, so the test can connect |
| // to it to start the child. |
| await builder.addRoute(Route() |
| ..capability(ProtocolCapability(fcomponent.Binder.$serviceName, |
| as: echoClientBinder)) |
| ..from(Ref.child(v2Client)) |
| ..to(Ref.parent())); |
| |
| // Start the realmInstance. The child component (the client) is not |
| // "eager", so it should not start automatically. |
| realmInstance = await builder.build(); |
| final scopedInstance = realmInstance.root; |
| |
| // Start the client by binding. |
| /*proxy=*/ scopedInstance.connectToProtocolAtPath( |
| fcomponent.BinderProxy(), |
| echoClientBinder, |
| ); |
| |
| final requestedEchoString = await echoRequestReceived.future; |
| |
| expect(requestedEchoString, v2EchoClientWithBinderArg); |
| } on Exception catch (err, stacktrace) { |
| checkCommonExceptions(err, stacktrace); |
| rethrow; |
| } finally { |
| if (realmInstance != null) { |
| realmInstance.root.close(); |
| } |
| } |
| }); |
| |
| test('replace config', () async { |
| RealmInstance? realmInstance; |
| try { |
| final builder = await RealmBuilder.create(); |
| |
| const echoServerName = 'localEchoServer'; |
| const echoClientName = 'v2EchoClient'; |
| |
| final v2EchoClientStructuredConfig = await builder.addChild( |
| echoClientName, |
| v2EchoClientStructuredConfigUrl, |
| ChildOptions()..eager(), |
| ); |
| |
| // NOTE: Important! This test updates the default configuration values |
| // in the successful calls to `replaceConfigValue...()` below (after the |
| // try/catch blocks). |
| // |
| // If this test was changed to run the EchoClient without first |
| // replacing some configurations, the EchoClient's default configuration |
| // values (as presently implemented) cause EchoClient to generate a |
| // string that exceeds the `echoString()` FIDL API's [MAX_STRING_LENGTH] |
| // (presently only 32, as declared in `echo.fidl`). This would cause the |
| // client to crash with a FIDL error, and the request would never reach |
| // the server. |
| |
| final echoRequestReceived = Completer<String?>(); |
| |
| final localEchoServer = await builder.addLocalChild( |
| echoServerName, |
| onRun: (handles, onStop) async { |
| LocalEcho(handles, echoRequestReceived: echoRequestReceived); |
| |
| // Keep the component alive until the test is complete |
| await onStop.future; |
| }, |
| ); |
| |
| // fail to replace a config field in a component that doesn't have a config schema |
| var caught = false; |
| try { |
| await builder.replaceConfigValueBool( |
| localEchoServer, 'echo_bool', false); |
| } on fidl.MethodException<fctest.RealmBuilderError> catch (err) { |
| expect(err.value, fctest.RealmBuilderError.noConfigSchema); |
| caught = true; |
| } finally { |
| expect(caught, true); |
| } |
| |
| // fail to replace a field that doesn't exist |
| caught = false; |
| try { |
| await builder.replaceConfigValueString( |
| v2EchoClientStructuredConfig, 'doesnt_exist', 'test'); |
| } on fidl.MethodException<fctest.RealmBuilderError> catch (err) { |
| expect(err.value, fctest.RealmBuilderError.noSuchConfigField); |
| caught = true; |
| } finally { |
| expect(caught, true); |
| } |
| |
| // fail to replace a field with the wrong type |
| caught = false; |
| try { |
| await builder.replaceConfigValueString( |
| v2EchoClientStructuredConfig, 'echo_bool', 'test'); |
| } on fidl.MethodException<fctest.RealmBuilderError> catch (err) { |
| expect(err.value, fctest.RealmBuilderError.configValueInvalid); |
| caught = true; |
| } finally { |
| expect(caught, true); |
| } |
| |
| // fail to replace a string that violates max_len |
| final longString = 'F' * 20; |
| caught = false; |
| try { |
| await builder.replaceConfigValueString( |
| v2EchoClientStructuredConfig, 'echo_string', longString); |
| } on fidl.MethodException<fctest.RealmBuilderError> catch (err) { |
| expect(err.value, fctest.RealmBuilderError.configValueInvalid); |
| caught = true; |
| } finally { |
| expect(caught, true); |
| } |
| |
| // fail to replace a vector whose string element violates max_len |
| caught = false; |
| try { |
| await builder.replaceConfigValueStringVector( |
| v2EchoClientStructuredConfig, 'echo_string_vector', [longString]); |
| } on fidl.MethodException<fctest.RealmBuilderError> catch (err) { |
| expect(err.value, fctest.RealmBuilderError.configValueInvalid); |
| caught = true; |
| } finally { |
| expect(caught, true); |
| } |
| |
| // fail to replace a vector that violates max_count |
| caught = false; |
| try { |
| await builder.replaceConfigValueStringVector( |
| v2EchoClientStructuredConfig, |
| 'echo_string_vector', |
| ['a', 'b', 'c', 'd'], |
| ); |
| } on fidl.MethodException<fctest.RealmBuilderError> catch (err) { |
| expect(err.value, fctest.RealmBuilderError.configValueInvalid); |
| caught = true; |
| } finally { |
| expect(caught, true); |
| } |
| |
| // succeed at replacing all fields with proper constraints |
| await builder.replaceConfigValueString( |
| v2EchoClientStructuredConfig, 'echo_string', 'Foobar!'); |
| await builder.replaceConfigValueStringVector( |
| v2EchoClientStructuredConfig, |
| 'echo_string_vector', |
| ['Hey', 'Folks']); |
| await builder.replaceConfigValueBool( |
| v2EchoClientStructuredConfig, 'echo_bool', true); |
| await builder.replaceConfigValueUint64( |
| v2EchoClientStructuredConfig, 'echo_num', 42); |
| |
| // Route logging to children |
| await builder.addRoute(Route() |
| ..capability(ProtocolCapability(flogger.LogSink.$serviceName)) |
| ..from(Ref.parent()) |
| ..to(Ref.child(localEchoServer)) |
| ..to(Ref.child(v2EchoClientStructuredConfig))); |
| |
| // Route the echo service from server to client |
| await builder.addRoute(Route() |
| ..capability(ProtocolCapability(fecho.Echo.$serviceName)) |
| ..from(Ref.child(localEchoServer)) |
| ..to(Ref.child(v2EchoClientStructuredConfig))); |
| |
| // The EchoClient at the referenced URL should be using this string: |
| const echoClientStructuredConfigRequest = |
| 'Foobar!, Hey, Folks, true, 42'; |
| |
| // Start the realm instance. |
| realmInstance = await builder.build(); |
| |
| final requestedEchoString = await echoRequestReceived.future; |
| |
| expect(requestedEchoString, echoClientStructuredConfigRequest); |
| } on Exception catch (err, stacktrace) { |
| checkCommonExceptions(err, stacktrace); |
| rethrow; |
| } finally { |
| if (realmInstance != null) { |
| realmInstance.root.close(); |
| } |
| } |
| }); |
| |
| group('local child tests', () { |
| test('local server', () async { |
| RealmInstance? realmInstance; |
| try { |
| final builder = await RealmBuilder.create(); |
| |
| const echoServerName = 'localEchoServer'; |
| |
| final localEchoServer = await builder.addLocalChild( |
| echoServerName, |
| onRun: (handles, onStop) async { |
| LocalEcho(handles); |
| // Keep the component alive until the test is complete |
| await onStop.future; |
| }, |
| ); |
| |
| await builder.addRoute(Route() |
| ..capability(ProtocolCapability(fecho.Echo.$serviceName)) |
| ..from(Ref.child(localEchoServer)) |
| ..to(Ref.parent())); |
| |
| realmInstance = await builder.build(); |
| |
| final echo = realmInstance.root |
| .connectToProtocolAtExposedDir(fecho.EchoProxy()); |
| const testString = 'ECHO...Echo...echo...(echo)...'; |
| |
| final reply = await echo.echoString(testString); |
| |
| expect(testString, reply); |
| } on Exception catch (err, stacktrace) { |
| checkCommonExceptions(err, stacktrace); |
| rethrow; |
| } finally { |
| if (realmInstance != null) { |
| realmInstance.root.close(); |
| } |
| } |
| }); |
| }); |
| }); |
| } |
| |
| class LocalEcho extends fecho.Echo { |
| final LocalComponentHandles handles; |
| |
| final Completer<String?>? echoRequestReceived; |
| |
| final echoBinding = fecho.EchoBinding(); |
| |
| LocalEcho(this.handles, {this.echoRequestReceived}) { |
| services.Outgoing() |
| ..serve( |
| fidl.InterfaceRequest<fio.Node>(handles.outgoingDir.passChannel()!)) |
| ..addPublicService( |
| (fidl.InterfaceRequest<fecho.Echo> connector) { |
| echoBinding.bind(this, connector); |
| }, |
| fecho.Echo.$serviceName, |
| ); |
| } |
| |
| @override |
| Future<String?> echoString(String? str) async { |
| if (echoRequestReceived != null) { |
| echoRequestReceived!.complete(str); |
| } |
| return str; |
| } |
| } |