|  | // 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:flutter_plugin_tools/src/common/core.dart'; | 
|  | import 'package:flutter_plugin_tools/src/common/plugin_utils.dart'; | 
|  | import 'package:flutter_plugin_tools/src/podspec_check_command.dart'; | 
|  | import 'package:git/git.dart'; | 
|  | import 'package:platform/platform.dart'; | 
|  | import 'package:test/test.dart'; | 
|  |  | 
|  | import 'mocks.dart'; | 
|  | import 'util.dart'; | 
|  |  | 
|  | /// Adds a fake podspec to [plugin]'s [platform] directory. | 
|  | /// | 
|  | /// If [includeSwiftWorkaround] is set, the xcconfig additions to make Swift | 
|  | /// libraries work in apps that have no Swift will be included. If | 
|  | /// [scopeSwiftWorkaround] is set, it will be specific to the iOS configuration. | 
|  | void _writeFakePodspec( | 
|  | RepositoryPackage plugin, | 
|  | String platform, { | 
|  | bool includeSwiftWorkaround = false, | 
|  | bool scopeSwiftWorkaround = false, | 
|  | bool includePrivacyManifest = false, | 
|  | }) { | 
|  | final String pluginName = plugin.directory.basename; | 
|  | final File file = plugin.directory | 
|  | .childDirectory(platform) | 
|  | .childFile('$pluginName.podspec'); | 
|  | final String swiftWorkaround = includeSwiftWorkaround | 
|  | ? ''' | 
|  | s.${scopeSwiftWorkaround ? 'ios.' : ''}xcconfig = { | 
|  | 'LIBRARY_SEARCH_PATHS' => '\$(TOOLCHAIN_DIR)/usr/lib/swift/\$(PLATFORM_NAME)/ \$(SDKROOT)/usr/lib/swift', | 
|  | 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', | 
|  | } | 
|  | ''' | 
|  | : ''; | 
|  | final String privacyManifest = includePrivacyManifest | 
|  | ? ''' | 
|  | s.resource_bundles = {'$pluginName' => ['Resources/PrivacyInfo.xcprivacy']} | 
|  | ''' | 
|  | : ''; | 
|  | file.createSync(recursive: true); | 
|  | file.writeAsStringSync(''' | 
|  | # | 
|  | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html | 
|  | # | 
|  | Pod::Spec.new do |s| | 
|  | s.name             = 'shared_preferences_foundation' | 
|  | s.version          = '0.0.1' | 
|  | s.summary          = 'iOS and macOS implementation of the shared_preferences plugin.' | 
|  | s.description      = <<-DESC | 
|  | Wraps NSUserDefaults, providing a persistent store for simple key-value pairs. | 
|  | DESC | 
|  | s.homepage         = 'https://github.com/flutter/packages/tree/main/packages/shared_preferences/shared_preferences_foundation' | 
|  | s.license          = { :type => 'BSD', :file => '../LICENSE' } | 
|  | s.author           = { 'Flutter Team' => 'flutter-dev@googlegroups.com' } | 
|  | s.source           = { :http => 'https://github.com/flutter/packages/tree/main/packages/shared_preferences/shared_preferences_foundation' } | 
|  | s.source_files = 'Classes/**/*' | 
|  | s.ios.dependency 'Flutter' | 
|  | s.osx.dependency 'FlutterMacOS' | 
|  | s.ios.deployment_target = '9.0' | 
|  | s.osx.deployment_target = '10.11' | 
|  | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } | 
|  | $swiftWorkaround | 
|  | s.swift_version = '5.0' | 
|  | $privacyManifest | 
|  |  | 
|  | end | 
|  | '''); | 
|  | } | 
|  |  | 
|  | void main() { | 
|  | group('PodspecCheckCommand', () { | 
|  | late Directory packagesDir; | 
|  | late CommandRunner<void> runner; | 
|  | late MockPlatform mockPlatform; | 
|  | late RecordingProcessRunner processRunner; | 
|  |  | 
|  | setUp(() { | 
|  | mockPlatform = MockPlatform(isMacOS: true); | 
|  | final GitDir gitDir; | 
|  | (:packagesDir, :processRunner, gitProcessRunner: _, :gitDir) = | 
|  | configureBaseCommandMocks(platform: mockPlatform); | 
|  | final PodspecCheckCommand command = PodspecCheckCommand( | 
|  | packagesDir, | 
|  | processRunner: processRunner, | 
|  | platform: mockPlatform, | 
|  | gitDir: gitDir, | 
|  | ); | 
|  |  | 
|  | runner = | 
|  | CommandRunner<void>('podspec_test', 'Test for $PodspecCheckCommand'); | 
|  | runner.addCommand(command); | 
|  | }); | 
|  |  | 
|  | test('only runs on macOS', () async { | 
|  | createFakePlugin('plugin1', packagesDir, | 
|  | extraFiles: <String>['plugin1.podspec']); | 
|  | mockPlatform.isMacOS = false; | 
|  |  | 
|  | Error? commandError; | 
|  | final List<String> output = await runCapturingPrint( | 
|  | runner, <String>['podspec-check'], errorHandler: (Error e) { | 
|  | commandError = e; | 
|  | }); | 
|  |  | 
|  | expect(commandError, isA<ToolExit>()); | 
|  |  | 
|  | expect( | 
|  | processRunner.recordedCalls, | 
|  | equals(<ProcessCall>[]), | 
|  | ); | 
|  |  | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder( | 
|  | <Matcher>[contains('only supported on macOS')], | 
|  | )); | 
|  | }); | 
|  |  | 
|  | test('runs pod lib lint on a podspec', () async { | 
|  | final RepositoryPackage plugin = createFakePlugin( | 
|  | 'plugin1', | 
|  | packagesDir, | 
|  | extraFiles: <String>[ | 
|  | 'bogus.dart', // Ignore non-podspecs. | 
|  | ], | 
|  | ); | 
|  | _writeFakePodspec(plugin, 'ios'); | 
|  |  | 
|  | processRunner.mockProcessesForExecutable['pod'] = <FakeProcessInfo>[ | 
|  | FakeProcessInfo(MockProcess(stdout: 'Foo', stderr: 'Bar')), | 
|  | FakeProcessInfo(MockProcess()), | 
|  | ]; | 
|  |  | 
|  | final List<String> output = | 
|  | await runCapturingPrint(runner, <String>['podspec-check']); | 
|  |  | 
|  | expect( | 
|  | processRunner.recordedCalls, | 
|  | orderedEquals(<ProcessCall>[ | 
|  | ProcessCall('which', const <String>['pod'], packagesDir.path), | 
|  | ProcessCall( | 
|  | 'pod', | 
|  | <String>[ | 
|  | 'lib', | 
|  | 'lint', | 
|  | plugin | 
|  | .platformDirectory(FlutterPlatform.ios) | 
|  | .childFile('plugin1.podspec') | 
|  | .path, | 
|  | '--configuration=Debug', | 
|  | '--skip-tests', | 
|  | '--use-libraries' | 
|  | ], | 
|  | packagesDir.path), | 
|  | ProcessCall( | 
|  | 'pod', | 
|  | <String>[ | 
|  | 'lib', | 
|  | 'lint', | 
|  | plugin | 
|  | .platformDirectory(FlutterPlatform.ios) | 
|  | .childFile('plugin1.podspec') | 
|  | .path, | 
|  | '--configuration=Debug', | 
|  | '--skip-tests', | 
|  | ], | 
|  | packagesDir.path), | 
|  | ]), | 
|  | ); | 
|  |  | 
|  | expect(output, contains('Linting plugin1.podspec')); | 
|  | expect(output, contains('Foo')); | 
|  | expect(output, contains('Bar')); | 
|  | }); | 
|  |  | 
|  | test('skips shim podspecs for the Flutter framework', () async { | 
|  | final RepositoryPackage plugin = createFakePlugin( | 
|  | 'plugin1', | 
|  | packagesDir, | 
|  | extraFiles: <String>[ | 
|  | 'example/ios/Flutter/Flutter.podspec', | 
|  | 'example/macos/Flutter/ephemeral/FlutterMacOS.podspec', | 
|  | ], | 
|  | ); | 
|  | _writeFakePodspec(plugin, 'macos'); | 
|  |  | 
|  | final List<String> output = | 
|  | await runCapturingPrint(runner, <String>['podspec-check']); | 
|  |  | 
|  | expect(output, isNot(contains('FlutterMacOS.podspec'))); | 
|  | expect( | 
|  | processRunner.recordedCalls, | 
|  | orderedEquals(<ProcessCall>[ | 
|  | ProcessCall('which', const <String>['pod'], packagesDir.path), | 
|  | ProcessCall( | 
|  | 'pod', | 
|  | <String>[ | 
|  | 'lib', | 
|  | 'lint', | 
|  | plugin | 
|  | .platformDirectory(FlutterPlatform.macos) | 
|  | .childFile('plugin1.podspec') | 
|  | .path, | 
|  | '--configuration=Debug', | 
|  | '--skip-tests', | 
|  | '--use-libraries' | 
|  | ], | 
|  | packagesDir.path), | 
|  | ProcessCall( | 
|  | 'pod', | 
|  | <String>[ | 
|  | 'lib', | 
|  | 'lint', | 
|  | plugin | 
|  | .platformDirectory(FlutterPlatform.macos) | 
|  | .childFile('plugin1.podspec') | 
|  | .path, | 
|  | '--configuration=Debug', | 
|  | '--skip-tests', | 
|  | ], | 
|  | packagesDir.path), | 
|  | ]), | 
|  | ); | 
|  | }); | 
|  |  | 
|  | test('fails if pod is missing', () async { | 
|  | final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir); | 
|  | _writeFakePodspec(plugin, 'ios'); | 
|  |  | 
|  | // Simulate failure from `which pod`. | 
|  | processRunner.mockProcessesForExecutable['which'] = <FakeProcessInfo>[ | 
|  | FakeProcessInfo(MockProcess(exitCode: 1), <String>['pod']), | 
|  | ]; | 
|  |  | 
|  | Error? commandError; | 
|  | final List<String> output = await runCapturingPrint( | 
|  | runner, <String>['podspec-check'], errorHandler: (Error e) { | 
|  | commandError = e; | 
|  | }); | 
|  |  | 
|  | expect(commandError, isA<ToolExit>()); | 
|  |  | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder( | 
|  | <Matcher>[ | 
|  | contains('Unable to find "pod". Make sure it is in your path.'), | 
|  | ], | 
|  | )); | 
|  | }); | 
|  |  | 
|  | test('fails if linting as a framework fails', () async { | 
|  | final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir); | 
|  | _writeFakePodspec(plugin, 'ios'); | 
|  |  | 
|  | // Simulate failure from `pod`. | 
|  | processRunner.mockProcessesForExecutable['pod'] = <FakeProcessInfo>[ | 
|  | FakeProcessInfo(MockProcess(exitCode: 1)), | 
|  | ]; | 
|  |  | 
|  | Error? commandError; | 
|  | final List<String> output = await runCapturingPrint( | 
|  | runner, <String>['podspec-check'], errorHandler: (Error e) { | 
|  | commandError = e; | 
|  | }); | 
|  |  | 
|  | expect(commandError, isA<ToolExit>()); | 
|  |  | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder( | 
|  | <Matcher>[ | 
|  | contains('The following packages had errors:'), | 
|  | contains('plugin1:\n' | 
|  | '    plugin1.podspec') | 
|  | ], | 
|  | )); | 
|  | }); | 
|  |  | 
|  | test('fails if linting as a static library fails', () async { | 
|  | final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir); | 
|  | _writeFakePodspec(plugin, 'ios'); | 
|  |  | 
|  | // Simulate failure from the second call to `pod`. | 
|  | processRunner.mockProcessesForExecutable['pod'] = <FakeProcessInfo>[ | 
|  | FakeProcessInfo(MockProcess()), | 
|  | FakeProcessInfo(MockProcess(exitCode: 1)), | 
|  | ]; | 
|  |  | 
|  | Error? commandError; | 
|  | final List<String> output = await runCapturingPrint( | 
|  | runner, <String>['podspec-check'], errorHandler: (Error e) { | 
|  | commandError = e; | 
|  | }); | 
|  |  | 
|  | expect(commandError, isA<ToolExit>()); | 
|  |  | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder( | 
|  | <Matcher>[ | 
|  | contains('The following packages had errors:'), | 
|  | contains('plugin1:\n' | 
|  | '    plugin1.podspec') | 
|  | ], | 
|  | )); | 
|  | }); | 
|  |  | 
|  | test('fails if an iOS Swift plugin is missing the search paths workaround', | 
|  | () async { | 
|  | final RepositoryPackage plugin = createFakePlugin( | 
|  | 'plugin1', | 
|  | packagesDir, | 
|  | extraFiles: <String>[ | 
|  | 'ios/Classes/SomeSwift.swift', | 
|  | 'ios/plugin1/Package.swift', | 
|  | ], | 
|  | ); | 
|  | _writeFakePodspec(plugin, 'ios'); | 
|  |  | 
|  | Error? commandError; | 
|  | final List<String> output = await runCapturingPrint( | 
|  | runner, <String>['podspec-check'], errorHandler: (Error e) { | 
|  | commandError = e; | 
|  | }); | 
|  |  | 
|  | expect(commandError, isA<ToolExit>()); | 
|  |  | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder( | 
|  | <Matcher>[ | 
|  | contains(r''' | 
|  | s.xcconfig = { | 
|  | 'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift', | 
|  | 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', | 
|  | }'''), | 
|  | contains('The following packages had errors:'), | 
|  | contains('plugin1:\n' | 
|  | '    plugin1.podspec') | 
|  | ], | 
|  | )); | 
|  | }); | 
|  |  | 
|  | test( | 
|  | 'fails if a shared-source Swift plugin is missing the search paths workaround', | 
|  | () async { | 
|  | final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir, | 
|  | extraFiles: <String>['darwin/Classes/SomeSwift.swift']); | 
|  | _writeFakePodspec(plugin, 'darwin'); | 
|  |  | 
|  | Error? commandError; | 
|  | final List<String> output = await runCapturingPrint( | 
|  | runner, <String>['podspec-check'], errorHandler: (Error e) { | 
|  | commandError = e; | 
|  | }); | 
|  |  | 
|  | expect(commandError, isA<ToolExit>()); | 
|  |  | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder( | 
|  | <Matcher>[ | 
|  | contains(r''' | 
|  | s.xcconfig = { | 
|  | 'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift', | 
|  | 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', | 
|  | }'''), | 
|  | contains('The following packages had errors:'), | 
|  | contains('plugin1:\n' | 
|  | '    plugin1.podspec') | 
|  | ], | 
|  | )); | 
|  | }); | 
|  |  | 
|  | test('does not require the search paths workaround for iOS Package.swift', | 
|  | () async { | 
|  | final RepositoryPackage plugin = createFakePlugin( | 
|  | 'plugin1', | 
|  | packagesDir, | 
|  | extraFiles: <String>['ios/plugin1/Package.swift'], | 
|  | ); | 
|  | _writeFakePodspec(plugin, 'ios'); | 
|  |  | 
|  | final List<String> output = | 
|  | await runCapturingPrint(runner, <String>['podspec-check']); | 
|  |  | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder( | 
|  | <Matcher>[ | 
|  | contains('Ran for 1 package(s)'), | 
|  | ], | 
|  | )); | 
|  | }); | 
|  |  | 
|  | test('does not require the search paths workaround for Swift tests', | 
|  | () async { | 
|  | final RepositoryPackage plugin = createFakePlugin( | 
|  | 'plugin1', | 
|  | packagesDir, | 
|  | extraFiles: <String>[ | 
|  | 'darwin/Tests/SharedTest.swift', | 
|  | 'example/ios/RunnerTests/UnitTest.swift', | 
|  | 'example/ios/RunnerUITests/UITest.swift', | 
|  | ], | 
|  | ); | 
|  | _writeFakePodspec(plugin, 'ios'); | 
|  |  | 
|  | final List<String> output = | 
|  | await runCapturingPrint(runner, <String>['podspec-check']); | 
|  |  | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder( | 
|  | <Matcher>[ | 
|  | contains('Ran for 1 package(s)'), | 
|  | ], | 
|  | )); | 
|  | }); | 
|  |  | 
|  | test( | 
|  | 'does not require the search paths workaround for darwin Package.swift', | 
|  | () async { | 
|  | final RepositoryPackage plugin = createFakePlugin( | 
|  | 'plugin1', | 
|  | packagesDir, | 
|  | extraFiles: <String>['darwin/plugin1/Package.swift'], | 
|  | ); | 
|  | _writeFakePodspec(plugin, 'darwin'); | 
|  |  | 
|  | final List<String> output = | 
|  | await runCapturingPrint(runner, <String>['podspec-check']); | 
|  |  | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder( | 
|  | <Matcher>[ | 
|  | contains('Ran for 1 package(s)'), | 
|  | ], | 
|  | )); | 
|  | }); | 
|  |  | 
|  | test('does not require the search paths workaround for macOS plugins', | 
|  | () async { | 
|  | final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir, | 
|  | extraFiles: <String>['macos/Classes/SomeSwift.swift']); | 
|  | _writeFakePodspec(plugin, 'macos'); | 
|  |  | 
|  | final List<String> output = | 
|  | await runCapturingPrint(runner, <String>['podspec-check']); | 
|  |  | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder( | 
|  | <Matcher>[ | 
|  | contains('Ran for 1 package(s)'), | 
|  | ], | 
|  | )); | 
|  | }); | 
|  |  | 
|  | test('does not require the search paths workaround for ObjC iOS plugins', | 
|  | () async { | 
|  | final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir, | 
|  | extraFiles: <String>[ | 
|  | 'ios/Classes/SomeObjC.h', | 
|  | 'ios/Classes/SomeObjC.m' | 
|  | ]); | 
|  | _writeFakePodspec(plugin, 'ios'); | 
|  |  | 
|  | final List<String> output = | 
|  | await runCapturingPrint(runner, <String>['podspec-check']); | 
|  |  | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder( | 
|  | <Matcher>[ | 
|  | contains('Ran for 1 package(s)'), | 
|  | ], | 
|  | )); | 
|  | }); | 
|  |  | 
|  | test('passes if the search paths workaround is present', () async { | 
|  | final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir, | 
|  | extraFiles: <String>['ios/Classes/SomeSwift.swift']); | 
|  | _writeFakePodspec(plugin, 'ios', includeSwiftWorkaround: true); | 
|  |  | 
|  | final List<String> output = | 
|  | await runCapturingPrint(runner, <String>['podspec-check']); | 
|  |  | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder( | 
|  | <Matcher>[ | 
|  | contains('Ran for 1 package(s)'), | 
|  | ], | 
|  | )); | 
|  | }); | 
|  |  | 
|  | test('passes if the search paths workaround is present for iOS only', | 
|  | () async { | 
|  | final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir, | 
|  | extraFiles: <String>['ios/Classes/SomeSwift.swift']); | 
|  | _writeFakePodspec(plugin, 'ios', | 
|  | includeSwiftWorkaround: true, scopeSwiftWorkaround: true); | 
|  |  | 
|  | final List<String> output = | 
|  | await runCapturingPrint(runner, <String>['podspec-check']); | 
|  |  | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder( | 
|  | <Matcher>[ | 
|  | contains('Ran for 1 package(s)'), | 
|  | ], | 
|  | )); | 
|  | }); | 
|  |  | 
|  | test('does not require the search paths workaround for Swift example code', | 
|  | () async { | 
|  | final RepositoryPackage plugin = | 
|  | createFakePlugin('plugin1', packagesDir, extraFiles: <String>[ | 
|  | 'ios/Classes/SomeObjC.h', | 
|  | 'ios/Classes/SomeObjC.m', | 
|  | 'example/ios/Runner/AppDelegate.swift', | 
|  | ]); | 
|  | _writeFakePodspec(plugin, 'ios'); | 
|  |  | 
|  | final List<String> output = | 
|  | await runCapturingPrint(runner, <String>['podspec-check']); | 
|  |  | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder( | 
|  | <Matcher>[ | 
|  | contains('Ran for 1 package(s)'), | 
|  | ], | 
|  | )); | 
|  | }); | 
|  |  | 
|  | test('skips when there are no podspecs', () async { | 
|  | createFakePlugin('plugin1', packagesDir); | 
|  |  | 
|  | final List<String> output = | 
|  | await runCapturingPrint(runner, <String>['podspec-check']); | 
|  |  | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder( | 
|  | <Matcher>[contains('SKIPPING: No podspecs.')], | 
|  | )); | 
|  | }); | 
|  |  | 
|  | test('fails when an iOS plugin is missing a privacy manifest', () async { | 
|  | final RepositoryPackage plugin = createFakePlugin( | 
|  | 'plugin1', | 
|  | packagesDir, | 
|  | platformSupport: <String, PlatformDetails>{ | 
|  | Platform.iOS: const PlatformDetails(PlatformSupport.inline), | 
|  | }, | 
|  | ); | 
|  | _writeFakePodspec(plugin, 'ios'); | 
|  |  | 
|  | Error? commandError; | 
|  | final List<String> output = await runCapturingPrint( | 
|  | runner, <String>['podspec-check'], errorHandler: (Error e) { | 
|  | commandError = e; | 
|  | }); | 
|  |  | 
|  | expect(commandError, isA<ToolExit>()); | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder( | 
|  | <Matcher>[contains('No PrivacyInfo.xcprivacy file specified.')], | 
|  | )); | 
|  | }); | 
|  |  | 
|  | test('passes when an iOS plugin has a privacy manifest', () async { | 
|  | final RepositoryPackage plugin = createFakePlugin( | 
|  | 'plugin1', | 
|  | packagesDir, | 
|  | platformSupport: <String, PlatformDetails>{ | 
|  | Platform.iOS: const PlatformDetails(PlatformSupport.inline), | 
|  | }, | 
|  | ); | 
|  | _writeFakePodspec(plugin, 'ios', includePrivacyManifest: true); | 
|  |  | 
|  | final List<String> output = | 
|  | await runCapturingPrint(runner, <String>['podspec-check']); | 
|  |  | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder( | 
|  | <Matcher>[contains('Ran for 1 package(s)')], | 
|  | )); | 
|  | }); | 
|  |  | 
|  | test('fails when a macOS plugin is missing a privacy manifest', () async { | 
|  | final RepositoryPackage plugin = createFakePlugin( | 
|  | 'plugin1', | 
|  | packagesDir, | 
|  | platformSupport: <String, PlatformDetails>{ | 
|  | Platform.macOS: const PlatformDetails(PlatformSupport.inline), | 
|  | }, | 
|  | ); | 
|  | _writeFakePodspec(plugin, 'macos'); | 
|  |  | 
|  | Error? commandError; | 
|  | final List<String> output = await runCapturingPrint( | 
|  | runner, <String>['podspec-check'], errorHandler: (Error e) { | 
|  | commandError = e; | 
|  | }); | 
|  |  | 
|  | expect(commandError, isA<ToolExit>()); | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder( | 
|  | <Matcher>[contains('No PrivacyInfo.xcprivacy file specified.')], | 
|  | )); | 
|  | }); | 
|  |  | 
|  | test('passes when a macOS plugin has a privacy manifest', () async { | 
|  | final RepositoryPackage plugin = createFakePlugin( | 
|  | 'plugin1', | 
|  | packagesDir, | 
|  | platformSupport: <String, PlatformDetails>{ | 
|  | Platform.macOS: const PlatformDetails(PlatformSupport.inline), | 
|  | }, | 
|  | ); | 
|  | _writeFakePodspec(plugin, 'macos', includePrivacyManifest: true); | 
|  |  | 
|  | final List<String> output = | 
|  | await runCapturingPrint(runner, <String>['podspec-check']); | 
|  |  | 
|  | expect( | 
|  | output, | 
|  | containsAllInOrder( | 
|  | <Matcher>[contains('Ran for 1 package(s)')], | 
|  | )); | 
|  | }); | 
|  | }); | 
|  | } |