Coverage for manila/tests/test_hacking.py: 100%
84 statements
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
1# Copyright 2014 Red Hat, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
15import itertools
16import textwrap
17from unittest import mock
19import ddt
20import pycodestyle
22from manila import test
23from manila.tests.hacking import checks
26@ddt.ddt
27class HackingTestCase(test.TestCase):
28 """Hacking test cases
30 This class tests the hacking checks in manila.tests.hacking.checks by
31 passing strings to the check methods like the pep8/flake8 parser would.
32 The parser loops over each line in the file and then passes the
33 parameters to the check method. The parameter names in the check method
34 dictate what type of object is passed to the check method. The
35 parameter types are::
37 logical_line: A processed line with the following modifications:
38 - Multi-line statements converted to a single line.
39 - Stripped left and right.
40 - Contents of strings replaced with "xxx" of same length.
41 - Comments removed.
42 physical_line: Raw line of text from the input file.
43 lines: a list of the raw lines from the input file
44 tokens: the tokens that contribute to this logical line
45 line_number: line number in the input file
46 total_lines: number of lines in the input file
47 blank_lines: blank lines before this one
48 indent_char: indentation character in this file (" " or "\t")
49 indent_level: indentation (with tabs expanded to multiples of 8)
50 previous_indent_level: indentation on previous line
51 previous_logical: previous logical line
52 filename: Path of the file being run through pep8
54 When running a test on a check method the return will be False/None if
55 there is no violation in the sample input. If there is an error a tuple is
56 returned with a position in the line, and a message. So to check the result
57 just assertTrue if the check is expected to fail and assertFalse if it
58 should pass.
59 """
61 @ddt.data(*itertools.product(
62 ('', '_', '_LE', '_LI', '_LW'),
63 ('audit', 'debug', 'error', 'info', 'warn', 'warning', 'critical',
64 'exception',)))
65 @ddt.unpack
66 def test_no_translate_logs(self, log_marker, log_method):
67 code = "LOG.{0}({1}('foo'))".format(log_method, log_marker)
69 if log_marker:
70 self.assertEqual(1, len(list(checks.no_translate_logs(code))))
71 else:
72 self.assertEqual(0, len(list(checks.no_translate_logs(code))))
74 def test_check_explicit_underscore_import(self):
75 self.assertEqual(1, len(list(checks.check_explicit_underscore_import(
76 "LOG.info(_('My info message'))",
77 "manila/tests/other_files.py"))))
78 self.assertEqual(1, len(list(checks.check_explicit_underscore_import(
79 "msg = _('My message')",
80 "manila/tests/other_files.py"))))
81 self.assertEqual(0, len(list(checks.check_explicit_underscore_import(
82 "from manila.i18n import _",
83 "manila/tests/other_files.py"))))
84 self.assertEqual(0, len(list(checks.check_explicit_underscore_import(
85 "LOG.info(_('My info message'))",
86 "manila/tests/other_files.py"))))
87 self.assertEqual(0, len(list(checks.check_explicit_underscore_import(
88 "msg = _('My message')",
89 "manila/tests/other_files.py"))))
90 self.assertEqual(0, len(list(checks.check_explicit_underscore_import(
91 "from manila.i18n import _LE, _, _LW",
92 "manila/tests/other_files2.py"))))
93 self.assertEqual(0, len(list(checks.check_explicit_underscore_import(
94 "msg = _('My message')",
95 "manila/tests/other_files2.py"))))
96 self.assertEqual(0, len(list(checks.check_explicit_underscore_import(
97 "_ = translations.ugettext",
98 "manila/tests/other_files3.py"))))
99 self.assertEqual(0, len(list(checks.check_explicit_underscore_import(
100 "msg = _('My message')",
101 "manila/tests/other_files3.py"))))
102 # Complete code coverage by falling through all checks
103 self.assertEqual(0, len(list(checks.check_explicit_underscore_import(
104 "LOG.info('My info message')",
105 "manila.tests.unit/other_files4.py"))))
106 self.assertEqual(0, len(list(checks.check_explicit_underscore_import(
107 "from manila.i18n import _LW",
108 "manila.tests.unit/other_files5.py"))))
109 self.assertEqual(1, len(list(checks.check_explicit_underscore_import(
110 "msg = _('My message')",
111 "manila.tests.unit/other_files5.py"))))
113 # We are patching pep8 so that only the check under test is actually
114 # installed.
115 @mock.patch('pycodestyle._checks',
116 {'physical_line': {}, 'logical_line': {}, 'tree': {}})
117 def _run_check(self, code, checker, filename=None):
118 pycodestyle.register_check(checker)
120 lines = textwrap.dedent(code).strip().splitlines(True)
122 checker = pycodestyle.Checker(filename=filename, lines=lines)
123 checker.check_all()
124 checker.report._deferred_print.sort()
125 return checker.report._deferred_print
127 def _assert_has_errors(self, code, checker, expected_errors=None,
128 filename=None):
129 actual_errors = [e[:3] for e in
130 self._run_check(code, checker, filename)]
131 self.assertEqual(expected_errors or [], actual_errors)
133 def _assert_has_no_errors(self, code, checker, filename=None):
134 self._assert_has_errors(code, checker, filename=filename)
136 def test_logging_format_no_tuple_arguments(self):
137 checker = checks.CheckLoggingFormatArgs
138 code = """
139 import logging
140 LOG = logging.getLogger()
141 LOG.info("Message without a second argument.")
142 LOG.critical("Message with %s arguments.", 'two')
143 LOG.debug("Volume %s caught fire and is at %d degrees C and"
144 " climbing.", 'volume1', 500)
145 """
146 self._assert_has_no_errors(code, checker)
148 @ddt.data(*checks.CheckLoggingFormatArgs.LOG_METHODS)
149 def test_logging_with_tuple_argument(self, log_method):
150 checker = checks.CheckLoggingFormatArgs
151 code = """
152 import logging
153 LOG = logging.getLogger()
154 LOG.{0}("Volume %s caught fire and is at %d degrees C and "
155 "climbing.", ('volume1', 500))
156 """
157 self._assert_has_errors(code.format(log_method), checker,
158 expected_errors=[(4, mock.ANY, 'M310')])
160 def test_trans_add(self):
162 checker = checks.CheckForTransAdd
163 code = """
164 def fake_tran(msg):
165 return msg
168 _ = fake_tran
169 _LI = _
170 _LW = _
171 _LE = _
172 _LC = _
175 def f(a, b):
176 msg = _('test') + 'add me'
177 msg = _LI('test') + 'add me'
178 msg = _LW('test') + 'add me'
179 msg = _LE('test') + 'add me'
180 msg = _LC('test') + 'add me'
181 msg = 'add to me' + _('test')
182 return msg
183 """
185 errors = [(13, 10, 'M326'), (14, 10, 'M326'), (15, 10, 'M326'),
186 (16, 10, 'M326'), (17, 10, 'M326'), (18, 24, 'M326')]
187 self._assert_has_errors(code, checker, expected_errors=errors)
189 code = """
190 def f(a, b):
191 msg = 'test' + 'add me'
192 return msg
193 """
194 errors = []
195 self._assert_has_errors(code, checker, expected_errors=errors)
197 def test_dict_constructor_with_list_copy(self):
198 self.assertEqual(1, len(list(checks.dict_constructor_with_list_copy(
199 " dict([(i, connect_info[i])"))))
201 self.assertEqual(1, len(list(checks.dict_constructor_with_list_copy(
202 " attrs = dict([(k, _from_json(v))"))))
204 self.assertEqual(1, len(list(checks.dict_constructor_with_list_copy(
205 " type_names = dict((value, key) for key, value in"))))
207 self.assertEqual(1, len(list(checks.dict_constructor_with_list_copy(
208 " dict((value, key) for key, value in"))))
210 self.assertEqual(1, len(list(checks.dict_constructor_with_list_copy(
211 "foo(param=dict((k, v) for k, v in bar.items()))"))))
213 self.assertEqual(1, len(list(checks.dict_constructor_with_list_copy(
214 " dict([[i,i] for i in range(3)])"))))
216 self.assertEqual(1, len(list(checks.dict_constructor_with_list_copy(
217 " dd = dict([i,i] for i in range(3))"))))
219 self.assertEqual(0, len(list(checks.dict_constructor_with_list_copy(
220 " create_kwargs = dict(snapshot=snapshot,"))))
222 self.assertEqual(0, len(list(checks.dict_constructor_with_list_copy(
223 " self._render_dict(xml, data_el, data.__dict__)"))))
225 def test_validate_assertTrue(self):
226 test_value = True
227 self.assertEqual(0, len(list(checks.validate_assertTrue(
228 "assertTrue(True)"))))
229 self.assertEqual(1, len(list(checks.validate_assertTrue(
230 "assertEqual(True, %s)" % test_value))))
232 def test_check_uuid4(self):
233 code = """
234 fake_uuid = uuid.uuid4()
235 """
236 errors = [(1, 0, 'M354')]
237 self._assert_has_errors(code, checks.check_uuid4,
238 expected_errors=errors)
240 code = """
241 hex_uuid = uuid.uuid4().hex
242 """
243 self._assert_has_no_errors(code, checks.check_uuid4)
245 def test_no_log_warn_check(self):
246 self.assertEqual(0, len(list(checks.no_log_warn_check(
247 "LOG.warning('This should not trigger LOG.warn "
248 "hacking check.')"))))
249 self.assertEqual(1, len(list(checks.no_log_warn_check(
250 "LOG.warn('We should not use LOG.warn')"))))
251 foo = """
252 LOG.warn('Catch me too, please'
253 )
254 """
255 self.assertEqual(1, len(list(checks.no_log_warn_check(foo))))