blob: 9c246f9ae9a951ef8d226edafe27521e65b1fbd2 [file] [log] [blame]
// Copyright 2014 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:conductor_core/src/codesign.dart';
import 'package:conductor_core/src/repository.dart';
import 'package:file/memory.dart';
import 'package:platform/platform.dart';
import './common.dart';
void main() {
group('codesign command', () {
const String flutterRoot = '/flutter';
const String checkoutsParentDirectory = '$flutterRoot/dev/conductor/';
const String flutterCache =
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache';
const String flutterBin =
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/flutter';
const String revision = 'abcd1234';
late CommandRunner<void> runner;
late Checkouts checkouts;
late MemoryFileSystem fileSystem;
late FakePlatform platform;
late TestStdio stdio;
late FakeProcessManager processManager;
const List<String> binariesWithEntitlements = <String>[
'$flutterCache/dart-sdk/bin/dart',
'$flutterCache/dart-sdk/bin/dartaotruntime',
];
const List<String> binariesWithoutEntitlements = <String>[
'$flutterCache/engine/darwin-x64/font-subset',
];
const List<String> allBinaries = <String>[
...binariesWithEntitlements,
...binariesWithoutEntitlements,
];
void createRunner({
String operatingSystem = 'macos',
List<FakeCommand>? commands,
}) {
stdio = TestStdio();
fileSystem = MemoryFileSystem.test();
platform = FakePlatform(operatingSystem: operatingSystem);
processManager = FakeProcessManager.list(commands ?? <FakeCommand>[]);
checkouts = Checkouts(
fileSystem: fileSystem,
parentDirectory: fileSystem.directory(checkoutsParentDirectory),
platform: platform,
processManager: processManager,
stdio: stdio,
);
final FakeCodesignCommand command = FakeCodesignCommand(
checkouts: checkouts,
binariesWithEntitlements: Future<List<String>>.value(binariesWithEntitlements),
binariesWithoutEntitlements: Future<List<String>>.value(binariesWithoutEntitlements),
flutterRoot: fileSystem.directory(flutterRoot),
);
runner = CommandRunner<void>('codesign-test', '')
..addCommand(command);
}
test('throws exception if not run from macos', () async {
createRunner(operatingSystem: 'linux');
expect(
() async => runner.run(<String>['codesign']),
throwsExceptionWith('Error! Expected operating system "macos"'),
);
});
test('throws exception if verify flag is not provided', () async {
createRunner();
expect(
() async => runner.run(<String>['codesign']),
throwsExceptionWith(
'Sorry, but codesigning is not implemented yet. Please pass the --$kVerify flag to verify signatures'),
);
});
test('does not fail if --revision flag not provided', () async {
final List<FakeCommand> codesignCheckCommands = <FakeCommand>[];
for (final String bin in binariesWithEntitlements) {
codesignCheckCommands.add(
FakeCommand(
command: <String>['codesign', '-vvv', bin],
),
);
codesignCheckCommands.add(
FakeCommand(
command: <String>['codesign', '--display', '--entitlements', ':-', bin],
stdout: expectedEntitlements.join('\n'),
),
);
}
for (final String bin in binariesWithoutEntitlements) {
codesignCheckCommands.add(
FakeCommand(
command: <String>['codesign', '-vvv', bin],
),
);
}
createRunner(commands: <FakeCommand>[
const FakeCommand(command: <String>[
'git',
'rev-parse',
'HEAD',
], stdout: revision),
const FakeCommand(command: <String>[
'git',
'clone',
'--origin',
'upstream',
'--',
FrameworkRepository.defaultUpstream,
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
]),
const FakeCommand(command: <String>[
'git',
'checkout',
FrameworkRepository.defaultBranch,
]),
const FakeCommand(command: <String>[
'git',
'rev-parse',
'HEAD',
], stdout: revision),
const FakeCommand(command: <String>[
'git',
'checkout',
revision,
]),
const FakeCommand(command: <String>[
flutterBin,
'help',
]),
const FakeCommand(command: <String>[
flutterBin,
'help',
]),
const FakeCommand(command: <String>[
flutterBin,
'precache',
'--android',
'--ios',
'--macos',
]),
FakeCommand(
command: const <String>[
'find',
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache',
'-type',
'f',
],
stdout: allBinaries.join('\n'),
),
for (String bin in allBinaries)
FakeCommand(
command: <String>['file', '--mime-type', '-b', bin],
stdout: 'application/x-mach-binary',
),
...codesignCheckCommands,
]);
await runner.run(<String>['codesign', '--$kVerify']);
expect(processManager.hasRemainingExpectations, false);
expect(stdio.stdout, contains('Verified that binaries are codesigned and have expected entitlements'));
});
test('framework cloned from repo provided by --$kUpstream', () async {
const String upstreamRepo = 'https://githost.org/org/project';
final List<FakeCommand> codesignCheckCommands = <FakeCommand>[];
for (final String bin in binariesWithEntitlements) {
codesignCheckCommands.add(
FakeCommand(
command: <String>['codesign', '-vvv', bin],
),
);
codesignCheckCommands.add(
FakeCommand(
command: <String>['codesign', '--display', '--entitlements', ':-', bin],
stdout: expectedEntitlements.join('\n'),
),
);
}
for (final String bin in binariesWithoutEntitlements) {
codesignCheckCommands.add(
FakeCommand(
command: <String>['codesign', '-vvv', bin],
),
);
}
createRunner(commands: <FakeCommand>[
const FakeCommand(command: <String>[
'git',
'clone',
'--origin',
'upstream',
'--',
upstreamRepo,
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
]),
const FakeCommand(command: <String>[
'git',
'checkout',
FrameworkRepository.defaultBranch,
]),
const FakeCommand(command: <String>[
'git',
'rev-parse',
'HEAD',
], stdout: revision),
const FakeCommand(command: <String>[
'git',
'checkout',
revision,
]),
const FakeCommand(command: <String>[
flutterBin,
'help',
]),
const FakeCommand(command: <String>[
flutterBin,
'help',
]),
const FakeCommand(command: <String>[
flutterBin,
'precache',
'--android',
'--ios',
'--macos',
]),
FakeCommand(
command: const <String>[
'find',
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache',
'-type',
'f',
],
stdout: allBinaries.join('\n'),
),
for (String bin in allBinaries)
FakeCommand(
command: <String>['file', '--mime-type', '-b', bin],
stdout: 'application/x-mach-binary',
),
...codesignCheckCommands,
]);
await runner.run(<String>[
'codesign',
'--$kVerify',
'--$kRevision',
revision,
'--$kUpstream',
upstreamRepo,
]);
expect(processManager, hasNoRemainingExpectations);
expect(stdio.stdout, contains('Verified that binaries for commit $revision are codesigned and have expected entitlements'));
});
test('succeeds if every binary is codesigned and has correct entitlements', () async {
final List<FakeCommand> codesignCheckCommands = <FakeCommand>[];
for (final String bin in binariesWithEntitlements) {
codesignCheckCommands.add(
FakeCommand(
command: <String>['codesign', '-vvv', bin],
),
);
codesignCheckCommands.add(
FakeCommand(
command: <String>['codesign', '--display', '--entitlements', ':-', bin],
stdout: expectedEntitlements.join('\n'),
),
);
}
for (final String bin in binariesWithoutEntitlements) {
codesignCheckCommands.add(
FakeCommand(
command: <String>['codesign', '-vvv', bin],
),
);
}
createRunner(commands: <FakeCommand>[
const FakeCommand(command: <String>[
'git',
'clone',
'--origin',
'upstream',
'--',
FrameworkRepository.defaultUpstream,
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
]),
const FakeCommand(command: <String>[
'git',
'checkout',
FrameworkRepository.defaultBranch,
]),
const FakeCommand(command: <String>[
'git',
'rev-parse',
'HEAD',
], stdout: revision),
const FakeCommand(command: <String>[
'git',
'checkout',
revision,
]),
const FakeCommand(command: <String>[
flutterBin,
'help',
]),
const FakeCommand(command: <String>[
flutterBin,
'help',
]),
const FakeCommand(command: <String>[
flutterBin,
'precache',
'--android',
'--ios',
'--macos',
]),
FakeCommand(
command: const <String>[
'find',
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache',
'-type',
'f',
],
stdout: allBinaries.join('\n'),
),
for (String bin in allBinaries)
FakeCommand(
command: <String>['file', '--mime-type', '-b', bin],
stdout: 'application/x-mach-binary',
),
...codesignCheckCommands,
]);
await runner.run(<String>['codesign', '--$kVerify', '--$kRevision', revision]);
expect(processManager.hasRemainingExpectations, false);
expect(stdio.stdout, contains('Verified that binaries for commit $revision are codesigned and have expected entitlements'));
});
test('fails if a single binary is not codesigned', () async {
final List<FakeCommand> codesignCheckCommands = <FakeCommand>[];
codesignCheckCommands.add(
const FakeCommand(
command: <String>['codesign', '-vvv', '$flutterCache/dart-sdk/bin/dart'],
),
);
codesignCheckCommands.add(
FakeCommand(
command: const <String>[
'codesign',
'--display',
'--entitlements',
':-',
'$flutterCache/dart-sdk/bin/dart',
],
stdout: expectedEntitlements.join('\n'),
)
);
// Not signed
codesignCheckCommands.add(
const FakeCommand(
command: <String>['codesign', '-vvv', '$flutterCache/dart-sdk/bin/dartaotruntime'],
exitCode: 1,
),
);
codesignCheckCommands.add(
const FakeCommand(
command: <String>['codesign', '-vvv', '$flutterCache/engine/darwin-x64/font-subset'],
),
);
createRunner(commands: <FakeCommand>[
const FakeCommand(command: <String>[
'git',
'clone',
'--origin',
'upstream',
'--',
FrameworkRepository.defaultUpstream,
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
]),
const FakeCommand(command: <String>[
'git',
'checkout',
FrameworkRepository.defaultBranch,
]),
const FakeCommand(command: <String>[
'git',
'rev-parse',
'HEAD',
], stdout: revision),
const FakeCommand(command: <String>[
'git',
'checkout',
revision,
]),
const FakeCommand(command: <String>[
flutterBin,
'help',
]),
const FakeCommand(command: <String>[
flutterBin,
'help',
]),
const FakeCommand(command: <String>[
flutterBin,
'precache',
'--android',
'--ios',
'--macos',
]),
FakeCommand(
command: const <String>[
'find',
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache',
'-type',
'f',
],
stdout: allBinaries.join('\n'),
),
for (String bin in allBinaries)
FakeCommand(
command: <String>['file', '--mime-type', '-b', bin],
stdout: 'application/x-mach-binary',
),
...codesignCheckCommands,
]);
await expectLater(
() => runner.run(<String>['codesign', '--$kVerify', '--$kRevision', revision]),
throwsExceptionWith('Test failed because unsigned binaries detected.'),
);
expect(processManager.hasRemainingExpectations, false);
});
test('fails if a single binary has the wrong entitlements', () async {
final List<FakeCommand> codesignCheckCommands = <FakeCommand>[];
codesignCheckCommands.add(
const FakeCommand(
command: <String>['codesign', '-vvv', '$flutterCache/dart-sdk/bin/dart'],
),
);
codesignCheckCommands.add(
FakeCommand(
command: const <String>['codesign', '--display', '--entitlements', ':-', '$flutterCache/dart-sdk/bin/dart'],
stdout: expectedEntitlements.join('\n'),
)
);
codesignCheckCommands.add(
const FakeCommand(
command: <String>['codesign', '-vvv', '$flutterCache/dart-sdk/bin/dartaotruntime'],
),
);
// No entitlements
codesignCheckCommands.add(
const FakeCommand(
command: <String>['codesign', '--display', '--entitlements', ':-', '$flutterCache/dart-sdk/bin/dartaotruntime'],
)
);
codesignCheckCommands.add(
const FakeCommand(
command: <String>['codesign', '-vvv', '$flutterCache/engine/darwin-x64/font-subset'],
),
);
createRunner(commands: <FakeCommand>[
const FakeCommand(command: <String>[
'git',
'clone',
'--origin',
'upstream',
'--',
FrameworkRepository.defaultUpstream,
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
]),
const FakeCommand(command: <String>[
'git',
'checkout',
FrameworkRepository.defaultBranch,
]),
const FakeCommand(command: <String>[
'git',
'rev-parse',
'HEAD',
], stdout: revision),
const FakeCommand(command: <String>[
'git',
'checkout',
revision,
]),
const FakeCommand(command: <String>[
flutterBin,
'help',
]),
const FakeCommand(command: <String>[
flutterBin,
'help',
]),
const FakeCommand(command: <String>[
flutterBin,
'precache',
'--android',
'--ios',
'--macos',
]),
FakeCommand(
command: const <String>[
'find',
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache',
'-type',
'f',
],
stdout: allBinaries.join('\n'),
),
for (String bin in allBinaries)
FakeCommand(
command: <String>['file', '--mime-type', '-b', bin],
stdout: 'application/x-mach-binary',
),
...codesignCheckCommands,
]);
await expectLater(
() => runner.run(<String>['codesign', '--$kVerify', '--$kRevision', revision]),
throwsExceptionWith('Test failed because files found with the wrong entitlements'),
);
expect(processManager.hasRemainingExpectations, false);
});
test('does not check signatures or entitlements if --no-$kSignatures specified', () async {
createRunner(commands: <FakeCommand>[
const FakeCommand(command: <String>[
'git',
'clone',
'--origin',
'upstream',
'--',
FrameworkRepository.defaultUpstream,
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
]),
const FakeCommand(command: <String>[
'git',
'checkout',
FrameworkRepository.defaultBranch,
]),
const FakeCommand(command: <String>[
'git',
'rev-parse',
'HEAD',
], stdout: revision),
const FakeCommand(command: <String>[
'git',
'checkout',
revision,
]),
const FakeCommand(command: <String>[
flutterBin,
'help',
]),
const FakeCommand(command: <String>[
flutterBin,
'help',
]),
const FakeCommand(command: <String>[
flutterBin,
'precache',
'--android',
'--ios',
'--macos',
]),
FakeCommand(
command: const <String>[
'find',
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache',
'-type',
'f',
],
stdout: allBinaries.join('\n'),
),
for (String bin in allBinaries)
FakeCommand(
command: <String>['file', '--mime-type', '-b', bin],
stdout: 'application/x-mach-binary',
),
]);
await runner.run(<String>[
'codesign',
'--$kVerify',
'--no-$kSignatures',
'--$kRevision',
revision,
]);
expect(
processManager.hasRemainingExpectations,
false,
);
});
});
}
class FakeCodesignCommand extends CodesignCommand {
FakeCodesignCommand({
required super.checkouts,
required this.binariesWithEntitlements,
required this.binariesWithoutEntitlements,
required super.flutterRoot,
});
@override
final Future<List<String>> binariesWithEntitlements;
@override
final Future<List<String>> binariesWithoutEntitlements;
}