Copyright Check Script
[cps.git] / checkstyle / src / main / test_CopyrightCheck.py
1 #  ============LICENSE_START=======================================================
2 #  Copyright (C) 2022 Nordix Foundation
3 #  ================================================================================
4 #  Licensed under the Apache License, Version 2.0 (the "License");
5 #  you may not use this file except in compliance with the License.
6 #  You may obtain a copy of the License at
7 #
8 #        http://www.apache.org/licenses/LICENSE-2.0
9 #
10 #  Unless required by applicable law or agreed to in writing, software
11 #  distributed under the License is distributed on an "AS IS" BASIS,
12 #  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 #  See the License for the specific language governing permissions and
14 #  limitations under the License.
15 #
16 #  SPDX-License-Identifier: Apache-2.0
17 #  ============LICENSE_END=========================================================
18
19 import datetime
20 import sys
21 import unittest
22 from unittest import mock
23 from unittest.mock import MagicMock
24 import io
25
26 import CopyrightCheck
27
28 BANNER = '=' * 120
29
30 def MockStdout(command):
31     mock_stdout = MagicMock()
32     mock_stdout.configure_mock(**{"stdout.decode.return_value": command})
33     return mock_stdout
34
35 class TestCopyrightCheck(unittest.TestCase):
36
37     @mock.patch('subprocess.run')
38     def test_PermissionsCheckFalse(self, mock_subprocess_run):
39         mock_subprocess_run.return_value = MockStdout('Permission denied')
40
41         capturedOutput = io.StringIO()
42         sys.stdout = capturedOutput  # Capture output to stdout
43         with self.assertRaises(SystemExit):
44             CopyrightCheck.PermissionsCheck()
45         sys.stdout = sys.__stdout__
46
47         self.assertEqual(capturedOutput.getvalue(),
48                          'Error, I may not have the necessary permissions. Exiting...\n' + BANNER + '\n')
49
50     @mock.patch('subprocess.run')
51     def test_PermissionsCheckTrue(self, mock_subprocess_run):
52         mock_subprocess_run.return_value = MockStdout(
53             'usage: git [--version] [--help] [-C <path>] [-c <name>=<value>]...')
54         CopyrightCheck.PermissionsCheck()   # Assert no error thrown
55
56     @mock.patch('CopyrightCheck.GetIgnoredFiles')
57     @mock.patch('subprocess.run')
58     def test_FindAlteredFiles(self, mock_subprocess_run, mock_GetIgnoredFiles):
59         mock_GetIgnoredFiles.return_value = ['.*.json', 'dir/.*']
60         mock_subprocess_run.return_value = MockStdout('File1.json\nFile2.java\nFile2.java\ndir/File3.java')
61         result = CopyrightCheck.FindAlteredFiles()
62         # Duplicates, .json and files in 'dir' removed
63         self.assertEqual(result, ['File2.java'])
64
65     @mock.patch('CopyrightCheck.GetIgnoredFiles')
66     @mock.patch('subprocess.run')
67     def test_FindAlteredFilesWithNoFileChanges(self, mock_subprocess_run, mock_GetIgnoredFiles):
68         mock_GetIgnoredFiles.return_value = ['.*.json', 'dir/.*']
69         mock_subprocess_run.return_value = MockStdout('File1.json\ndir/File3.java')
70         capturedOutput = io.StringIO()
71         sys.stdout = capturedOutput  # Capture output to stdout
72         result = CopyrightCheck.FindAlteredFiles()
73         sys.stdout = sys.__stdout__
74
75         self.assertEqual(result, [])
76         self.assertEqual(capturedOutput.getvalue(), '')
77
78     @mock.patch('subprocess.run')
79     def test_GetCommitterEmailExtension(self, mock_subprocess_run):
80         mock_subprocess_run.return_value = MockStdout('a.committer.name@address.com')
81         result = CopyrightCheck.GetCommitterEmailExtension()
82         self.assertEqual(result, '@address.com')
83
84     def test_ReadProjectCommittersConfigFile(self):
85         mock_open = mock.mock_open(read_data="email,signature\n@address.com,Company Name")
86         with mock.patch('builtins.open', mock_open):
87             result = CopyrightCheck.ReadProjectCommittersConfigFile()
88         self.assertEqual(result, {'@address.com': 'Company Name'})
89
90     @mock.patch('CopyrightCheck.open')
91     def test_ReadProjectCommittersConfigFileError(self, mock_OpenFile):
92         mock_OpenFile.side_effect = FileNotFoundError
93         capturedOutput = io.StringIO()
94         sys.stdout = capturedOutput  # Capture output to stdout
95         with self.assertRaises(SystemExit):
96             CopyrightCheck.ReadProjectCommittersConfigFile()
97         sys.stdout = sys.__stdout__
98         expectedOutput = ('Unable to open Project Committers Config File, have the command line arguments been set?\n' +
99                           BANNER + '\n')
100         self.assertEqual(capturedOutput.getvalue(), expectedOutput)
101
102     def test_CheckCommitterInConfigFileTrue(self):
103         committerEmailExtension = '@address.com'
104         projectCommitters = {'@address.com': 'Company Name'}
105         capturedOutput = io.StringIO()
106         sys.stdout = capturedOutput  # Capture output to stdout
107         result = CopyrightCheck.CheckCommitterInConfigFile(committerEmailExtension, projectCommitters)
108         sys.stdout = sys.__stdout__
109         self.assertTrue(result)
110         self.assertEqual(capturedOutput.getvalue(), "")
111
112     def test_CheckCommitterInConfigFileFalse(self):
113         committerEmailExtension = '@address.com'
114         projectCommitters = {'@anotheraddress.com': 'Another Company Name'}
115         capturedOutput = io.StringIO()
116         sys.stdout = capturedOutput  # Capture output to stdout
117         with self.assertRaises(SystemExit):
118             CopyrightCheck.CheckCommitterInConfigFile(committerEmailExtension, projectCommitters)
119         sys.stdout = sys.__stdout__
120         expectedOutput = ('Error, Committer email is not included in config file.\n' +
121                           'If your company is new to the project please make appropriate changes to project-committers-config.csv\n' +
122                           'for Copyright Check to work.\n' +
123                           'Exiting...\n' + BANNER + '\n')
124         self.assertEqual(capturedOutput.getvalue(), expectedOutput)
125
126     def test_GetIgnoredFiles(self):
127         mock_open = mock.mock_open(read_data="file path\n*checkstyle/*\n*.json")
128         with mock.patch('builtins.open', mock_open):
129             result = CopyrightCheck.GetIgnoredFiles()
130         self.assertEqual(result, [".*checkstyle/.*", ".*.json"])
131
132     @mock.patch('CopyrightCheck.open')
133     def test_GetIgnoredFilesError(self, mock_OpenFile):
134         mock_OpenFile.side_effect = FileNotFoundError
135         capturedOutput = io.StringIO()
136         sys.stdout = capturedOutput  # Capture output to stdout
137         with self.assertRaises(SystemExit):
138             CopyrightCheck.GetIgnoredFiles()
139         sys.stdout = sys.__stdout__
140         expectedOutput = ('Unable to open File Ignore Config File, have the command line arguments been set?\n' +
141                           BANNER + '\n')
142         self.assertEqual(capturedOutput.getvalue(), expectedOutput)
143
144     def test_GetCopyrightTemplate(self):
145         mock_open = mock.mock_open(read_data="****\nThis is a\nCopyright File\n****")
146         with mock.patch('builtins.open', mock_open):
147             result = CopyrightCheck.GetCopyrightTemplate()
148         self.assertEqual(result, ["****\n", "This is a\n", "Copyright File\n", "****"])
149
150     @mock.patch('CopyrightCheck.open')
151     def test_GetCopyrightTemplateError(self, mock_OpenFile):
152         mock_OpenFile.side_effect = FileNotFoundError
153         capturedOutput = io.StringIO()
154         sys.stdout = capturedOutput  # Capture output to stdout
155         with self.assertRaises(SystemExit):
156             CopyrightCheck.GetCopyrightTemplate()
157         sys.stdout = sys.__stdout__
158         expectedOutput = ('Unable to open Template Copyright File, have the command line arguments been set?\n' +
159                           BANNER + '\n')
160         self.assertEqual(capturedOutput.getvalue(), expectedOutput)
161
162     @mock.patch('subprocess.run')
163     def test_GetProjectRootDir(self, mock_subprocess_run):
164         mock_subprocess_run.return_value = MockStdout('project/root/dir\n')
165         result = CopyrightCheck.GetProjectRootDir()
166         self.assertEqual(result, 'project/root/dir/')
167
168
169     def test_ParseFileCopyright(self):
170         readFromFile = ["#Before lines will not be included\n",
171                         "#===LICENSE_START===\n",
172                         "#Copyright (C) 0000 Some Company\n",
173                         "#A line without signature\n",
174                         "#===============================\n",
175                         "#This is the start of the Copyright\n",
176                         "#===LICENSE_END===\n",
177                         "After lines will not be included"]
178         copyright, signatures = CopyrightCheck.ParseFileCopyright(readFromFile)
179         self.assertEqual(copyright, {2: "#===LICENSE_START===\n",
180                                      5: "#===============================\n",
181                                      6: "#This is the start of the Copyright\n",
182                                      7: "#===LICENSE_END===\n"})
183         self.assertEqual(signatures, {3: "#Copyright (C) 0000 Some Company\n",
184                                       4: "#A line without signature\n"})
185
186     def test_ParseFileCopyrightNoCopyright(self):
187         fileObject = io.StringIO("#This is not\na copyright\n")
188         fileObject.name = 'some/file/name'
189
190         capturedOutput = io.StringIO()
191         sys.stdout = capturedOutput  # Capture output to stdout
192         copyright, signatures = CopyrightCheck.ParseFileCopyright(fileObject)
193         sys.stdout = sys.__stdout__
194
195         self.assertEqual(copyright, {})
196         self.assertEqual(signatures, {})
197         self.assertEqual(capturedOutput.getvalue(), 'some/file/name | no copyright found\n')
198
199     def test_RemoveCommentBlock(self):
200         commentCharactersList = ['# ', '* ', '#  ', '*  ']
201
202         for commentCharacters in commentCharactersList:
203             copyright = {1: commentCharacters + '===LICENSE_START===\n',
204                          2: '\n',
205                          3: commentCharacters + 'This is the License\n',
206                          4: commentCharacters + '===LICENSE_END===\n'}
207             result = CopyrightCheck.RemoveCommentBlock(copyright)
208             self.assertEqual(result, {1: '===LICENSE_START===\n',
209                                       2: '\n',
210                                       3: 'This is the License\n',
211                                       4: '===LICENSE_END===\n'})
212
213     @mock.patch('CopyrightCheck.open')
214     @mock.patch('CopyrightCheck.GetProjectRootDir')
215     @mock.patch('CopyrightCheck.GetCopyrightTemplate')
216     def test_CheckCopyrightForFileNotFound(self, mock_GetCopyrightTemplate, mock_GetProjectRootDir, mock_OpenFile):
217         mock_GetCopyrightTemplate.return_value = 'some-copyright-template'
218         mock_GetProjectRootDir.return_value = 'some/project/root/dir/'
219         mock_OpenFile.side_effect = FileNotFoundError
220
221         capturedOutput = io.StringIO()
222         sys.stdout = capturedOutput  # Capture output to stdout
223         result = CopyrightCheck.CheckCopyrightForFiles(['some-file.java'], {}, [])
224         sys.stdout = sys.__stdout__
225
226         self.assertEqual(capturedOutput.getvalue(), 'Unable to find file some/project/root/dir/some-file.java\n')
227         self.assertEqual(result, 1)
228
229     @mock.patch('CopyrightCheck.ParseFileCopyright')
230     @mock.patch('CopyrightCheck.GetProjectRootDir')
231     @mock.patch('CopyrightCheck.GetCopyrightTemplate')
232     def test_CheckCopyrightForFileWithNoCopyright(self, mock_GetCopyrightTemplate, mock_GetProjectRootDir,
233                                                   mock_ParseFileCopyright):
234         mock_GetCopyrightTemplate.return_value = 'some-copyright-template'
235         mock_GetProjectRootDir.return_value = 'some/project/root/dir/'
236         mock_ParseFileCopyright.return_value = ({}, {})
237         mock_open = mock.mock_open(read_data="some-file-content")
238
239         capturedOutput = io.StringIO()
240         sys.stdout = capturedOutput  # Capture output to stdout
241         with mock.patch('builtins.open', mock_open):
242             result = CopyrightCheck.CheckCopyrightForFiles(['some-file.java'], {}, [])
243         sys.stdout = sys.__stdout__
244
245         self.assertEqual(capturedOutput.getvalue(), "")
246         self.assertEqual(result, 1)
247
248
249     @mock.patch('CopyrightCheck.CheckCopyrightSignature')
250     @mock.patch('CopyrightCheck.CheckCopyrightFormat')
251     @mock.patch('CopyrightCheck.ParseFileCopyright')
252     @mock.patch('CopyrightCheck.GetProjectRootDir')
253     @mock.patch('CopyrightCheck.GetCopyrightTemplate')
254     def test_CheckCopyrightForFilesWhichAreRight(self, mock_GetCopyrightTemplate, mock_GetProjectRootDir,
255                                                   mock_ParseFileCopyright, mock_CheckCopyrightFormat,
256                                                   mock_CheckCopyrightSignature):
257         mock_GetCopyrightTemplate.return_value = 'some-copyright-template'
258         mock_GetProjectRootDir.return_value = 'some/project/root/dir/'
259         mock_ParseFileCopyright.return_value = ({1: '# =some-copyright-line'}, {2: '# =some-signature-line'})
260         mock_open = mock.mock_open(read_data="# =some-file-content")
261         mock_CheckCopyrightFormat.return_value = 0
262         mock_CheckCopyrightSignature.return_value = 0
263
264         capturedOutput = io.StringIO()
265         sys.stdout = capturedOutput  # Capture output to stdout
266         with mock.patch('builtins.open', mock_open):
267             result = CopyrightCheck.CheckCopyrightForFiles(['some-file.java', 'another-file.java'], {'@address.com': 'Some Company'}, '@address.com')
268         sys.stdout = sys.__stdout__
269         self.assertEqual(result, 0)
270         self.assertEqual(capturedOutput.getvalue(), "")
271
272         mock_GetCopyrightTemplate.assert_called_once_with()
273         mock_GetProjectRootDir.assert_called_once_with()
274         self.assertEqual(mock_ParseFileCopyright.call_count, 2)
275         mock_CheckCopyrightFormat.assert_has_calls([
276             mock.call({1: '=some-copyright-line'}, 'some-copyright-template', 'some/project/root/dir/some-file.java'),
277             mock.call({1: '=some-copyright-line'}, 'some-copyright-template', 'some/project/root/dir/another-file.java')
278         ])
279         mock_CheckCopyrightSignature.assert_has_calls([
280             mock.call({2: '# =some-signature-line'}, 'Some Company', 'some/project/root/dir/some-file.java'),
281             mock.call({2: '# =some-signature-line'}, 'Some Company', 'some/project/root/dir/another-file.java')
282         ])
283         self.assertEqual(mock_CheckCopyrightFormat.call_count, 2)
284         self.assertEqual(mock_CheckCopyrightSignature.call_count, 2)
285
286
287     def test_CheckCopyrightFormatWhichIsWrong(self):
288         fileCopyright = {1: '---LICENSE_START---\n',
289                          2: 'This is the license typo\n',
290                          3: '',
291                          4: '===license_end===\n'}
292         templateCopyright = ['===LICENSE_START===\n',
293                              'This is the license\n',
294                              '\n',
295                              '===LICENSE_END===\n']
296
297         capturedOutput = io.StringIO()
298         sys.stdout = capturedOutput  # Capture output to stdout
299         result = CopyrightCheck.CheckCopyrightFormat(fileCopyright, templateCopyright, 'some/file/path')
300         sys.stdout = sys.__stdout__
301
302         expectedOutput = ("some/file/path | line  1 read \t  '---LICENSE_START---\\n'\n" +
303                           "some/file/path | line  1 expected '===LICENSE_START===\\n'\n" +
304                           "some/file/path | line  2 read \t  'This is the license typo\\n'\n" +
305                           "some/file/path | line  2 expected 'This is the license\\n'\n" +
306                           "some/file/path | line  3 read \t  ''\n" +
307                           "some/file/path | line  3 expected '\\n'\n" +
308                           "some/file/path | line  4 read \t  '===license_end===\\n'\n" +
309                           "some/file/path | line  4 expected '===LICENSE_END===\\n'\n")
310
311         self.assertEqual(capturedOutput.getvalue(), expectedOutput)
312         self.assertEqual(result, 4)
313
314     def test_CheckCopyrightFormatWhichIsCorrect(self):
315         fileCopyright = {1: '===LICENSE_START===\n',
316                          2: 'This is the license\n',
317                          3: '\n',
318                          4: '===LICENSE_END===\n'}
319         templateCopyright = ['===LICENSE_START===\n',
320                              'This is the license\n',
321                              '\n',
322                              '===LICENSE_END===\n']
323
324         capturedOutput = io.StringIO()
325         sys.stdout = capturedOutput  # Capture output to stdout
326         result = CopyrightCheck.CheckCopyrightFormat(fileCopyright, templateCopyright, 'some/file/path')
327         sys.stdout = sys.__stdout__
328
329         self.assertEqual(capturedOutput.getvalue(), "")
330         self.assertEqual(result, 0)
331
332     def test_CheckCopyrightSignatureWhichIsWrong(self):
333         fileSignatures = {1: "Trigger expected Copy-right",
334                           2: "Trigger expected Mod Copy-right"}
335         capturedOutput = io.StringIO()
336         sys.stdout = capturedOutput  # Capture output to stdout
337         result = CopyrightCheck.CheckCopyrightSignature(fileSignatures, 'Some-Company', 'some/file/path')
338         sys.stdout = sys.__stdout__
339
340         expectedOutput = ("some/file/path | line 1 expected Copyright\n" +
341                           "some/file/path | line 2 expected Modifications Copyright\n" +
342                           "some/file/path | missing company name and year for Some-Company\n")
343
344         self.assertEqual(capturedOutput.getvalue(), expectedOutput)
345         self.assertEqual(result, 3)
346
347     def test_CheckCopyrightSignatureWhichHasWrongYear(self):
348         currentYear = datetime.date.today().year
349         fileSignatures = {1: "Copyright (C) 1999 Some-Company"}
350
351         capturedOutput = io.StringIO()
352         sys.stdout = capturedOutput  # Capture output to stdout
353         result = CopyrightCheck.CheckCopyrightSignature(fileSignatures, 'Some-Company', 'some/file/path')
354         sys.stdout = sys.__stdout__
355
356         self.assertEqual(capturedOutput.getvalue(),
357                          "some/file/path | line 1 update year to include " + str(currentYear) + "\n")
358         self.assertEqual(result, 1)
359
360     def test_CheckCopyrightSignatureWhichIsRight(self):
361         currentYear = datetime.date.today().year
362         fileSignatures = {1: "Copyright (C) " + str(currentYear) + " Some-Company"}
363
364         capturedOutput = io.StringIO()
365         sys.stdout = capturedOutput  # Capture output to stdout
366         result = CopyrightCheck.CheckCopyrightSignature(fileSignatures, 'Some-Company', 'some/file/path')
367         sys.stdout = sys.__stdout__
368
369         self.assertEqual(capturedOutput.getvalue(), "")
370         self.assertEqual(result, 0)
371
372     @mock.patch('CopyrightCheck.CheckCopyrightForFiles')
373     @mock.patch('CopyrightCheck.FindAlteredFiles')
374     @mock.patch('CopyrightCheck.CheckCommitterInConfigFile')
375     @mock.patch('CopyrightCheck.ReadProjectCommittersConfigFile')
376     @mock.patch('CopyrightCheck.GetCommitterEmailExtension')
377     @mock.patch('CopyrightCheck.PermissionsCheck')
378     def test_Main(self, mock_PermissionsCheck, mock_GetCommitterEmailExtension, mock_ReadProjectCommittersConfigFile,
379                   mock_CheckCommitterInConfigFile, mock_FindAlteredFiles, mock_CheckCopyrightForFiles):
380
381         mock_GetCommitterEmailExtension.return_value = '@address.com'
382         mock_ReadProjectCommittersConfigFile.return_value = {'@address.com', 'Some Company'}
383         mock_FindAlteredFiles.return_value = ['some-file.java']
384         mock_CheckCopyrightForFiles.return_value = 5
385
386         capturedOutput = io.StringIO()
387         sys.stdout = capturedOutput  # Capture output to stdout
388
389         CopyrightCheck.main()
390
391         sys.stdout = sys.__stdout__
392
393         expectedOutput = (BANNER + '\nCopyright Check Python Script:\n' +
394                           '5 issue(s) found after 1 altered file(s) checked\n' +
395                           BANNER + '\n')
396
397         self.assertEqual(capturedOutput.getvalue(), expectedOutput)
398
399         mock_PermissionsCheck.assert_called_once_with()
400         mock_GetCommitterEmailExtension.assert_called_once_with()
401         mock_ReadProjectCommittersConfigFile.assert_called_once_with()
402         mock_CheckCommitterInConfigFile.assert_called_once_with('@address.com', {'@address.com', 'Some Company'})
403         mock_FindAlteredFiles.assert_called_once_with()
404         mock_CheckCopyrightForFiles.assert_called_once_with(['some-file.java'], {'@address.com', 'Some Company'}, '@address.com')
405
406     @mock.patch('CopyrightCheck.CheckCopyrightForFiles')
407     @mock.patch('CopyrightCheck.FindAlteredFiles')
408     @mock.patch('CopyrightCheck.CheckCommitterInConfigFile')
409     @mock.patch('CopyrightCheck.ReadProjectCommittersConfigFile')
410     @mock.patch('CopyrightCheck.GetCommitterEmailExtension')
411     @mock.patch('CopyrightCheck.PermissionsCheck')
412     def test_MainNoFiles(self, mock_PermissionsCheck, mock_GetCommitterEmailExtension, mock_ReadProjectCommittersConfigFile,
413                   mock_CheckCommitterInConfigFile, mock_FindAlteredFiles, mock_CheckCopyrightForFiles):
414
415         mock_GetCommitterEmailExtension.return_value = '@address.com'
416         mock_ReadProjectCommittersConfigFile.return_value = {'@address.com', 'Some Company'}
417         mock_FindAlteredFiles.return_value = []
418
419         capturedOutput = io.StringIO()
420         sys.stdout = capturedOutput  # Capture output to stdout
421
422         CopyrightCheck.main()
423
424         sys.stdout = sys.__stdout__
425
426         expectedOutput = (BANNER + '\nCopyright Check Python Script:\n' +
427                           '0 issue(s) found after 0 altered file(s) checked\n' +
428                           BANNER + '\n')
429
430         self.assertEqual(capturedOutput.getvalue(), expectedOutput)
431
432         mock_PermissionsCheck.assert_called_once_with()
433         mock_GetCommitterEmailExtension.assert_called_once_with()
434         mock_ReadProjectCommittersConfigFile.assert_called_once_with()
435         mock_CheckCommitterInConfigFile.assert_called_once_with('@address.com', {'@address.com', 'Some Company'})
436         mock_FindAlteredFiles.assert_called_once_with()
437         mock_CheckCopyrightForFiles.assert_not_called()
438
439
440 if __name__ == '__main__':
441     unittest.main()