1#!/usr/bin/env python3
2# Copyright 2022 The Android Open Source Project
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"""Unittests for clang-format."""
17
18import contextlib
19from pathlib import Path
20import sys
21import tempfile
22import unittest
23
24
25DIR = Path(__file__).resolve().parent
26sys.path.insert(0, str(DIR.parent))
27
28# We have to import our local modules after the sys.path tweak.  We can't use
29# relative imports because this is an executable program, not a module.
30# pylint: disable=wrong-import-position,import-error
31import rh.utils
32
33
34CLANG_FORMAT = DIR / 'clang-format.py'
35
36
37@contextlib.contextmanager
38def git_clang_format(data: str):
39    """Create a fake git-clang-format script."""
40    with tempfile.TemporaryDirectory(prefix='repohooks-tests') as tempdir:
41        tempdir = Path(tempdir)
42        script = tempdir / 'git-clang-format-fake.sh'
43        script.write_text(f'#!/bin/sh\n{data}', encoding='utf-8')
44        script.chmod(0o755)
45        yield script
46
47
48def run_clang_format(script, args, **kwargs):
49    """Helper to run clang-format.py with fake git-clang-format script."""
50    kwargs.setdefault('capture_output', True)
51    return rh.utils.run(
52        [CLANG_FORMAT, '--git-clang-format', script] + args, **kwargs)
53
54
55class GitClangFormatExit(unittest.TestCase):
56    """Test git-clang-format parsing."""
57
58    def test_diff_exit_0_no_output(self):
59        """Test exit 0 w/no output."""
60        with git_clang_format('exit 0') as script:
61            result = run_clang_format(script, ['--working-tree'])
62            self.assertEqual(result.stdout, '')
63
64    def test_diff_exit_0_stderr(self):
65        """Test exit 0 w/stderr output."""
66        with git_clang_format('echo bad >&2; exit 0') as script:
67            with self.assertRaises(rh.utils.CalledProcessError) as e:
68                run_clang_format(script, ['--working-tree'])
69            self.assertIn('clang-format failed', e.exception.stderr)
70
71    def test_diff_exit_1_no_output(self):
72        """Test exit 1 w/no output."""
73        with git_clang_format('exit 1') as script:
74            result = run_clang_format(script, ['--working-tree'])
75            self.assertEqual(result.stdout, '')
76
77    def test_diff_exit_1_output(self):
78        """Test exit 1 with output."""
79        with git_clang_format('echo bad; exit 1') as script:
80            with self.assertRaises(rh.utils.CalledProcessError) as e:
81                run_clang_format(script, ['--working-tree'])
82            self.assertIn('clang-format failed', e.exception.stderr)
83
84    def test_diff_exit_1_stderr(self):
85        """Test exit 1 w/stderr."""
86        with git_clang_format('echo bad >&2; exit 1') as script:
87            with self.assertRaises(rh.utils.CalledProcessError) as e:
88                run_clang_format(script, ['--working-tree'])
89            self.assertIn('clang-format failed', e.exception.stderr)
90
91    def test_diff_exit_2(self):
92        """Test exit 2."""
93        with git_clang_format('exit 2') as script:
94            with self.assertRaises(rh.utils.CalledProcessError) as e:
95                run_clang_format(script, ['--working-tree'])
96            self.assertIn('clang-format failed', e.exception.stderr)
97
98    def test_fix_exit_1_output(self):
99        """Test fix with incorrect patch syntax."""
100        with git_clang_format('echo bad patch; exit 1') as script:
101            with self.assertRaises(rh.utils.CalledProcessError) as e:
102                run_clang_format(script, ['--working-tree', '--fix'])
103            self.assertIn('Error: Unable to automatically fix things',
104                          e.exception.stderr)
105
106
107if __name__ == '__main__':
108    unittest.main()
109