summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorlukegleeson <luke.gleeson@est.tech>2022-03-08 11:41:52 +0000
committerlukegleeson <luke.gleeson@est.tech>2022-03-25 17:03:21 +0000
commit165e3b8bcc492ebe31431f30c14d5dc98bb7f18e (patch)
tree7e55fa1b817ad6820d26b8008f05f6208da5d12a
parent06b6584a741b565922b32bb7e861ae4d16854673 (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.xml54
-rw-r--r--checkstyle/src/main/CopyrightCheck.py262
-rw-r--r--checkstyle/src/main/resources/copyright-template.txt16
-rw-r--r--checkstyle/src/main/resources/ignore-files-config.csv6
-rw-r--r--checkstyle/src/main/resources/project-committers-config.csv3
-rw-r--r--checkstyle/src/main/test_CopyrightCheck.py441
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()