// Copyright 2013 The Flutter 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 'package:args/command_runner.dart'; import 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:flutter_plugin_tools/src/common/core.dart'; import 'package:flutter_plugin_tools/src/update_excerpts_command.dart'; import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; import 'common/package_command_test.mocks.dart'; import 'mocks.dart'; import 'util.dart'; void main() { late FileSystem fileSystem; late Directory packagesDir; late RecordingProcessRunner processRunner; late CommandRunner runner; setUp(() { fileSystem = MemoryFileSystem(); packagesDir = createPackagesDirectory(fileSystem: fileSystem); final MockGitDir gitDir = MockGitDir(); when(gitDir.path).thenReturn(packagesDir.parent.path); processRunner = RecordingProcessRunner(); final UpdateExcerptsCommand command = UpdateExcerptsCommand( packagesDir, processRunner: processRunner, platform: MockPlatform(), gitDir: gitDir, ); runner = CommandRunner( 'update_excerpts_command', 'Test for update_excerpts_command'); runner.addCommand(command); }); test('runs pub get before running scripts', () async { final RepositoryPackage package = createFakePlugin('a_package', packagesDir, extraFiles: [kReadmeExcerptConfigPath]); final Directory example = getExampleDir(package); await runCapturingPrint(runner, ['update-excerpts']); expect( processRunner.recordedCalls, containsAll([ ProcessCall('dart', const ['pub', 'get'], example.path), ProcessCall( 'dart', const [ 'run', 'build_runner', 'build', '--config', 'excerpt', '--output', 'excerpts', '--delete-conflicting-outputs', ], example.path), ])); }); test('runs when config is present', () async { final RepositoryPackage package = createFakePlugin('a_package', packagesDir, extraFiles: [kReadmeExcerptConfigPath]); final Directory example = getExampleDir(package); final List output = await runCapturingPrint(runner, ['update-excerpts']); expect( processRunner.recordedCalls, containsAll([ ProcessCall( 'dart', const [ 'run', 'build_runner', 'build', '--config', 'excerpt', '--output', 'excerpts', '--delete-conflicting-outputs', ], example.path), ProcessCall( 'dart', const [ 'run', 'code_excerpt_updater', '--write-in-place', '--yaml', '--no-escape-ng-interpolation', '../README.md', ], example.path), ])); expect( output, containsAllInOrder([ contains('Ran for 1 package(s)'), ])); }); test('updates example readme when config is present', () async { final RepositoryPackage package = createFakePlugin('a_package', packagesDir, extraFiles: [kReadmeExcerptConfigPath]); final Directory example = getExampleDir(package); final List output = await runCapturingPrint(runner, ['update-excerpts']); expect( processRunner.recordedCalls, containsAll([ ProcessCall( 'dart', const [ 'run', 'build_runner', 'build', '--config', 'excerpt', '--output', 'excerpts', '--delete-conflicting-outputs', ], example.path), ProcessCall( 'dart', const [ 'run', 'code_excerpt_updater', '--write-in-place', '--yaml', '--no-escape-ng-interpolation', 'README.md', ], example.path), ])); expect( output, containsAllInOrder([ contains('Ran for 1 package(s)'), ])); }); test('skips when no config is present', () async { createFakePlugin('a_package', packagesDir); final List output = await runCapturingPrint(runner, ['update-excerpts']); expect(processRunner.recordedCalls, isEmpty); expect( output, containsAllInOrder([ contains('Skipped 1 package(s)'), ])); }); test('restores pubspec even if running the script fails', () async { final RepositoryPackage package = createFakePlugin('a_package', packagesDir, extraFiles: [kReadmeExcerptConfigPath]); processRunner.mockProcessesForExecutable['dart'] = [ FakeProcessInfo(MockProcess(exitCode: 1), ['pub', 'get']) ]; Error? commandError; final List output = await runCapturingPrint( runner, ['update-excerpts'], errorHandler: (Error e) { commandError = e; }); // Check that it's definitely a failure in a step between making the changes // and restoring the original. expect(commandError, isA()); expect( output, containsAllInOrder([ contains('The following packages had errors:'), contains('a_package:\n' ' Unable to get script dependencies') ])); final String examplePubspecContent = package.getExamples().first.pubspecFile.readAsStringSync(); expect(examplePubspecContent, isNot(contains('code_excerpter'))); expect(examplePubspecContent, isNot(contains('code_excerpt_updater'))); }); test('fails if pub get fails', () async { createFakePlugin('a_package', packagesDir, extraFiles: [kReadmeExcerptConfigPath]); processRunner.mockProcessesForExecutable['dart'] = [ FakeProcessInfo(MockProcess(exitCode: 1), ['pub', 'get']) ]; Error? commandError; final List output = await runCapturingPrint( runner, ['update-excerpts'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ contains('The following packages had errors:'), contains('a_package:\n' ' Unable to get script dependencies') ])); }); test('fails if extraction fails', () async { createFakePlugin('a_package', packagesDir, extraFiles: [kReadmeExcerptConfigPath]); processRunner.mockProcessesForExecutable['dart'] = [ FakeProcessInfo(MockProcess(), ['pub', 'get']), FakeProcessInfo(MockProcess(exitCode: 1), ['run', 'build_runner']) ]; Error? commandError; final List output = await runCapturingPrint( runner, ['update-excerpts'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ contains('The following packages had errors:'), contains('a_package:\n' ' Unable to extract excerpts') ])); }); test('fails if injection fails', () async { createFakePlugin('a_package', packagesDir, extraFiles: [kReadmeExcerptConfigPath]); processRunner.mockProcessesForExecutable['dart'] = [ FakeProcessInfo(MockProcess(), ['pub', 'get']), FakeProcessInfo(MockProcess(), ['run', 'build_runner']), FakeProcessInfo( MockProcess(exitCode: 1), ['run', 'code_excerpt_updater']), ]; Error? commandError; final List output = await runCapturingPrint( runner, ['update-excerpts'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ contains('The following packages had errors:'), contains('a_package:\n' ' Unable to inject excerpts') ])); }); test('fails if example injection fails', () async { createFakePlugin('a_package', packagesDir, extraFiles: [kReadmeExcerptConfigPath]); processRunner.mockProcessesForExecutable['dart'] = [ FakeProcessInfo(MockProcess(), ['pub', 'get']), FakeProcessInfo(MockProcess(), ['run', 'build_runner']), FakeProcessInfo(MockProcess(), ['run', 'code_excerpt_updater']), FakeProcessInfo( MockProcess(exitCode: 1), ['run', 'code_excerpt_updater']), ]; Error? commandError; final List output = await runCapturingPrint( runner, ['update-excerpts'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ contains('The following packages had errors:'), contains('a_package:\n' ' Unable to inject example excerpts') ])); }); test('fails if READMEs are changed with --fail-on-change', () async { createFakePlugin('a_plugin', packagesDir, extraFiles: [kReadmeExcerptConfigPath]); const String changedFilePath = 'packages/a_plugin/README.md'; processRunner.mockProcessesForExecutable['git'] = [ FakeProcessInfo(MockProcess(stdout: changedFilePath)), ]; Error? commandError; final List output = await runCapturingPrint( runner, ['update-excerpts', '--fail-on-change'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ contains('README.md is out of sync with its source excerpts'), contains('Snippets are out of sync in the following files: ' 'packages/a_plugin/README.md'), ])); }); test('passes if unrelated files are changed with --fail-on-change', () async { createFakePlugin('a_plugin', packagesDir, extraFiles: [kReadmeExcerptConfigPath]); const String changedFilePath = 'packages/a_plugin/linux/CMakeLists.txt'; processRunner.mockProcessesForExecutable['git'] = [ FakeProcessInfo(MockProcess(stdout: changedFilePath)), ]; final List output = await runCapturingPrint( runner, ['update-excerpts', '--fail-on-change']); expect( output, containsAllInOrder([ contains('Ran for 1 package(s)'), ])); }); test('fails if git ls-files fails', () async { createFakePlugin('a_plugin', packagesDir, extraFiles: [kReadmeExcerptConfigPath]); processRunner.mockProcessesForExecutable['git'] = [ FakeProcessInfo(MockProcess(exitCode: 1)) ]; Error? commandError; final List output = await runCapturingPrint( runner, ['update-excerpts', '--fail-on-change'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ contains('Unable to determine local file state'), ])); }); }