| // 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. |
| |
| // TODO(https://fxbug.dev/84961): Fix null safety and remove this language version. |
| // @dart=2.9 |
| |
| 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: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/src/tests/end_to_end/package_manger/README.md' |
| ' for details!'); |
| } |
| |
| // validRepoName replaces invalid characters in the input sequence to ensure |
| // the returned string complies to |
| // https://fuchsia.dev/fuchsia-src/concepts/packages/package_url?hl=en#repository |
| String validRepoName(String originalName) { |
| return originalName.replaceAll(RegExp(r'(?![a-z0-9-]).'), '-'); |
| } |
| |
| 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(); |
| |
| 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 { |
| sl4fDriver.close(); |
| |
| printErrorHelp(); |
| }); |
| |
| group('Package Manager', () { |
| String originalRewriteRuleJson; |
| Set<String> originalRepos; |
| PackageManagerRepo repoServer; |
| String testPackageName = 'package-manager-sample'; |
| String testRepoRewriteRule = |
| '{"version":"1","content":[{"host_match":"package-manager-test","host_replacement":"%%NAME%%","path_prefix_match":"/","path_prefix_replacement":"/"}]}'; |
| |
| setUp(() async { |
| repoServer = await PackageManagerRepo.initRepo(sl4fDriver, pmPath, log); |
| |
| // Gather the original package management settings before test begins. |
| originalRepos = await getCurrentRepos(sl4fDriver); |
| originalRewriteRuleJson = (await repoServer.pkgctlRuleDumpdynamic( |
| 'Save original rewrite rules from `pkgctl rule dump-dynamic`', 0)) |
| .stdout |
| .toString(); |
| }); |
| tearDown(() async { |
| if (!await resetPkgctl( |
| sl4fDriver, originalRepos, originalRewriteRuleJson)) { |
| log.severe('Failed to reset pkgctl to default state'); |
| } |
| if (repoServer != null) { |
| repoServer |
| ..kill() |
| ..cleanup(); |
| } |
| }); |
| test( |
| 'Test that creates a repository, registers it using pkgctl, and validates that the ' |
| 'package in the repository is visible.', () async { |
| // Covers these commands (success cases only): |
| // |
| // Newly covered: |
| // pkgctl get-hash fuchsia-pkg://<repo URL>/<package name> |
| // pkgctl rule dump-dynamic |
| // pkgctl repo add url <repo URL> -f 1 |
| // pkgctl repo rm fuchsia-pkg://<repo URL> |
| // 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; |
| |
| String repoName = 'pm-test-repo'; |
| String repoUrl = 'fuchsia-pkg://$repoName'; |
| |
| // Confirm our serve is serving what we expect. |
| 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); |
| |
| var gethashOutput = (await repoServer.pkgctlGethash( |
| 'Should error when checking for the package', |
| '$repoUrl/$testPackageName', |
| 1)) |
| .stdout |
| .toString(); |
| |
| // Record what the rule list is before we begin, and confirm that is |
| // the rule list when we are finished. |
| final originalRuleList = (await repoServer.pkgctlRuleDumpdynamic( |
| 'Recording the current rule list', 0)) |
| .stdout |
| .toString(); |
| |
| await repoServer.pkgctlRepoAddUrlNF( |
| 'Adding the new repository ${repoServer.getRepoPath()} as an update source with http://$hostAddress:$port/config.json', |
| 'http://$hostAddress:$port/config.json', |
| repoName, |
| '1', |
| 0); |
| |
| // Check that our new repo source is listed. |
| var listSrcsOutput = (await repoServer.pkgctlRepo( |
| 'Running pkgctl repo to list sources', 0)) |
| .stdout |
| .toString(); |
| |
| log.info('listSrcsOutput: $listSrcsOutput'); |
| expect(listSrcsOutput.contains(repoUrl), isTrue); |
| |
| gethashOutput = (await repoServer.pkgctlGethash( |
| 'Checking if the package now exists', |
| '$repoUrl/$testPackageName', |
| 0)) |
| .stdout |
| .toString(); |
| |
| log.info('gethashOutput: $gethashOutput'); |
| |
| expect( |
| gethashOutput |
| .contains('Error: Failed to get package hash with error:'), |
| isFalse); |
| |
| var ruleListOutput = (await repoServer.pkgctlRuleDumpdynamic( |
| 'Confirm rule list did not change.', 0)) |
| .stdout |
| .toString(); |
| expect(ruleListOutput, originalRuleList); |
| |
| await repoServer.pkgctlRepoRm('Delete $repoUrl', repoUrl, 0); |
| |
| // Check that our new repo source is gone. |
| listSrcsOutput = (await repoServer.pkgctlRepo( |
| 'Running pkgctl repo to list sources', 0)) |
| .stdout |
| .toString(); |
| |
| log.info('listSrcsOutput: $listSrcsOutput'); |
| expect(listSrcsOutput.contains(repoUrl), isFalse); |
| |
| ruleListOutput = (await repoServer.pkgctlRuleDumpdynamic( |
| 'Confirm rule list did not change.', 0)) |
| .stdout |
| .toString(); |
| expect(ruleListOutput, 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: |
| // pkgctl repo add url http://<host>:<port>/config.json -n testhost -f 1 |
| 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.pkgctlRepoAddUrlNF( |
| 'Adding the new repository as an update source with http://$hostAddress:$port', |
| 'http://$hostAddress:$port/config.json', |
| 'testhost', |
| '1', |
| 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 pkgctl default name behavior when no name is given.', () async { |
| // Covers these commands (success cases only): |
| // |
| // Newly covered: |
| // pkgctl repo add url http://<host>:<port>/config.json -f 1 |
| // |
| // 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.pkgctlRepoAddUrlF( |
| 'Adding the new repository as an update source with http://$hostAddress:$port', |
| 'http://$hostAddress:$port/config.json', |
| '1', |
| 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'; |
| // Ensure the repo name complies to |
| // https://fuchsia.dev/fuchsia-src/concepts/packages/package_url?hl=en#repository |
| repoName = validRepoName(repoName); |
| |
| 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 `pkgctl resolve` base case.', () async { |
| // Covers these commands (success cases only): |
| // |
| // Newly covered: |
| // pkgctl resolve fuchsia-pkg://package-manager-test/<name> |
| var resolveProcessResult = await repoServer.pkgctlResolve( |
| 'Confirm that `$testPackageName` does not exist.', |
| 'fuchsia-pkg://package-manager-test/$testPackageName', |
| 1); |
| expect(resolveProcessResult.exitCode, isNonZero); |
| expect( |
| resolveProcessResult.stdout.toString(), |
| equals( |
| 'resolving fuchsia-pkg://package-manager-test/package-manager-sample\n')); |
| |
| await repoServer.setupServe('$testPackageName-0.far', manifestPath, []); |
| final optionalPort = repoServer.getServePort(); |
| expect(optionalPort.isPresent, isTrue); |
| final port = optionalPort.value; |
| |
| // The repo path usually contains disallowed characters like '/' and |
| // uppercase characters. pkgctl will return an error in this case. |
| // Ensure we have a repoNameFixed that is known to comply with |
| // https://fuchsia.dev/fuchsia-src/concepts/packages/package_url?hl=en#repository |
| var repoNameFixed = validRepoName(repoServer.getRepoPath()); |
| |
| await repoServer.pkgctlRepoAddUrlNF( |
| 'Adding the new repository with http://$hostAddress:$port', |
| 'http://$hostAddress:$port/config.json', |
| repoNameFixed, |
| '1', |
| 0); |
| |
| var localRewriteRule = testRepoRewriteRule; |
| localRewriteRule = |
| localRewriteRule.replaceAll('%%NAME%%', '$repoNameFixed'); |
| await repoServer.pkgctlRuleReplace( |
| 'Setting rewriting rule for new repository', localRewriteRule, 0); |
| |
| resolveProcessResult = await repoServer.pkgctlResolve( |
| 'Confirm that `$testPackageName` now exists.', |
| 'fuchsia-pkg://package-manager-test/$testPackageName', |
| 0); |
| expect(resolveProcessResult.exitCode, isZero); |
| expect( |
| resolveProcessResult.stdout.toString(), |
| equals( |
| 'resolving fuchsia-pkg://package-manager-test/package-manager-sample\n')); |
| |
| await repoServer.pkgctlRuleReplace( |
| 'Restoring rewriting rule to original state', |
| originalRewriteRuleJson, |
| 0); |
| }); |
| test('Test `pkgctl resolve --verbose` base case.', () async { |
| // Covers these commands (success cases only): |
| // |
| // Newly covered: |
| // pkgctl resolve --verbose fuchsia-pkg://package-manager-test/<name> |
| var resolveVProcessResult = await repoServer.pkgctlResolveV( |
| 'Confirm that `$testPackageName` does not exist.', |
| 'fuchsia-pkg://package-manager-test/$testPackageName', |
| 1); |
| expect(resolveVProcessResult.exitCode, isNonZero); |
| expect(resolveVProcessResult.stdout.toString(), |
| isNot(contains('package contents:\n'))); |
| |
| await repoServer.setupServe('$testPackageName-0.far', manifestPath, []); |
| final optionalPort = repoServer.getServePort(); |
| expect(optionalPort.isPresent, isTrue); |
| final port = optionalPort.value; |
| |
| // The repo path usually contains disallowed characters like '/' and |
| // uppercase characters. pkgctl will return an error in this case. |
| // Ensure we have a repoNameFixed that is known to comply with |
| // https://fuchsia.dev/fuchsia-src/concepts/packages/package_url?hl=en#repository |
| var repoNameFixed = validRepoName(repoServer.getRepoPath()); |
| |
| await repoServer.pkgctlRepoAddUrlNF( |
| 'Adding the new repository with http://$hostAddress:$port', |
| 'http://$hostAddress:$port/config.json', |
| repoNameFixed, |
| '1', |
| 0); |
| |
| var localRewriteRule = testRepoRewriteRule; |
| localRewriteRule = |
| localRewriteRule.replaceAll('%%NAME%%', '$repoNameFixed'); |
| await repoServer.pkgctlRuleReplace( |
| 'Setting rewriting rule for new repository', localRewriteRule, 0); |
| |
| resolveVProcessResult = await repoServer.pkgctlResolveV( |
| 'Confirm that `$testPackageName` now exists.', |
| 'fuchsia-pkg://package-manager-test/$testPackageName', |
| 0); |
| expect(resolveVProcessResult.exitCode, isZero); |
| expect(resolveVProcessResult.stdout.toString(), |
| contains('package contents:\n')); |
| |
| await repoServer.pkgctlRuleReplace( |
| 'Restoring rewriting rule to original state', |
| originalRewriteRuleJson, |
| 0); |
| }); |
| test( |
| 'Test the flow from repo creation, to archive generation, ' |
| 'to using pkgctl and 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 resolveExitCode = (await repoServer.pkgctlResolve( |
| 'Confirm that `$testPackageName` does not exist.', |
| 'fuchsia-pkg://package-manager-test/$testPackageName', |
| 1)) |
| .exitCode; |
| expect(resolveExitCode, isNonZero); |
| |
| await repoServer.setupServe('$testPackageName-0.far', manifestPath, []); |
| final optionalPort = repoServer.getServePort(); |
| expect(optionalPort.isPresent, isTrue); |
| final port = optionalPort.value; |
| |
| var repoNameFixed = validRepoName(repoServer.getRepoPath()); |
| |
| await repoServer.pkgctlRepoAddUrlNF( |
| 'Adding the new repository with http://$hostAddress:$port', |
| 'http://$hostAddress:$port/config.json', |
| repoNameFixed, |
| '1', |
| 0); |
| |
| var localRewriteRule = testRepoRewriteRule; |
| localRewriteRule = |
| localRewriteRule.replaceAll('%%NAME%%', '$repoNameFixed'); |
| await repoServer.pkgctlRuleReplace( |
| 'Setting rewriting rule for new repository', localRewriteRule, 0); |
| |
| var response = await sl4fDriver.ssh.run( |
| 'run-test-suite fuchsia-pkg://package-manager-test/$testPackageName#meta/package-manager-sample.cm'); |
| expect(response.exitCode, 0); |
| expect(response.stdout.toString().contains('Hello, World!\n'), true); |
| response = await sl4fDriver.ssh.run( |
| 'run-test-suite fuchsia-pkg://package-manager-test/$testPackageName#meta/package-manager-sample2.cm'); |
| expect(response.exitCode, 0); |
| expect(response.stdout.toString().contains('Hello, World2!\n'), true); |
| |
| await repoServer.pkgctlRuleReplace( |
| 'Restoring rewriting rule to original state', |
| originalRewriteRuleJson, |
| 0); |
| }); |
| test( |
| 'Test the flow from repo creation, to archive generation, ' |
| 'to using pkgctl and running the component on the device.', () async { |
| // Covers several key steps: |
| // 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. |
| await repoServer.setupServe('$testPackageName-0.far', manifestPath, []); |
| final optionalPort = repoServer.getServePort(); |
| expect(optionalPort.isPresent, isTrue); |
| final port = optionalPort.value; |
| |
| String repoName = 'pm-test-repo'; |
| String repoUrl = 'fuchsia-pkg://$repoName'; |
| |
| await repoServer.pkgctlRepoAddUrlNF( |
| 'Adding the new repository as an update source with http://$hostAddress:$port', |
| 'http://$hostAddress:$port/config.json', |
| repoName, |
| '1', |
| 0); |
| |
| var localRewriteRule = testRepoRewriteRule; |
| localRewriteRule = localRewriteRule.replaceAll('%%NAME%%', '$repoName'); |
| await repoServer.pkgctlRuleReplace( |
| 'Setting rewriting rule for new repository', localRewriteRule, 0); |
| |
| var response = await sl4fDriver.ssh.run( |
| 'run-test-suite $repoUrl/$testPackageName#meta/package-manager-sample.cm'); |
| expect(response.exitCode, 0); |
| expect(response.stdout.toString().contains('Hello, World!\n'), true); |
| response = await sl4fDriver.ssh.run( |
| 'run-test-suite $repoUrl/$testPackageName#meta/package-manager-sample2.cm'); |
| expect(response.exitCode, 0); |
| expect(response.stdout.toString().contains('Hello, World2!\n'), true); |
| |
| await repoServer.pkgctlRuleReplace( |
| 'Restoring rewriting rule to original state', |
| originalRewriteRuleJson, |
| 0); |
| }); |
| }, timeout: _timeout); |
| } |