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

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. 

14 

15import itertools 

16import textwrap 

17from unittest import mock 

18 

19import ddt 

20import pycodestyle 

21 

22from manila import test 

23from manila.tests.hacking import checks 

24 

25 

26@ddt.ddt 

27class HackingTestCase(test.TestCase): 

28 """Hacking test cases 

29 

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:: 

36 

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 

53 

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 """ 

60 

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) 

68 

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)))) 

73 

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")))) 

112 

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) 

119 

120 lines = textwrap.dedent(code).strip().splitlines(True) 

121 

122 checker = pycodestyle.Checker(filename=filename, lines=lines) 

123 checker.check_all() 

124 checker.report._deferred_print.sort() 

125 return checker.report._deferred_print 

126 

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) 

132 

133 def _assert_has_no_errors(self, code, checker, filename=None): 

134 self._assert_has_errors(code, checker, filename=filename) 

135 

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) 

147 

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')]) 

159 

160 def test_trans_add(self): 

161 

162 checker = checks.CheckForTransAdd 

163 code = """ 

164 def fake_tran(msg): 

165 return msg 

166 

167 

168 _ = fake_tran 

169 _LI = _ 

170 _LW = _ 

171 _LE = _ 

172 _LC = _ 

173 

174 

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 """ 

184 

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) 

188 

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) 

196 

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])")))) 

200 

201 self.assertEqual(1, len(list(checks.dict_constructor_with_list_copy( 

202 " attrs = dict([(k, _from_json(v))")))) 

203 

204 self.assertEqual(1, len(list(checks.dict_constructor_with_list_copy( 

205 " type_names = dict((value, key) for key, value in")))) 

206 

207 self.assertEqual(1, len(list(checks.dict_constructor_with_list_copy( 

208 " dict((value, key) for key, value in")))) 

209 

210 self.assertEqual(1, len(list(checks.dict_constructor_with_list_copy( 

211 "foo(param=dict((k, v) for k, v in bar.items()))")))) 

212 

213 self.assertEqual(1, len(list(checks.dict_constructor_with_list_copy( 

214 " dict([[i,i] for i in range(3)])")))) 

215 

216 self.assertEqual(1, len(list(checks.dict_constructor_with_list_copy( 

217 " dd = dict([i,i] for i in range(3))")))) 

218 

219 self.assertEqual(0, len(list(checks.dict_constructor_with_list_copy( 

220 " create_kwargs = dict(snapshot=snapshot,")))) 

221 

222 self.assertEqual(0, len(list(checks.dict_constructor_with_list_copy( 

223 " self._render_dict(xml, data_el, data.__dict__)")))) 

224 

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)))) 

231 

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) 

239 

240 code = """ 

241 hex_uuid = uuid.uuid4().hex 

242 """ 

243 self._assert_has_no_errors(code, checks.check_uuid4) 

244 

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))))