blob: 94606f54b764fda2ce0b559a614cdfc2f374fb6b [file] [log] [blame]
// 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.dart';
import 'package:flutter_plugin_tools/src/license_check_command.dart';
import 'package:test/test.dart';
void main() {
group('$LicenseCheckCommand', () {
CommandRunner<void> runner;
FileSystem fileSystem;
List<String> printedMessages;
Directory root;
setUp(() {
fileSystem = MemoryFileSystem();
final Directory packagesDir =
fileSystem.currentDirectory.childDirectory('packages');
root = packagesDir.parent;
printedMessages = <String>[];
final LicenseCheckCommand command = LicenseCheckCommand(
packagesDir,
fileSystem,
print: (Object message) => printedMessages.add(message.toString()),
);
runner =
CommandRunner<void>('license_test', 'Test for $LicenseCheckCommand');
runner.addCommand(command);
});
/// Writes a copyright+license block to [file], defaulting to a standard
/// block for this repository.
///
/// [commentString] is added to the start of each line.
/// [prefix] is added to the start of the entire block.
/// [suffix] is added to the end of the entire block.
void _writeLicense(
File file, {
String comment = '// ',
String prefix = '',
String suffix = '',
String copyright =
'Copyright 2013 The Flutter Authors. All rights reserved.',
List<String> license = const <String>[
'Use of this source code is governed by a BSD-style license that can be',
'found in the LICENSE file.',
],
}) {
final List<String> lines = <String>['$prefix$comment$copyright'];
for (final String line in license) {
lines.add('$comment$line');
}
file.writeAsStringSync(lines.join('\n') + suffix + '\n');
}
test('looks at only expected extensions', () async {
final Map<String, bool> extensions = <String, bool>{
'c': true,
'cc': true,
'cpp': true,
'dart': true,
'h': true,
'html': true,
'java': true,
'json': false,
'm': true,
'md': false,
'mm': true,
'png': false,
'swift': true,
'sh': true,
'yaml': false,
};
const String filenameBase = 'a_file';
for (final String fileExtension in extensions.keys) {
root.childFile('$filenameBase.$fileExtension').createSync();
}
try {
await runner.run(<String>['license-check']);
} on ToolExit {
// Ignore failure; the files are empty so the check is expected to fail,
// but this test isn't for that behavior.
}
extensions.forEach((String fileExtension, bool shouldCheck) {
final Matcher logLineMatcher =
contains('Checking $filenameBase.$fileExtension');
expect(printedMessages,
shouldCheck ? logLineMatcher : isNot(logLineMatcher));
});
});
test('ignore list overrides extension matches', () async {
final List<String> ignoredFiles = <String>[
// Ignored base names.
'flutter_export_environment.sh',
'GeneratedPluginRegistrant.java',
'GeneratedPluginRegistrant.m',
'generated_plugin_registrant.cc',
'generated_plugin_registrant.cpp',
// Ignored path suffixes.
'foo.g.dart',
'foo.mocks.dart',
// Ignored files.
'resource.h',
];
for (final String name in ignoredFiles) {
root.childFile(name).createSync();
}
await runner.run(<String>['license-check']);
for (final String name in ignoredFiles) {
expect(printedMessages, isNot(contains('Checking $name')));
}
});
test('passes if all checked files have license blocks', () async {
final File checked = root.childFile('checked.cc');
checked.createSync();
_writeLicense(checked);
final File notChecked = root.childFile('not_checked.md');
notChecked.createSync();
await runner.run(<String>['license-check']);
// Sanity check that the test did actually check a file.
expect(printedMessages, contains('Checking checked.cc'));
expect(printedMessages, contains('All source files passed validation!'));
});
test('handles the comment styles for all supported languages', () async {
final File fileA = root.childFile('file_a.cc');
fileA.createSync();
_writeLicense(fileA, comment: '// ');
final File fileB = root.childFile('file_b.sh');
fileB.createSync();
_writeLicense(fileB, comment: '# ');
final File fileC = root.childFile('file_c.html');
fileC.createSync();
_writeLicense(fileC, comment: '', prefix: '<!-- ', suffix: ' -->');
await runner.run(<String>['license-check']);
// Sanity check that the test did actually check the files.
expect(printedMessages, contains('Checking file_a.cc'));
expect(printedMessages, contains('Checking file_b.sh'));
expect(printedMessages, contains('Checking file_c.html'));
expect(printedMessages, contains('All source files passed validation!'));
});
test('fails if any checked files are missing license blocks', () async {
final File goodA = root.childFile('good.cc');
goodA.createSync();
_writeLicense(goodA);
final File goodB = root.childFile('good.h');
goodB.createSync();
_writeLicense(goodB);
root.childFile('bad.cc').createSync();
root.childFile('bad.h').createSync();
await expectLater(() => runner.run(<String>['license-check']),
throwsA(const TypeMatcher<ToolExit>()));
// Failure should give information about the problematic files.
expect(
printedMessages,
contains(
'The license block for these files is missing or incorrect:'));
expect(printedMessages, contains(' bad.cc'));
expect(printedMessages, contains(' bad.h'));
// Failure shouldn't print the success message.
expect(printedMessages,
isNot(contains('All source files passed validation!')));
});
test('fails if any checked files are missing just the copyright', () async {
final File good = root.childFile('good.cc');
good.createSync();
_writeLicense(good);
final File bad = root.childFile('bad.cc');
bad.createSync();
_writeLicense(bad, copyright: '');
await expectLater(() => runner.run(<String>['license-check']),
throwsA(const TypeMatcher<ToolExit>()));
// Failure should give information about the problematic files.
expect(
printedMessages,
contains(
'The license block for these files is missing or incorrect:'));
expect(printedMessages, contains(' bad.cc'));
// Failure shouldn't print the success message.
expect(printedMessages,
isNot(contains('All source files passed validation!')));
});
test('fails if any checked files are missing just the license', () async {
final File good = root.childFile('good.cc');
good.createSync();
_writeLicense(good);
final File bad = root.childFile('bad.cc');
bad.createSync();
_writeLicense(bad, license: <String>[]);
await expectLater(() => runner.run(<String>['license-check']),
throwsA(const TypeMatcher<ToolExit>()));
// Failure should give information about the problematic files.
expect(
printedMessages,
contains(
'The license block for these files is missing or incorrect:'));
expect(printedMessages, contains(' bad.cc'));
// Failure shouldn't print the success message.
expect(printedMessages,
isNot(contains('All source files passed validation!')));
});
test('fails if any third-party code is not in a third_party directory',
() async {
final File thirdPartyFile = root.childFile('third_party.cc');
thirdPartyFile.createSync();
_writeLicense(thirdPartyFile, copyright: 'Copyright 2017 Someone Else');
await expectLater(() => runner.run(<String>['license-check']),
throwsA(const TypeMatcher<ToolExit>()));
// Failure should give information about the problematic files.
expect(
printedMessages,
contains(
'The license block for these files is missing or incorrect:'));
expect(printedMessages, contains(' third_party.cc'));
// Failure shouldn't print the success message.
expect(printedMessages,
isNot(contains('All source files passed validation!')));
});
test('succeeds for third-party code in a third_party directory', () async {
final File thirdPartyFile = root
.childDirectory('a_plugin')
.childDirectory('lib')
.childDirectory('src')
.childDirectory('third_party')
.childFile('file.cc');
thirdPartyFile.createSync(recursive: true);
_writeLicense(thirdPartyFile,
copyright: 'Copyright 2017 Workiva Inc.',
license: <String>[
'Licensed under the Apache License, Version 2.0 (the "License");',
'you may not use this file except in compliance with the License.'
]);
await runner.run(<String>['license-check']);
// Sanity check that the test did actually check the file.
expect(printedMessages,
contains('Checking a_plugin/lib/src/third_party/file.cc'));
expect(printedMessages, contains('All source files passed validation!'));
});
test('fails for licenses that the tool does not expect', () async {
final File good = root.childFile('good.cc');
good.createSync();
_writeLicense(good);
final File bad = root.childDirectory('third_party').childFile('bad.cc');
bad.createSync(recursive: true);
_writeLicense(bad, license: <String>[
'This program is free software: you can redistribute it and/or modify',
'it under the terms of the GNU General Public License',
]);
await expectLater(() => runner.run(<String>['license-check']),
throwsA(const TypeMatcher<ToolExit>()));
// Failure should give information about the problematic files.
expect(
printedMessages,
contains(
'No recognized license was found for the following third-party files:'));
expect(printedMessages, contains(' third_party/bad.cc'));
// Failure shouldn't print the success message.
expect(printedMessages,
isNot(contains('All source files passed validation!')));
});
test('Apache is not recognized for new authors without validation changes',
() async {
final File good = root.childFile('good.cc');
good.createSync();
_writeLicense(good);
final File bad = root.childDirectory('third_party').childFile('bad.cc');
bad.createSync(recursive: true);
_writeLicense(
bad,
copyright: 'Copyright 2017 Some New Authors.',
license: <String>[
'Licensed under the Apache License, Version 2.0 (the "License");',
'you may not use this file except in compliance with the License.'
],
);
await expectLater(() => runner.run(<String>['license-check']),
throwsA(const TypeMatcher<ToolExit>()));
// Failure should give information about the problematic files.
expect(
printedMessages,
contains(
'No recognized license was found for the following third-party files:'));
expect(printedMessages, contains(' third_party/bad.cc'));
// Failure shouldn't print the success message.
expect(printedMessages,
isNot(contains('All source files passed validation!')));
});
test('passes if all first-party LICENSE files are correctly formatted',
() async {
final File license = root.childFile('LICENSE');
license.createSync();
license.writeAsStringSync(_correctLicenseFileText);
await runner.run(<String>['license-check']);
// Sanity check that the test did actually check the file.
expect(printedMessages, contains('Checking LICENSE'));
expect(printedMessages, contains('All LICENSE files passed validation!'));
});
test('fails if any first-party LICENSE files are incorrectly formatted',
() async {
final File license = root.childFile('LICENSE');
license.createSync();
license.writeAsStringSync(_incorrectLicenseFileText);
await expectLater(() => runner.run(<String>['license-check']),
throwsA(const TypeMatcher<ToolExit>()));
expect(printedMessages,
isNot(contains('All LICENSE files passed validation!')));
});
test('ignores third-party LICENSE format', () async {
final File license =
root.childDirectory('third_party').childFile('LICENSE');
license.createSync(recursive: true);
license.writeAsStringSync(_incorrectLicenseFileText);
await runner.run(<String>['license-check']);
// The file shouldn't be checked.
expect(printedMessages, isNot(contains('Checking third_party/LICENSE')));
expect(printedMessages, contains('All LICENSE files passed validation!'));
});
});
}
const String _correctLicenseFileText = '''
Copyright 2013 The Flutter Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
''';
// A common incorrect version created by copying text intended for a code file,
// with comment markers.
const String _incorrectLicenseFileText = '''
// Copyright 2013 The Flutter Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
''';