| // Copyright 2020 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:convert'; |
| import 'dart:io'; |
| |
| import 'package:archive/archive.dart'; |
| import 'package:archive/archive_io.dart'; |
| import 'package:logging/logging.dart'; |
| import 'package:path/path.dart' as path; |
| import 'package:pkg/pkg.dart'; |
| import 'package:pm/pm.dart'; |
| import 'package:quiver/core.dart' show Optional; |
| import 'package:retry/retry.dart'; |
| import 'package:sl4f/sl4f.dart' as sl4f; |
| import 'package:test/test.dart'; |
| |
| // TODO(fxbug.dev/53292): update to use test size. |
| const _timeout = Timeout(Duration(minutes: 5)); |
| |
| void printErrorHelp() { |
| print('If this test fails, see ' |
| 'https://fuchsia.googlesource.com/a/fuchsia/+/HEAD/sdk/cts/tools/package_manager/README.md' |
| ' for details!'); |
| } |
| |
| Future<String> formattedHostAddress(sl4f.Sl4f sl4fDriver) async { |
| final output = await sl4fDriver.ssh.run('echo \$SSH_CONNECTION'); |
| final hostAddress = |
| output.stdout.toString().split(' ')[0].replaceAll('%', '%25'); |
| return '[$hostAddress]'; |
| } |
| |
| void main() { |
| final log = Logger('package_manager_test'); |
| final runtimeDepsPath = Platform.script.resolve('runtime_deps').toFilePath(); |
| final pmPath = Platform.script.resolve('runtime_deps/pm').toFilePath(); |
| String hostAddress; |
| String manifestPath; |
| |
| sl4f.Sl4f sl4fDriver; |
| |
| setUpAll(() async { |
| Logger.root |
| ..level = Level.ALL |
| ..onRecord.listen((rec) => print('[${rec.level}]: ${rec.message}')); |
| sl4fDriver = sl4f.Sl4f.fromEnvironment(); |
| await sl4fDriver.startServer(); |
| |
| hostAddress = await formattedHostAddress(sl4fDriver); |
| |
| // Extract the `package.tar`. |
| final packageTarPath = |
| Platform.script.resolve('runtime_deps/package.tar').toFilePath(); |
| final bytes = File(packageTarPath).readAsBytesSync(); |
| final packageTar = TarDecoder().decodeBytes(bytes); |
| for (final file in packageTar) { |
| final filename = file.name; |
| if (file.isFile) { |
| List<int> data = file.content; |
| File(runtimeDepsPath + Platform.pathSeparator + filename) |
| ..createSync(recursive: true) |
| ..writeAsBytesSync(data); |
| } |
| } |
| |
| // The `package.manifest` file comes from the tar extracted above. |
| manifestPath = |
| Platform.script.resolve('runtime_deps/package.manifest').toFilePath(); |
| }); |
| |
| tearDownAll(() async { |
| await sl4fDriver.stopServer(); |
| sl4fDriver.close(); |
| |
| printErrorHelp(); |
| }); |
| group('Package Manager', () { |
| Optional<String> originalRewriteRule; |
| Set<String> originalRepos; |
| PackageManagerRepo repoServer; |
| String testPackageName = 'cts-package-manager-sample-component'; |
| |
| setUp(() async { |
| repoServer = await PackageManagerRepo.initRepo(sl4fDriver, pmPath, log); |
| |
| // Gather the original package management settings before test begins. |
| originalRepos = await getCurrentRepos(sl4fDriver); |
| var ruleListResponse = await sl4fDriver.ssh.run('pkgctl rule list'); |
| expect(ruleListResponse.exitCode, 0); |
| originalRewriteRule = |
| getCurrentRewriteRule(ruleListResponse.stdout.toString()); |
| }); |
| tearDown(() async { |
| if (!await resetPkgctl(sl4fDriver, originalRepos, originalRewriteRule)) { |
| log.severe('Failed to reset pkgctl to default state'); |
| } |
| if (repoServer != null) { |
| repoServer |
| ..kill() |
| ..cleanup(); |
| } |
| }); |
| test( |
| 'Test that creates a repository, deploys a package, and ' |
| 'validates that the deployed package is visible from the server', |
| () async { |
| // Covers these commands (success cases only): |
| // |
| // Newly covered: |
| // amberctl add_src -n <path> -f http://<host>:<port>/config.json |
| // amberctl enable_src -n devhost |
| // amberctl rm_src -n <name> |
| // pkgctl repo |
| // pm serve -repo=<path> -l :<port> |
| await repoServer.setupServe('$testPackageName-0.far', manifestPath, []); |
| final optionalPort = repoServer.getServePort(); |
| expect(optionalPort.isPresent, isTrue); |
| final port = optionalPort.value; |
| |
| log.info('Getting the available packages'); |
| final curlResponse = await Process.run( |
| 'curl', ['http://localhost:$port/targets.json', '-i']); |
| |
| log.info('curl response: ${curlResponse.stdout.toString()}'); |
| expect(curlResponse.exitCode, 0); |
| final curlOutput = curlResponse.stdout.toString(); |
| expect(curlOutput.contains('$testPackageName/0'), isTrue); |
| |
| // Typically, there is a pre-existing rule pointing to `devhost`, but it isn't |
| // guaranteed. Record what the rule list is before we begin, and confirm that is |
| // the rule list when we are finished. |
| final originalRuleList = (await repoServer.pkgctlRuleList( |
| 'Recording the current rule list', 0)) |
| .stdout |
| .toString(); |
| |
| await repoServer.amberctlAddSrcNF( |
| 'Adding the new repository ${repoServer.getRepoPath()} as an update source with http://$hostAddress:$port/config.json', |
| repoServer.getRepoPath(), |
| 'http://$hostAddress:$port/config.json', |
| 0); |
| |
| // Check that our new repo source is listed. |
| var listSrcsOutput = (await repoServer.pkgctlRepo( |
| 'Running pkgctl repo to list sources', 0)) |
| .stdout |
| .toString(); |
| String repoName = repoServer.getRepoPath().replaceAll('/', '_'); |
| String repoUrl = 'fuchsia-pkg://$repoName'; |
| expect(listSrcsOutput.contains(repoUrl), isTrue); |
| |
| var ruleListOutput = (await repoServer.pkgctlRuleList( |
| 'Confirm rule list points to $repoName', 0)) |
| .stdout |
| .toString(); |
| expect( |
| hasExclusivelyOneItem(ruleListOutput, 'host_replacement', repoName), |
| isTrue); |
| |
| await repoServer.amberctlRmsrcN('Cleaning up temp repo', repoName, 0); |
| |
| log.info('Checking that $repoUrl is gone'); |
| listSrcsOutput = (await repoServer.pkgctlRepo( |
| 'Running pkgctl repo to list sources', 0)) |
| .stdout |
| .toString(); |
| expect(listSrcsOutput.contains(repoUrl), isFalse); |
| |
| if (originalRewriteRule.isPresent) { |
| await repoServer.amberctlEnablesrcN( |
| 'Re-enabling original repo source', originalRewriteRule.value, 0); |
| } |
| |
| listSrcsOutput = (await repoServer.pkgctlRuleList( |
| 'Confirm rule list is back to its original set.', 0)) |
| .stdout |
| .toString(); |
| expect(listSrcsOutput, originalRuleList); |
| |
| log.info( |
| 'Killing serve process and ensuring the output contains `[pm serve]`.'); |
| final killStatus = repoServer.kill(); |
| expect(killStatus, isTrue); |
| |
| var serveOutputBuilder = StringBuffer(); |
| var serveProcess = repoServer.getServeProcess(); |
| expect(serveProcess.isPresent, isTrue); |
| await repoServer |
| .getServeStdoutSplitStream() |
| .transform(utf8.decoder) |
| .listen((data) { |
| serveOutputBuilder.write(data); |
| }).asFuture(); |
| final serveOutput = serveOutputBuilder.toString(); |
| // Ensuring that `[pm serve]` appears in the output because the `-q` flag |
| // wasn't used in the serve command. |
| expect(serveOutput.contains('[pm serve]'), isTrue); |
| }); |
| test( |
| 'Test that creates a repository, deploys a package, and ' |
| 'validates that the deployed package is visible from the server. ' |
| 'Then make sure `pm serve` output is quiet.', () async { |
| // Covers these commands (success cases only): |
| // |
| // Newly covered: |
| // pm serve -repo=<path> -l :<port> -q |
| // |
| // Previously covered: |
| // amberctl add_src -n <path> -f http://<host>:<port>/config.json |
| await repoServer |
| .setupServe('$testPackageName-0.far', manifestPath, ['-q']); |
| final optionalPort = repoServer.getServePort(); |
| expect(optionalPort.isPresent, isTrue); |
| final port = optionalPort.value; |
| |
| log.info('Getting the available packages'); |
| final curlResponse = await Process.run( |
| 'curl', ['http://localhost:$port/targets.json', '-i']); |
| |
| log.info('curl response: ${curlResponse.stdout.toString()}'); |
| expect(curlResponse.exitCode, 0); |
| final curlOutput = curlResponse.stdout.toString(); |
| expect(curlOutput.contains('$testPackageName/0'), isTrue); |
| |
| await repoServer.amberctlAddSrcNF( |
| 'Adding the new repository as an update source with http://$hostAddress:$port', |
| repoServer.getRepoPath(), |
| 'http://$hostAddress:$port/config.json', |
| 0); |
| |
| log.info( |
| 'Killing serve process and ensuring the output does not contain `[pm serve]`.'); |
| final killStatus = repoServer.kill(); |
| expect(killStatus, isTrue); |
| |
| var serveOutputBuilder = StringBuffer(); |
| var serveProcess = repoServer.getServeProcess(); |
| expect(serveProcess.isPresent, isTrue); |
| await repoServer |
| .getServeStdoutSplitStream() |
| .transform(utf8.decoder) |
| .listen((data) { |
| serveOutputBuilder.write(data); |
| }).asFuture(); |
| final serveOutput = serveOutputBuilder.toString(); |
| // The `-q` flag was given to `pm serve`, so there should be no serve output. |
| expect(serveOutput.contains('[pm serve]'), isFalse); |
| }); |
| test('Test amberctl default name behavior when no name is given.', |
| () async { |
| // Covers these commands (success cases only): |
| // |
| // Newly covered: |
| // amberctl add_src -f http://<host>:<port>/config.json |
| // |
| // Previously covered: |
| // pm serve -repo=<path> -l :<port> |
| // pkgctl repo |
| await repoServer.setupServe('$testPackageName-0.far', manifestPath, []); |
| final optionalPort = repoServer.getServePort(); |
| expect(optionalPort.isPresent, isTrue); |
| final port = optionalPort.value; |
| |
| await repoServer.amberctlAddSrcF( |
| 'Adding the new repository as an update source with http://$hostAddress:$port', |
| 'http://$hostAddress:$port/config.json', |
| 0); |
| |
| var listSrcsOutput = (await repoServer.pkgctlRepo( |
| 'Running pkgctl repo to list sources', 0)) |
| .stdout |
| .toString(); |
| |
| log.info('Running pkgctl repo to list sources'); |
| String repoName = 'http://$hostAddress:$port'; |
| // Remove the `%25ethp0003` from |
| // http://[fe80::9813:f1ff:fe9b:c411%25ethp0003]:38729 |
| // (for example) |
| RegExp(r'(%\w+)]').allMatches(repoName).forEach((match) { |
| String str = match.group(1); |
| repoName = repoName.replaceAll(str, ''); |
| }); |
| // Clean up name to match what amberctl will do. |
| repoName = repoName |
| .replaceAll('/', '_') |
| .replaceAll(':', '_') |
| .replaceAll('[', '_') |
| .replaceAll(']', '_'); |
| |
| log.info('Checking repo name is $repoName'); |
| expect(listSrcsOutput.contains(repoName), isTrue); |
| }); |
| test('Test `pm serve` writes its port number to a given file path.', |
| () async { |
| // Covers these commands (success cases only): |
| // |
| // Newly covered: |
| // pm serve -repo=<path> -l :<port> -f <path to export port number> |
| await repoServer.setupRepo('$testPackageName-0.far', manifestPath); |
| final portFilePath = path.join(repoServer.getRepoPath(), 'port_file.txt'); |
| |
| await repoServer.pmServeRepoLExtra(['-f', '$portFilePath']); |
| final optionalPort = repoServer.getServePort(); |
| expect(optionalPort.isPresent, isTrue); |
| final port = optionalPort.value; |
| |
| log.info('Checking that $portFilePath was generated with content: $port'); |
| final retryOptions = RetryOptions(maxAttempts: 5); |
| String portString = |
| await retryOptions.retry(File(portFilePath).readAsStringSync); |
| expect(portString, isNotNull); |
| expect(int.parse(portString), port); |
| }); |
| test( |
| 'Test `pm serve` chooses its own port number and writes it to a given file path.', |
| () async { |
| // Covers these commands (success cases only): |
| // |
| // Newly covered: |
| // pm serve -repo=<path> -l :0 -f <path to export port number> |
| await repoServer.setupRepo('$testPackageName-0.far', manifestPath); |
| |
| await repoServer.pmServeRepoLFExtra([]); |
| final optionalPort = repoServer.getServePort(); |
| expect(optionalPort.isPresent, isTrue); |
| |
| log.info('Checking port ${optionalPort.value} is valid.'); |
| final curlResponse = await Process.run('curl', |
| ['http://localhost:${optionalPort.value}/targets.json', '-i']); |
| |
| log.info('curl response: ${curlResponse.stdout.toString()}'); |
| expect(curlResponse.exitCode, 0); |
| final curlOutput = curlResponse.stdout.toString(); |
| expect(curlOutput.contains('$testPackageName/0'), isTrue); |
| }); |
| test( |
| 'Test `amberctl add_repo_cfg` does not set a rewrite rule to use the ' |
| 'added repo.', () async { |
| // Covers these commands (success cases only): |
| // |
| // Newly covered: |
| // amberctl add_repo_cfg -n <path> -f http://<host>:<port>/config.json |
| // |
| // Previously covered: |
| // pkgctl repo |
| // pm serve -repo=<path> -l :<port> |
| await repoServer.setupServe('$testPackageName-0.far', manifestPath, []); |
| final optionalPort = repoServer.getServePort(); |
| expect(optionalPort.isPresent, isTrue); |
| final port = optionalPort.value; |
| |
| final originalRuleList = (await repoServer.pkgctlRuleList( |
| 'Recording the current rule list', 0)) |
| .stdout |
| .toString(); |
| |
| await repoServer.amberctlAddrepocfgNF( |
| 'Adding the new repository as an update source with http://$hostAddress:$port', |
| 'http://$hostAddress:$port/config.json', |
| 0); |
| String repoName = repoServer.getRepoPath().replaceAll('/', '_'); |
| String repoUrl = 'fuchsia-pkg://$repoName'; |
| |
| var listSrcsOutput = (await repoServer.pkgctlRepo( |
| 'Running pkgctl repo to list sources', 0)) |
| .stdout |
| .toString(); |
| log.info('list sources: $listSrcsOutput, expect: $repoUrl'); |
| expect(listSrcsOutput.contains(repoUrl), isTrue); |
| |
| var listRulesOutput = (await repoServer.pkgctlRuleList( |
| 'Confirm rule list is NOT updated to point to $repoName', 0)) |
| .stdout |
| .toString(); |
| expect(listRulesOutput.contains(repoName), isFalse); |
| expect(listRulesOutput, originalRuleList); |
| }); |
| test('Test `pkgctl resolve` base case.', () async { |
| // Covers these commands (success cases only): |
| // |
| // Newly covered: |
| // pkgctl resolve fuchsia-pkg://fuchsia.com/<name> |
| var resolveOutput = (await repoServer.pkgctlResolve( |
| 'Confirm that `$testPackageName` does not exist.', |
| 'fuchsia-pkg://fuchsia.com/$testPackageName', |
| 1)) |
| .stdout |
| .toString(); |
| expect(resolveOutput.contains('package contents:'), isFalse); |
| |
| await repoServer.setupServe('$testPackageName-0.far', manifestPath, []); |
| final optionalPort = repoServer.getServePort(); |
| expect(optionalPort.isPresent, isTrue); |
| final port = optionalPort.value; |
| |
| await repoServer.amberctlAddSrcNF( |
| 'Adding the new repository as an update source with http://$hostAddress:$port', |
| repoServer.getRepoPath(), |
| 'http://$hostAddress:$port/config.json', |
| 0); |
| |
| resolveOutput = (await repoServer.pkgctlResolve( |
| 'Confirm that `$testPackageName` now exists.', |
| 'fuchsia-pkg://fuchsia.com/$testPackageName', |
| 0)) |
| .stdout |
| .toString(); |
| expect(resolveOutput.contains('package contents:'), isTrue); |
| }); |
| test( |
| 'Test the flow from repo creation, to archive generation, ' |
| 'to running the component on the device.', () async { |
| // Covers several key steps: |
| // 0. Sanity check. The given component is not already available in the repo. |
| // 1. The given component is archived into a valid `.far`. |
| // 2. We are able to create our own repo. |
| // 3. We are able to serve our repo to a given Fuchsia device. |
| // 4. The device is able to pull the given component from our repo. |
| // 5. The given component contains the expected content. |
| var resolveOutput = (await repoServer.pkgctlResolve( |
| 'Confirm that `$testPackageName` does not exist.', |
| 'fuchsia-pkg://fuchsia.com/$testPackageName', |
| 1)) |
| .stdout |
| .toString(); |
| log.info('resolve before: $resolveOutput'); |
| expect(resolveOutput.contains('package contents:'), isFalse); |
| |
| await repoServer.setupServe('$testPackageName-0.far', manifestPath, []); |
| final optionalPort = repoServer.getServePort(); |
| expect(optionalPort.isPresent, isTrue); |
| final port = optionalPort.value; |
| |
| await repoServer.amberctlAddSrcNF( |
| 'Adding the new repository as an update source with http://$hostAddress:$port', |
| repoServer.getRepoPath(), |
| 'http://$hostAddress:$port/config.json', |
| 0); |
| |
| var response = await sl4fDriver.ssh.run( |
| 'run fuchsia-pkg://fuchsia.com/$testPackageName#meta/cts-package-manager-sample.cmx'); |
| expect(response.exitCode, 0); |
| expect(response.stdout.toString(), 'Hello, World!\n'); |
| response = await sl4fDriver.ssh.run( |
| 'run fuchsia-pkg://fuchsia.com/$testPackageName#meta/cts-package-manager-sample2.cmx'); |
| expect(response.exitCode, 0); |
| expect(response.stdout.toString(), 'Hello, World2!\n'); |
| }); |
| }, timeout: _timeout); |
| } |