blob: 177f9d4a5126d08ed6b4dde4e77fd6cd434714ac [file] [log] [blame]
lukegleeson165e3b82022-03-08 11:41:52 +00001# ============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
19import datetime
20import sys
21import unittest
22from unittest import mock
23from unittest.mock import MagicMock
24import io
25
26import CopyrightCheck
27
28BANNER = '=' * 120
29
30def MockStdout(command):
31 mock_stdout = MagicMock()
32 mock_stdout.configure_mock(**{"stdout.decode.return_value": command})
33 return mock_stdout
34
35class 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
440if __name__ == '__main__':
441 unittest.main()