diff options
author | lukegleeson <luke.gleeson@est.tech> | 2022-03-08 11:41:52 +0000 |
---|---|---|
committer | lukegleeson <luke.gleeson@est.tech> | 2022-03-25 17:03:21 +0000 |
commit | 165e3b8bcc492ebe31431f30c14d5dc98bb7f18e (patch) | |
tree | 7e55fa1b817ad6820d26b8008f05f6208da5d12a | |
parent | 06b6584a741b565922b32bb7e861ae4d16854673 (diff) |
Copyright Check Script
- Prints warnings to build log if copyright issues detected
- File ignore included for checkstyle folder and common extensions which don't have copyrights
- Included Tests
Issue-ID: CPS-911
Signed-off-by: lukegleeson <luke.gleeson@est.tech>
Change-Id: Idbdd050af964335cc32218e3c11c77d4101f9ecd
-rw-r--r-- | checkstyle/pom.xml | 54 | ||||
-rw-r--r-- | checkstyle/src/main/CopyrightCheck.py | 262 | ||||
-rw-r--r-- | checkstyle/src/main/resources/copyright-template.txt | 16 | ||||
-rw-r--r-- | checkstyle/src/main/resources/ignore-files-config.csv | 6 | ||||
-rw-r--r-- | checkstyle/src/main/resources/project-committers-config.csv | 3 | ||||
-rw-r--r-- | checkstyle/src/main/test_CopyrightCheck.py | 441 |
6 files changed, 782 insertions, 0 deletions
diff --git a/checkstyle/pom.xml b/checkstyle/pom.xml index 07e6cf9663..185d9c33a4 100644 --- a/checkstyle/pom.xml +++ b/checkstyle/pom.xml @@ -2,6 +2,7 @@ <!-- ============LICENSE_START======================================================= Copyright (C) 2020 Pantheon.tech + Modifications Copyright (C) 2022 Nordix Foundation ================================================================================ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -27,6 +28,31 @@ <artifactId>checkstyle</artifactId> <version>3.0.0-SNAPSHOT</version> + <profiles> + <profile> + <id>Windows</id> + <activation> + <os> + <family>Windows</family> + </os> + </activation> + <properties> + <script.executor>python</script.executor> + </properties> + </profile> + <profile> + <id>unix</id> + <activation> + <os> + <family>unix</family> + </os> + </activation> + <properties> + <script.executor>python3</script.executor> + </properties> + </profile> + </profiles> + <properties> <nexusproxy>https://nexus.onap.org</nexusproxy> <releaseNexusPath>/content/repositories/releases/</releaseNexusPath> @@ -46,4 +72,32 @@ <url>${nexusproxy}${snapshotNexusPath}</url> </snapshotRepository> </distributionManagement> + <build> + <plugins> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>exec-maven-plugin</artifactId> + <version>1.6.0</version> + <executions> + <execution> + <id>copyright-check</id> + <phase>verify</phase> + <goals> + <goal>exec</goal> + </goals> + <configuration> + <executable>${script.executor}</executable> + <workingDirectory>../checkstyle/src/main/</workingDirectory> + <arguments> + <argument>CopyrightCheck.py</argument> + <argument>resources/project-committers-config.csv</argument> + <argument>resources/copyright-template.txt</argument> + <argument>resources/ignore-files-config.csv</argument> + </arguments> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> </project>
\ No newline at end of file diff --git a/checkstyle/src/main/CopyrightCheck.py b/checkstyle/src/main/CopyrightCheck.py new file mode 100644 index 0000000000..8f1dbff571 --- /dev/null +++ b/checkstyle/src/main/CopyrightCheck.py @@ -0,0 +1,262 @@ +# ============LICENSE_START======================================================= +# Copyright (C) 2022 Nordix Foundation +# ================================================================================ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +import subprocess +import csv +import re +import datetime + +#constants +import sys + +COMMITTERS_CONFIG_FILE = '' +TEMPLATE_COPYRIGHT_FILE = '' +IGNORE_FILE = '' +if len(sys.argv) == 4: + COMMITTERS_CONFIG_FILE = sys.argv[1] + TEMPLATE_COPYRIGHT_FILE = sys.argv[2] + IGNORE_FILE = sys.argv[3] + +BANNER = '=' * 120 + +def main(): + print(BANNER + '\nCopyright Check Python Script:') + PermissionsCheck() + + committerEmailExtension = GetCommitterEmailExtension() + projectCommitters = ReadProjectCommittersConfigFile() + + CheckCommitterInConfigFile(committerEmailExtension, projectCommitters) + + alteredFiles = FindAlteredFiles() + + if alteredFiles: + issueCounter = CheckCopyrightForFiles(alteredFiles, projectCommitters, committerEmailExtension) + else: + issueCounter = 0 + + print(str(issueCounter) + ' issue(s) found after '+ str(len(alteredFiles)) + ' altered file(s) checked') + print(BANNER) + + +# Check that Script has access to command line functions to use git +def PermissionsCheck(): + if 'permission denied' in subprocess.run('git', shell=True, stdout=subprocess.PIPE).stdout.decode('utf-8').lower(): + print('Error, I may not have the necessary permissions. Exiting...') + print(BANNER) + sys.exit() + else: + return + +# Returns List of Strings of file tracked by git which have been changed/added +def FindAlteredFiles(): + ignoreFilePaths = GetIgnoredFiles() + + #Before Stage lower case d removes deleted files + stream = subprocess.run('git diff --name-only --diff-filter=d', shell=True, stdout=subprocess.PIPE) + fileNames = stream.stdout.decode('utf-8') + #Staged + stream = subprocess.run('git diff --name-only --cached --diff-filter=d', shell=True, stdout=subprocess.PIPE) + fileNames += '\n' + stream.stdout.decode('utf-8') + #New committed + stream = subprocess.run('git diff --name-only HEAD^ HEAD --diff-filter=d', shell=True, stdout=subprocess.PIPE) + fileNames += '\n' + stream.stdout.decode('utf-8') + + #String to list of strings + alteredFiles = fileNames.split("\n") + + #Remove duplicates + alteredFiles = list(dict.fromkeys(alteredFiles)) + + #Remove blank string(s) + alteredFiles = list(filter(None, alteredFiles)) + + #Remove ignored-extensions + alteredFiles = list(filter(lambda fileName: not re.match("|".join(ignoreFilePaths), fileName), alteredFiles)) + + return alteredFiles + +# Get the email of the most recent committer +def GetCommitterEmailExtension(): + email = subprocess.run('git show -s --format=\'%ce\'', shell=True, stdout=subprocess.PIPE).stdout.decode('utf-8').rstrip('\n') + return email[email.index('@'):] + +# Read the config file with names of companies and respective email extensions +def ReadProjectCommittersConfigFile(): + try: + with open(COMMITTERS_CONFIG_FILE, 'r') as file: + reader = csv.reader(file, delimiter=',') + projectCommitters = {row[0]:row[1] for row in reader} + projectCommitters.pop('email') #Remove csv header + except FileNotFoundError: + print('Unable to open Project Committers Config File, have the command line arguments been set?') + print(BANNER) + sys.exit() + return projectCommitters + +def CheckCommitterInConfigFile(committerEmailExtension, projectCommitters): + if not committerEmailExtension in projectCommitters: + print('Error, Committer email is not included in config file.') + print('If your company is new to the project please make appropriate changes to project-committers-config.csv') + print('for Copyright Check to work.') + print('Exiting...') + print(BANNER) + sys.exit() + else: + return True + +# Read config file with list of files to ignore +def GetIgnoredFiles(): + try: + with open(IGNORE_FILE, 'r') as file: + reader = csv.reader(file) + ignoreFilePaths = [row[0] for row in reader] + ignoreFilePaths.pop(0) #Remove csv header + ignoreFilePaths = [filePath.replace('*', '.*') for filePath in ignoreFilePaths] + except FileNotFoundError: + print('Unable to open File Ignore Config File, have the command line arguments been set?') + print(BANNER) + sys.exit() + return ignoreFilePaths + +# Read the template copyright file +def GetCopyrightTemplate(): + try: + with open(TEMPLATE_COPYRIGHT_FILE, 'r') as file: + copyrightTemplate = file.readlines() + except FileNotFoundError: + print('Unable to open Template Copyright File, have the command line arguments been set?') + print(BANNER) + sys.exit() + return copyrightTemplate + +def GetProjectRootDir(): + return subprocess.run('git rev-parse --show-toplevel', shell=True, stdout=subprocess.PIPE).stdout.decode('utf-8').rstrip('\n') + '/' + +# Get the Copyright from the altered file +def ParseFileCopyright(fileObject): + global issueCounter + copyrightFlag = False + copyrightInFile = {} + lineNumber = 1 + for line in fileObject: + if 'LICENSE_START' in line: + copyrightFlag = True + if copyrightFlag: + copyrightInFile[lineNumber] = line + if 'LICENSE_END' in line: + break + lineNumber += 1 + + if not copyrightFlag: + print(fileObject.name + ' | no copyright found') + return {}, {} + + copyrightSignatures = {} + copyrightLineNumbers = list(copyrightInFile.keys()) + #Capture signature lines after LICENSE_START line + for lineNumber in copyrightLineNumbers: + if '=' not in copyrightInFile[lineNumber]: + copyrightSignatures[lineNumber] = copyrightInFile[lineNumber] + copyrightInFile.pop(lineNumber) + elif 'LICENSE_START' not in copyrightInFile[lineNumber]: + break + + return (copyrightInFile, copyrightSignatures) + +# Remove the Block comment syntax +def RemoveCommentBlock(fileCopyright): + # Comment Characters can very depending on file # *.. + endOfCommentsIndex = list(fileCopyright.values())[0].index('=') + for key in fileCopyright: + fileCopyright[key] = fileCopyright[key][endOfCommentsIndex:] + if fileCopyright[key] == '': + fileCopyright[key] = '\n' + + return fileCopyright + +def CheckCopyrightForFiles(alteredFiles, projectCommitters, committerEmailExtension): + issueCounter = 0 + templateCopyright = GetCopyrightTemplate() #Get Copyright Template + projectRootDir = GetProjectRootDir() + + for fileName in alteredFiles: # Not removed files + try: + with open(projectRootDir + fileName, 'r') as fileObject: + (fileCopyright, fileSignatures) = ParseFileCopyright(fileObject) + + #Empty dict evaluates to false + if fileCopyright and fileSignatures: + fileCopyright = RemoveCommentBlock(fileCopyright) + issueCounter += CheckCopyrightFormat(fileCopyright, templateCopyright, projectRootDir + fileName) + committerCompany = projectCommitters[committerEmailExtension] + issueCounter += CheckCopyrightSignature(fileSignatures, committerCompany, projectRootDir + fileName) + else: + issueCounter += 1 + + except FileNotFoundError: + issueCounter += 1 + print('Unable to find file ' + projectRootDir + fileName) + return issueCounter + +# Check that the filecopyright matches the template copyright and print comparison +def CheckCopyrightFormat(copyrightInFile, templateCopyright, filePath): + issueCounter = 0 + errorWithComparison = '' + for copyrightInFileKey, templateLine in zip(copyrightInFile, templateCopyright): + if copyrightInFile[copyrightInFileKey] != templateLine: + issueCounter += 1 + errorWithComparison += filePath + ' | line ' + '{:2}'.format(copyrightInFileKey) + ' read \t ' + repr(copyrightInFile[copyrightInFileKey]) + '\n' + errorWithComparison += filePath + ' | line ' + '{:2}'.format(copyrightInFileKey) + ' expected ' + repr(templateLine) + '\n' + if errorWithComparison != '': + print(errorWithComparison.rstrip('\n')) + return issueCounter + +# Check the signatures and compare with committer signature and current year +def CheckCopyrightSignature(copyrightSignatures, committerCompany, filePath): + issueCounter = 0 + errorWithSignature = '' + signatureExists = False #signatureExistsForCommitter + afterFirstLine = False #afterFirstCopyright + for key in copyrightSignatures: + if afterFirstLine and 'Modifications Copyright' not in copyrightSignatures[key]: + issueCounter += 1 + errorWithSignature += filePath + ' | line ' + str(key) + ' expected Modifications Copyright\n' + elif not afterFirstLine and 'Copyright' not in copyrightSignatures[key]: + issueCounter += 1 + errorWithSignature += filePath + ' | line ' + str(key) + ' expected Copyright\n' + if committerCompany in copyrightSignatures[key]: + signatureExists = True + signatureYear = int(re.findall(r'\d+', copyrightSignatures[key])[-1]) + currentYear = datetime.date.today().year + if signatureYear != currentYear: + issueCounter += 1 + errorWithSignature += filePath + ' | line ' + str(key) + ' update year to include ' + str(currentYear) + '\n' + afterFirstLine = True + + if not signatureExists: + issueCounter += 1 + errorWithSignature += filePath + ' | missing company name and year for ' + committerCompany + + if errorWithSignature != '': + print(errorWithSignature.rstrip('\n')) + + return issueCounter + +if __name__ == '__main__': + main()
\ No newline at end of file diff --git a/checkstyle/src/main/resources/copyright-template.txt b/checkstyle/src/main/resources/copyright-template.txt new file mode 100644 index 0000000000..205e0caac2 --- /dev/null +++ b/checkstyle/src/main/resources/copyright-template.txt @@ -0,0 +1,16 @@ +============LICENSE_START======================================================= +================================================================================ +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +SPDX-License-Identifier: Apache-2.0 +============LICENSE_END========================================================= diff --git a/checkstyle/src/main/resources/ignore-files-config.csv b/checkstyle/src/main/resources/ignore-files-config.csv new file mode 100644 index 0000000000..4f7394fbb5 --- /dev/null +++ b/checkstyle/src/main/resources/ignore-files-config.csv @@ -0,0 +1,6 @@ +file path +*checkstyle/* +*.json +*.yang +*.rst +*.csv
\ No newline at end of file diff --git a/checkstyle/src/main/resources/project-committers-config.csv b/checkstyle/src/main/resources/project-committers-config.csv new file mode 100644 index 0000000000..85ee43bdab --- /dev/null +++ b/checkstyle/src/main/resources/project-committers-config.csv @@ -0,0 +1,3 @@ +email,signature +@est.tech,Nordix Foundation +@bell.ca,Bell Canada
\ No newline at end of file diff --git a/checkstyle/src/main/test_CopyrightCheck.py b/checkstyle/src/main/test_CopyrightCheck.py new file mode 100644 index 0000000000..177f9d4a51 --- /dev/null +++ b/checkstyle/src/main/test_CopyrightCheck.py @@ -0,0 +1,441 @@ +# ============LICENSE_START======================================================= +# Copyright (C) 2022 Nordix Foundation +# ================================================================================ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +import datetime +import sys +import unittest +from unittest import mock +from unittest.mock import MagicMock +import io + +import CopyrightCheck + +BANNER = '=' * 120 + +def MockStdout(command): + mock_stdout = MagicMock() + mock_stdout.configure_mock(**{"stdout.decode.return_value": command}) + return mock_stdout + +class TestCopyrightCheck(unittest.TestCase): + + @mock.patch('subprocess.run') + def test_PermissionsCheckFalse(self, mock_subprocess_run): + mock_subprocess_run.return_value = MockStdout('Permission denied') + + capturedOutput = io.StringIO() + sys.stdout = capturedOutput # Capture output to stdout + with self.assertRaises(SystemExit): + CopyrightCheck.PermissionsCheck() + sys.stdout = sys.__stdout__ + + self.assertEqual(capturedOutput.getvalue(), + 'Error, I may not have the necessary permissions. Exiting...\n' + BANNER + '\n') + + @mock.patch('subprocess.run') + def test_PermissionsCheckTrue(self, mock_subprocess_run): + mock_subprocess_run.return_value = MockStdout( + 'usage: git [--version] [--help] [-C <path>] [-c <name>=<value>]...') + CopyrightCheck.PermissionsCheck() # Assert no error thrown + + @mock.patch('CopyrightCheck.GetIgnoredFiles') + @mock.patch('subprocess.run') + def test_FindAlteredFiles(self, mock_subprocess_run, mock_GetIgnoredFiles): + mock_GetIgnoredFiles.return_value = ['.*.json', 'dir/.*'] + mock_subprocess_run.return_value = MockStdout('File1.json\nFile2.java\nFile2.java\ndir/File3.java') + result = CopyrightCheck.FindAlteredFiles() + # Duplicates, .json and files in 'dir' removed + self.assertEqual(result, ['File2.java']) + + @mock.patch('CopyrightCheck.GetIgnoredFiles') + @mock.patch('subprocess.run') + def test_FindAlteredFilesWithNoFileChanges(self, mock_subprocess_run, mock_GetIgnoredFiles): + mock_GetIgnoredFiles.return_value = ['.*.json', 'dir/.*'] + mock_subprocess_run.return_value = MockStdout('File1.json\ndir/File3.java') + capturedOutput = io.StringIO() + sys.stdout = capturedOutput # Capture output to stdout + result = CopyrightCheck.FindAlteredFiles() + sys.stdout = sys.__stdout__ + + self.assertEqual(result, []) + self.assertEqual(capturedOutput.getvalue(), '') + + @mock.patch('subprocess.run') + def test_GetCommitterEmailExtension(self, mock_subprocess_run): + mock_subprocess_run.return_value = MockStdout('a.committer.name@address.com') + result = CopyrightCheck.GetCommitterEmailExtension() + self.assertEqual(result, '@address.com') + + def test_ReadProjectCommittersConfigFile(self): + mock_open = mock.mock_open(read_data="email,signature\n@address.com,Company Name") + with mock.patch('builtins.open', mock_open): + result = CopyrightCheck.ReadProjectCommittersConfigFile() + self.assertEqual(result, {'@address.com': 'Company Name'}) + + @mock.patch('CopyrightCheck.open') + def test_ReadProjectCommittersConfigFileError(self, mock_OpenFile): + mock_OpenFile.side_effect = FileNotFoundError + capturedOutput = io.StringIO() + sys.stdout = capturedOutput # Capture output to stdout + with self.assertRaises(SystemExit): + CopyrightCheck.ReadProjectCommittersConfigFile() + sys.stdout = sys.__stdout__ + expectedOutput = ('Unable to open Project Committers Config File, have the command line arguments been set?\n' + + BANNER + '\n') + self.assertEqual(capturedOutput.getvalue(), expectedOutput) + + def test_CheckCommitterInConfigFileTrue(self): + committerEmailExtension = '@address.com' + projectCommitters = {'@address.com': 'Company Name'} + capturedOutput = io.StringIO() + sys.stdout = capturedOutput # Capture output to stdout + result = CopyrightCheck.CheckCommitterInConfigFile(committerEmailExtension, projectCommitters) + sys.stdout = sys.__stdout__ + self.assertTrue(result) + self.assertEqual(capturedOutput.getvalue(), "") + + def test_CheckCommitterInConfigFileFalse(self): + committerEmailExtension = '@address.com' + projectCommitters = {'@anotheraddress.com': 'Another Company Name'} + capturedOutput = io.StringIO() + sys.stdout = capturedOutput # Capture output to stdout + with self.assertRaises(SystemExit): + CopyrightCheck.CheckCommitterInConfigFile(committerEmailExtension, projectCommitters) + sys.stdout = sys.__stdout__ + expectedOutput = ('Error, Committer email is not included in config file.\n' + + 'If your company is new to the project please make appropriate changes to project-committers-config.csv\n' + + 'for Copyright Check to work.\n' + + 'Exiting...\n' + BANNER + '\n') + self.assertEqual(capturedOutput.getvalue(), expectedOutput) + + def test_GetIgnoredFiles(self): + mock_open = mock.mock_open(read_data="file path\n*checkstyle/*\n*.json") + with mock.patch('builtins.open', mock_open): + result = CopyrightCheck.GetIgnoredFiles() + self.assertEqual(result, [".*checkstyle/.*", ".*.json"]) + + @mock.patch('CopyrightCheck.open') + def test_GetIgnoredFilesError(self, mock_OpenFile): + mock_OpenFile.side_effect = FileNotFoundError + capturedOutput = io.StringIO() + sys.stdout = capturedOutput # Capture output to stdout + with self.assertRaises(SystemExit): + CopyrightCheck.GetIgnoredFiles() + sys.stdout = sys.__stdout__ + expectedOutput = ('Unable to open File Ignore Config File, have the command line arguments been set?\n' + + BANNER + '\n') + self.assertEqual(capturedOutput.getvalue(), expectedOutput) + + def test_GetCopyrightTemplate(self): + mock_open = mock.mock_open(read_data="****\nThis is a\nCopyright File\n****") + with mock.patch('builtins.open', mock_open): + result = CopyrightCheck.GetCopyrightTemplate() + self.assertEqual(result, ["****\n", "This is a\n", "Copyright File\n", "****"]) + + @mock.patch('CopyrightCheck.open') + def test_GetCopyrightTemplateError(self, mock_OpenFile): + mock_OpenFile.side_effect = FileNotFoundError + capturedOutput = io.StringIO() + sys.stdout = capturedOutput # Capture output to stdout + with self.assertRaises(SystemExit): + CopyrightCheck.GetCopyrightTemplate() + sys.stdout = sys.__stdout__ + expectedOutput = ('Unable to open Template Copyright File, have the command line arguments been set?\n' + + BANNER + '\n') + self.assertEqual(capturedOutput.getvalue(), expectedOutput) + + @mock.patch('subprocess.run') + def test_GetProjectRootDir(self, mock_subprocess_run): + mock_subprocess_run.return_value = MockStdout('project/root/dir\n') + result = CopyrightCheck.GetProjectRootDir() + self.assertEqual(result, 'project/root/dir/') + + + def test_ParseFileCopyright(self): + readFromFile = ["#Before lines will not be included\n", + "#===LICENSE_START===\n", + "#Copyright (C) 0000 Some Company\n", + "#A line without signature\n", + "#===============================\n", + "#This is the start of the Copyright\n", + "#===LICENSE_END===\n", + "After lines will not be included"] + copyright, signatures = CopyrightCheck.ParseFileCopyright(readFromFile) + self.assertEqual(copyright, {2: "#===LICENSE_START===\n", + 5: "#===============================\n", + 6: "#This is the start of the Copyright\n", + 7: "#===LICENSE_END===\n"}) + self.assertEqual(signatures, {3: "#Copyright (C) 0000 Some Company\n", + 4: "#A line without signature\n"}) + + def test_ParseFileCopyrightNoCopyright(self): + fileObject = io.StringIO("#This is not\na copyright\n") + fileObject.name = 'some/file/name' + + capturedOutput = io.StringIO() + sys.stdout = capturedOutput # Capture output to stdout + copyright, signatures = CopyrightCheck.ParseFileCopyright(fileObject) + sys.stdout = sys.__stdout__ + + self.assertEqual(copyright, {}) + self.assertEqual(signatures, {}) + self.assertEqual(capturedOutput.getvalue(), 'some/file/name | no copyright found\n') + + def test_RemoveCommentBlock(self): + commentCharactersList = ['# ', '* ', '# ', '* '] + + for commentCharacters in commentCharactersList: + copyright = {1: commentCharacters + '===LICENSE_START===\n', + 2: '\n', + 3: commentCharacters + 'This is the License\n', + 4: commentCharacters + '===LICENSE_END===\n'} + result = CopyrightCheck.RemoveCommentBlock(copyright) + self.assertEqual(result, {1: '===LICENSE_START===\n', + 2: '\n', + 3: 'This is the License\n', + 4: '===LICENSE_END===\n'}) + + @mock.patch('CopyrightCheck.open') + @mock.patch('CopyrightCheck.GetProjectRootDir') + @mock.patch('CopyrightCheck.GetCopyrightTemplate') + def test_CheckCopyrightForFileNotFound(self, mock_GetCopyrightTemplate, mock_GetProjectRootDir, mock_OpenFile): + mock_GetCopyrightTemplate.return_value = 'some-copyright-template' + mock_GetProjectRootDir.return_value = 'some/project/root/dir/' + mock_OpenFile.side_effect = FileNotFoundError + + capturedOutput = io.StringIO() + sys.stdout = capturedOutput # Capture output to stdout + result = CopyrightCheck.CheckCopyrightForFiles(['some-file.java'], {}, []) + sys.stdout = sys.__stdout__ + + self.assertEqual(capturedOutput.getvalue(), 'Unable to find file some/project/root/dir/some-file.java\n') + self.assertEqual(result, 1) + + @mock.patch('CopyrightCheck.ParseFileCopyright') + @mock.patch('CopyrightCheck.GetProjectRootDir') + @mock.patch('CopyrightCheck.GetCopyrightTemplate') + def test_CheckCopyrightForFileWithNoCopyright(self, mock_GetCopyrightTemplate, mock_GetProjectRootDir, + mock_ParseFileCopyright): + mock_GetCopyrightTemplate.return_value = 'some-copyright-template' + mock_GetProjectRootDir.return_value = 'some/project/root/dir/' + mock_ParseFileCopyright.return_value = ({}, {}) + mock_open = mock.mock_open(read_data="some-file-content") + + capturedOutput = io.StringIO() + sys.stdout = capturedOutput # Capture output to stdout + with mock.patch('builtins.open', mock_open): + result = CopyrightCheck.CheckCopyrightForFiles(['some-file.java'], {}, []) + sys.stdout = sys.__stdout__ + + self.assertEqual(capturedOutput.getvalue(), "") + self.assertEqual(result, 1) + + + @mock.patch('CopyrightCheck.CheckCopyrightSignature') + @mock.patch('CopyrightCheck.CheckCopyrightFormat') + @mock.patch('CopyrightCheck.ParseFileCopyright') + @mock.patch('CopyrightCheck.GetProjectRootDir') + @mock.patch('CopyrightCheck.GetCopyrightTemplate') + def test_CheckCopyrightForFilesWhichAreRight(self, mock_GetCopyrightTemplate, mock_GetProjectRootDir, + mock_ParseFileCopyright, mock_CheckCopyrightFormat, + mock_CheckCopyrightSignature): + mock_GetCopyrightTemplate.return_value = 'some-copyright-template' + mock_GetProjectRootDir.return_value = 'some/project/root/dir/' + mock_ParseFileCopyright.return_value = ({1: '# =some-copyright-line'}, {2: '# =some-signature-line'}) + mock_open = mock.mock_open(read_data="# =some-file-content") + mock_CheckCopyrightFormat.return_value = 0 + mock_CheckCopyrightSignature.return_value = 0 + + capturedOutput = io.StringIO() + sys.stdout = capturedOutput # Capture output to stdout + with mock.patch('builtins.open', mock_open): + result = CopyrightCheck.CheckCopyrightForFiles(['some-file.java', 'another-file.java'], {'@address.com': 'Some Company'}, '@address.com') + sys.stdout = sys.__stdout__ + self.assertEqual(result, 0) + self.assertEqual(capturedOutput.getvalue(), "") + + mock_GetCopyrightTemplate.assert_called_once_with() + mock_GetProjectRootDir.assert_called_once_with() + self.assertEqual(mock_ParseFileCopyright.call_count, 2) + mock_CheckCopyrightFormat.assert_has_calls([ + mock.call({1: '=some-copyright-line'}, 'some-copyright-template', 'some/project/root/dir/some-file.java'), + mock.call({1: '=some-copyright-line'}, 'some-copyright-template', 'some/project/root/dir/another-file.java') + ]) + mock_CheckCopyrightSignature.assert_has_calls([ + mock.call({2: '# =some-signature-line'}, 'Some Company', 'some/project/root/dir/some-file.java'), + mock.call({2: '# =some-signature-line'}, 'Some Company', 'some/project/root/dir/another-file.java') + ]) + self.assertEqual(mock_CheckCopyrightFormat.call_count, 2) + self.assertEqual(mock_CheckCopyrightSignature.call_count, 2) + + + def test_CheckCopyrightFormatWhichIsWrong(self): + fileCopyright = {1: '---LICENSE_START---\n', + 2: 'This is the license typo\n', + 3: '', + 4: '===license_end===\n'} + templateCopyright = ['===LICENSE_START===\n', + 'This is the license\n', + '\n', + '===LICENSE_END===\n'] + + capturedOutput = io.StringIO() + sys.stdout = capturedOutput # Capture output to stdout + result = CopyrightCheck.CheckCopyrightFormat(fileCopyright, templateCopyright, 'some/file/path') + sys.stdout = sys.__stdout__ + + expectedOutput = ("some/file/path | line 1 read \t '---LICENSE_START---\\n'\n" + + "some/file/path | line 1 expected '===LICENSE_START===\\n'\n" + + "some/file/path | line 2 read \t 'This is the license typo\\n'\n" + + "some/file/path | line 2 expected 'This is the license\\n'\n" + + "some/file/path | line 3 read \t ''\n" + + "some/file/path | line 3 expected '\\n'\n" + + "some/file/path | line 4 read \t '===license_end===\\n'\n" + + "some/file/path | line 4 expected '===LICENSE_END===\\n'\n") + + self.assertEqual(capturedOutput.getvalue(), expectedOutput) + self.assertEqual(result, 4) + + def test_CheckCopyrightFormatWhichIsCorrect(self): + fileCopyright = {1: '===LICENSE_START===\n', + 2: 'This is the license\n', + 3: '\n', + 4: '===LICENSE_END===\n'} + templateCopyright = ['===LICENSE_START===\n', + 'This is the license\n', + '\n', + '===LICENSE_END===\n'] + + capturedOutput = io.StringIO() + sys.stdout = capturedOutput # Capture output to stdout + result = CopyrightCheck.CheckCopyrightFormat(fileCopyright, templateCopyright, 'some/file/path') + sys.stdout = sys.__stdout__ + + self.assertEqual(capturedOutput.getvalue(), "") + self.assertEqual(result, 0) + + def test_CheckCopyrightSignatureWhichIsWrong(self): + fileSignatures = {1: "Trigger expected Copy-right", + 2: "Trigger expected Mod Copy-right"} + capturedOutput = io.StringIO() + sys.stdout = capturedOutput # Capture output to stdout + result = CopyrightCheck.CheckCopyrightSignature(fileSignatures, 'Some-Company', 'some/file/path') + sys.stdout = sys.__stdout__ + + expectedOutput = ("some/file/path | line 1 expected Copyright\n" + + "some/file/path | line 2 expected Modifications Copyright\n" + + "some/file/path | missing company name and year for Some-Company\n") + + self.assertEqual(capturedOutput.getvalue(), expectedOutput) + self.assertEqual(result, 3) + + def test_CheckCopyrightSignatureWhichHasWrongYear(self): + currentYear = datetime.date.today().year + fileSignatures = {1: "Copyright (C) 1999 Some-Company"} + + capturedOutput = io.StringIO() + sys.stdout = capturedOutput # Capture output to stdout + result = CopyrightCheck.CheckCopyrightSignature(fileSignatures, 'Some-Company', 'some/file/path') + sys.stdout = sys.__stdout__ + + self.assertEqual(capturedOutput.getvalue(), + "some/file/path | line 1 update year to include " + str(currentYear) + "\n") + self.assertEqual(result, 1) + + def test_CheckCopyrightSignatureWhichIsRight(self): + currentYear = datetime.date.today().year + fileSignatures = {1: "Copyright (C) " + str(currentYear) + " Some-Company"} + + capturedOutput = io.StringIO() + sys.stdout = capturedOutput # Capture output to stdout + result = CopyrightCheck.CheckCopyrightSignature(fileSignatures, 'Some-Company', 'some/file/path') + sys.stdout = sys.__stdout__ + + self.assertEqual(capturedOutput.getvalue(), "") + self.assertEqual(result, 0) + + @mock.patch('CopyrightCheck.CheckCopyrightForFiles') + @mock.patch('CopyrightCheck.FindAlteredFiles') + @mock.patch('CopyrightCheck.CheckCommitterInConfigFile') + @mock.patch('CopyrightCheck.ReadProjectCommittersConfigFile') + @mock.patch('CopyrightCheck.GetCommitterEmailExtension') + @mock.patch('CopyrightCheck.PermissionsCheck') + def test_Main(self, mock_PermissionsCheck, mock_GetCommitterEmailExtension, mock_ReadProjectCommittersConfigFile, + mock_CheckCommitterInConfigFile, mock_FindAlteredFiles, mock_CheckCopyrightForFiles): + + mock_GetCommitterEmailExtension.return_value = '@address.com' + mock_ReadProjectCommittersConfigFile.return_value = {'@address.com', 'Some Company'} + mock_FindAlteredFiles.return_value = ['some-file.java'] + mock_CheckCopyrightForFiles.return_value = 5 + + capturedOutput = io.StringIO() + sys.stdout = capturedOutput # Capture output to stdout + + CopyrightCheck.main() + + sys.stdout = sys.__stdout__ + + expectedOutput = (BANNER + '\nCopyright Check Python Script:\n' + + '5 issue(s) found after 1 altered file(s) checked\n' + + BANNER + '\n') + + self.assertEqual(capturedOutput.getvalue(), expectedOutput) + + mock_PermissionsCheck.assert_called_once_with() + mock_GetCommitterEmailExtension.assert_called_once_with() + mock_ReadProjectCommittersConfigFile.assert_called_once_with() + mock_CheckCommitterInConfigFile.assert_called_once_with('@address.com', {'@address.com', 'Some Company'}) + mock_FindAlteredFiles.assert_called_once_with() + mock_CheckCopyrightForFiles.assert_called_once_with(['some-file.java'], {'@address.com', 'Some Company'}, '@address.com') + + @mock.patch('CopyrightCheck.CheckCopyrightForFiles') + @mock.patch('CopyrightCheck.FindAlteredFiles') + @mock.patch('CopyrightCheck.CheckCommitterInConfigFile') + @mock.patch('CopyrightCheck.ReadProjectCommittersConfigFile') + @mock.patch('CopyrightCheck.GetCommitterEmailExtension') + @mock.patch('CopyrightCheck.PermissionsCheck') + def test_MainNoFiles(self, mock_PermissionsCheck, mock_GetCommitterEmailExtension, mock_ReadProjectCommittersConfigFile, + mock_CheckCommitterInConfigFile, mock_FindAlteredFiles, mock_CheckCopyrightForFiles): + + mock_GetCommitterEmailExtension.return_value = '@address.com' + mock_ReadProjectCommittersConfigFile.return_value = {'@address.com', 'Some Company'} + mock_FindAlteredFiles.return_value = [] + + capturedOutput = io.StringIO() + sys.stdout = capturedOutput # Capture output to stdout + + CopyrightCheck.main() + + sys.stdout = sys.__stdout__ + + expectedOutput = (BANNER + '\nCopyright Check Python Script:\n' + + '0 issue(s) found after 0 altered file(s) checked\n' + + BANNER + '\n') + + self.assertEqual(capturedOutput.getvalue(), expectedOutput) + + mock_PermissionsCheck.assert_called_once_with() + mock_GetCommitterEmailExtension.assert_called_once_with() + mock_ReadProjectCommittersConfigFile.assert_called_once_with() + mock_CheckCommitterInConfigFile.assert_called_once_with('@address.com', {'@address.com', 'Some Company'}) + mock_FindAlteredFiles.assert_called_once_with() + mock_CheckCopyrightForFiles.assert_not_called() + + +if __name__ == '__main__': + unittest.main() |