OLD | NEW |
| (Empty) |
1 #!/usr/bin/env python | |
2 # | |
3 # Copyright 2008, Google Inc. | |
4 # All rights reserved. | |
5 # | |
6 # Redistribution and use in source and binary forms, with or without | |
7 # modification, are permitted provided that the following conditions are | |
8 # met: | |
9 # | |
10 # * Redistributions of source code must retain the above copyright | |
11 # notice, this list of conditions and the following disclaimer. | |
12 # * Redistributions in binary form must reproduce the above | |
13 # copyright notice, this list of conditions and the following disclaimer | |
14 # in the documentation and/or other materials provided with the | |
15 # distribution. | |
16 # * Neither the name of Google Inc. nor the names of its | |
17 # contributors may be used to endorse or promote products derived from | |
18 # this software without specific prior written permission. | |
19 # | |
20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
21 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
22 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
23 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
24 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
25 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
26 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
27 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
28 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
29 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
30 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
31 | |
32 """Converts gcc errors in code using Google Mock to plain English.""" | |
33 | |
34 __author__ = 'wan@google.com (Zhanyong Wan)' | |
35 | |
36 import re | |
37 import sys | |
38 | |
39 _VERSION = '1.0.3' | |
40 | |
41 _COMMON_GMOCK_SYMBOLS = [ | |
42 # Matchers | |
43 '_', | |
44 'A', | |
45 'AddressSatisfies', | |
46 'AllOf', | |
47 'An', | |
48 'AnyOf', | |
49 'ContainerEq', | |
50 'Contains', | |
51 'ContainsRegex', | |
52 'DoubleEq', | |
53 'ElementsAre', | |
54 'ElementsAreArray', | |
55 'EndsWith', | |
56 'Eq', | |
57 'Field', | |
58 'FloatEq', | |
59 'Ge', | |
60 'Gt', | |
61 'HasSubstr', | |
62 'IsInitializedProto', | |
63 'Le', | |
64 'Lt', | |
65 'MatcherCast', | |
66 'Matches', | |
67 'MatchesRegex', | |
68 'NanSensitiveDoubleEq', | |
69 'NanSensitiveFloatEq', | |
70 'Ne', | |
71 'Not', | |
72 'NotNull', | |
73 'Pointee', | |
74 'Property', | |
75 'Ref', | |
76 'ResultOf', | |
77 'SafeMatcherCast', | |
78 'StartsWith', | |
79 'StrCaseEq', | |
80 'StrCaseNe', | |
81 'StrEq', | |
82 'StrNe', | |
83 'Truly', | |
84 'TypedEq', | |
85 'Value', | |
86 | |
87 # Actions | |
88 'Assign', | |
89 'ByRef', | |
90 'DeleteArg', | |
91 'DoAll', | |
92 'DoDefault', | |
93 'IgnoreResult', | |
94 'Invoke', | |
95 'InvokeArgument', | |
96 'InvokeWithoutArgs', | |
97 'Return', | |
98 'ReturnNew', | |
99 'ReturnNull', | |
100 'ReturnRef', | |
101 'SaveArg', | |
102 'SetArgReferee', | |
103 'SetArgumentPointee', | |
104 'SetArrayArgument', | |
105 'SetErrnoAndReturn', | |
106 'Throw', | |
107 'WithArg', | |
108 'WithArgs', | |
109 'WithoutArgs', | |
110 | |
111 # Cardinalities | |
112 'AnyNumber', | |
113 'AtLeast', | |
114 'AtMost', | |
115 'Between', | |
116 'Exactly', | |
117 | |
118 # Sequences | |
119 'InSequence', | |
120 'Sequence', | |
121 | |
122 # Misc | |
123 'DefaultValue', | |
124 'Mock', | |
125 ] | |
126 | |
127 # Regex for matching source file path and line number in gcc's errors. | |
128 _FILE_LINE_RE = r'(?P<file>.*):(?P<line>\d+):\s+' | |
129 | |
130 | |
131 def _FindAllMatches(regex, s): | |
132 """Generates all matches of regex in string s.""" | |
133 | |
134 r = re.compile(regex) | |
135 return r.finditer(s) | |
136 | |
137 | |
138 def _GenericDiagnoser(short_name, long_name, regex, diagnosis, msg): | |
139 """Diagnoses the given disease by pattern matching. | |
140 | |
141 Args: | |
142 short_name: Short name of the disease. | |
143 long_name: Long name of the disease. | |
144 regex: Regex for matching the symptoms. | |
145 diagnosis: Pattern for formatting the diagnosis. | |
146 msg: Gcc's error messages. | |
147 Yields: | |
148 Tuples of the form | |
149 (short name of disease, long name of disease, diagnosis). | |
150 """ | |
151 | |
152 diagnosis = '%(file)s:%(line)s:' + diagnosis | |
153 for m in _FindAllMatches(regex, msg): | |
154 yield (short_name, long_name, diagnosis % m.groupdict()) | |
155 | |
156 | |
157 def _NeedToReturnReferenceDiagnoser(msg): | |
158 """Diagnoses the NRR disease, given the error messages by gcc.""" | |
159 | |
160 regex = (r'In member function \'testing::internal::ReturnAction<R>.*\n' | |
161 + _FILE_LINE_RE + r'instantiated from here\n' | |
162 r'.*gmock-actions\.h.*error: creating array with negative size') | |
163 diagnosis = """ | |
164 You are using an Return() action in a function that returns a reference. | |
165 Please use ReturnRef() instead.""" | |
166 return _GenericDiagnoser('NRR', 'Need to Return Reference', | |
167 regex, diagnosis, msg) | |
168 | |
169 | |
170 def _NeedToReturnSomethingDiagnoser(msg): | |
171 """Diagnoses the NRS disease, given the error messages by gcc.""" | |
172 | |
173 regex = (_FILE_LINE_RE + | |
174 r'(instantiated from here\n.' | |
175 r'*gmock.*actions\.h.*error: void value not ignored)' | |
176 r'|(error: control reaches end of non-void function)') | |
177 diagnosis = """ | |
178 You are using an action that returns void, but it needs to return | |
179 *something*. Please tell it *what* to return. Perhaps you can use | |
180 the pattern DoAll(some_action, Return(some_value))?""" | |
181 return _GenericDiagnoser('NRS', 'Need to Return Something', | |
182 regex, diagnosis, msg) | |
183 | |
184 | |
185 def _NeedToReturnNothingDiagnoser(msg): | |
186 """Diagnoses the NRN disease, given the error messages by gcc.""" | |
187 | |
188 regex = (_FILE_LINE_RE + r'instantiated from here\n' | |
189 r'.*gmock-actions\.h.*error: instantiation of ' | |
190 r'\'testing::internal::ReturnAction<R>::Impl<F>::value_\' ' | |
191 r'as type \'void\'') | |
192 diagnosis = """ | |
193 You are using an action that returns *something*, but it needs to return | |
194 void. Please use a void-returning action instead. | |
195 | |
196 All actions but the last in DoAll(...) must return void. Perhaps you need | |
197 to re-arrange the order of actions in a DoAll(), if you are using one?""" | |
198 return _GenericDiagnoser('NRN', 'Need to Return Nothing', | |
199 regex, diagnosis, msg) | |
200 | |
201 | |
202 def _IncompleteByReferenceArgumentDiagnoser(msg): | |
203 """Diagnoses the IBRA disease, given the error messages by gcc.""" | |
204 | |
205 regex = (_FILE_LINE_RE + r'instantiated from here\n' | |
206 r'.*gtest-printers\.h.*error: invalid application of ' | |
207 r'\'sizeof\' to incomplete type \'(?P<type>.*)\'') | |
208 diagnosis = """ | |
209 In order to mock this function, Google Mock needs to see the definition | |
210 of type "%(type)s" - declaration alone is not enough. Either #include | |
211 the header that defines it, or change the argument to be passed | |
212 by pointer.""" | |
213 return _GenericDiagnoser('IBRA', 'Incomplete By-Reference Argument Type', | |
214 regex, diagnosis, msg) | |
215 | |
216 | |
217 def _OverloadedFunctionMatcherDiagnoser(msg): | |
218 """Diagnoses the OFM disease, given the error messages by gcc.""" | |
219 | |
220 regex = (_FILE_LINE_RE + r'error: no matching function for ' | |
221 r'call to \'Truly\(<unresolved overloaded function type>\)') | |
222 diagnosis = """ | |
223 The argument you gave to Truly() is an overloaded function. Please tell | |
224 gcc which overloaded version you want to use. | |
225 | |
226 For example, if you want to use the version whose signature is | |
227 bool Foo(int n); | |
228 you should write | |
229 Truly(static_cast<bool (*)(int n)>(Foo))""" | |
230 return _GenericDiagnoser('OFM', 'Overloaded Function Matcher', | |
231 regex, diagnosis, msg) | |
232 | |
233 | |
234 def _OverloadedFunctionActionDiagnoser(msg): | |
235 """Diagnoses the OFA disease, given the error messages by gcc.""" | |
236 | |
237 regex = (_FILE_LINE_RE + r'error: no matching function for call to \'Invoke\(' | |
238 r'<unresolved overloaded function type>') | |
239 diagnosis = """ | |
240 You are passing an overloaded function to Invoke(). Please tell gcc | |
241 which overloaded version you want to use. | |
242 | |
243 For example, if you want to use the version whose signature is | |
244 bool MyFunction(int n, double x); | |
245 you should write something like | |
246 Invoke(static_cast<bool (*)(int n, double x)>(MyFunction))""" | |
247 return _GenericDiagnoser('OFA', 'Overloaded Function Action', | |
248 regex, diagnosis, msg) | |
249 | |
250 | |
251 def _OverloadedMethodActionDiagnoser1(msg): | |
252 """Diagnoses the OMA disease, given the error messages by gcc.""" | |
253 | |
254 regex = (_FILE_LINE_RE + r'error: ' | |
255 r'.*no matching function for call to \'Invoke\(.*, ' | |
256 r'unresolved overloaded function type>') | |
257 diagnosis = """ | |
258 The second argument you gave to Invoke() is an overloaded method. Please | |
259 tell gcc which overloaded version you want to use. | |
260 | |
261 For example, if you want to use the version whose signature is | |
262 class Foo { | |
263 ... | |
264 bool Bar(int n, double x); | |
265 }; | |
266 you should write something like | |
267 Invoke(foo, static_cast<bool (Foo::*)(int n, double x)>(&Foo::Bar))""" | |
268 return _GenericDiagnoser('OMA', 'Overloaded Method Action', | |
269 regex, diagnosis, msg) | |
270 | |
271 | |
272 def _MockObjectPointerDiagnoser(msg): | |
273 """Diagnoses the MOP disease, given the error messages by gcc.""" | |
274 | |
275 regex = (_FILE_LINE_RE + r'error: request for member ' | |
276 r'\'gmock_(?P<method>.+)\' in \'(?P<mock_object>.+)\', ' | |
277 r'which is of non-class type \'(.*::)*(?P<class_name>.+)\*\'') | |
278 diagnosis = """ | |
279 The first argument to ON_CALL() and EXPECT_CALL() must be a mock *object*, | |
280 not a *pointer* to it. Please write '*(%(mock_object)s)' instead of | |
281 '%(mock_object)s' as your first argument. | |
282 | |
283 For example, given the mock class: | |
284 | |
285 class %(class_name)s : public ... { | |
286 ... | |
287 MOCK_METHOD0(%(method)s, ...); | |
288 }; | |
289 | |
290 and the following mock instance: | |
291 | |
292 %(class_name)s* mock_ptr = ... | |
293 | |
294 you should use the EXPECT_CALL like this: | |
295 | |
296 EXPECT_CALL(*mock_ptr, %(method)s(...));""" | |
297 return _GenericDiagnoser('MOP', 'Mock Object Pointer', | |
298 regex, diagnosis, msg) | |
299 | |
300 | |
301 def _OverloadedMethodActionDiagnoser2(msg): | |
302 """Diagnoses the OMA disease, given the error messages by gcc.""" | |
303 | |
304 regex = (_FILE_LINE_RE + r'error: no matching function for ' | |
305 r'call to \'Invoke\(.+, <unresolved overloaded function type>\)') | |
306 diagnosis = """ | |
307 The second argument you gave to Invoke() is an overloaded method. Please | |
308 tell gcc which overloaded version you want to use. | |
309 | |
310 For example, if you want to use the version whose signature is | |
311 class Foo { | |
312 ... | |
313 bool Bar(int n, double x); | |
314 }; | |
315 you should write something like | |
316 Invoke(foo, static_cast<bool (Foo::*)(int n, double x)>(&Foo::Bar))""" | |
317 return _GenericDiagnoser('OMA', 'Overloaded Method Action', | |
318 regex, diagnosis, msg) | |
319 | |
320 | |
321 def _NeedToUseSymbolDiagnoser(msg): | |
322 """Diagnoses the NUS disease, given the error messages by gcc.""" | |
323 | |
324 regex = (_FILE_LINE_RE + r'error: \'(?P<symbol>.+)\' ' | |
325 r'(was not declared in this scope|has not been declared)') | |
326 diagnosis = """ | |
327 '%(symbol)s' is defined by Google Mock in the testing namespace. | |
328 Did you forget to write | |
329 using testing::%(symbol)s; | |
330 ?""" | |
331 for m in _FindAllMatches(regex, msg): | |
332 symbol = m.groupdict()['symbol'] | |
333 if symbol in _COMMON_GMOCK_SYMBOLS: | |
334 yield ('NUS', 'Need to Use Symbol', diagnosis % m.groupdict()) | |
335 | |
336 | |
337 def _NeedToUseReturnNullDiagnoser(msg): | |
338 """Diagnoses the NRNULL disease, given the error messages by gcc.""" | |
339 | |
340 regex = ('instantiated from \'testing::internal::ReturnAction<R>' | |
341 '::operator testing::Action<Func>\(\) const.*\n' + | |
342 _FILE_LINE_RE + r'instantiated from here\n' | |
343 r'.*error: no matching function for call to \'implicit_cast\(' | |
344 r'long int&\)') | |
345 diagnosis = """ | |
346 You are probably calling Return(NULL) and the compiler isn't sure how to turn | |
347 NULL into the right type. Use ReturnNull() instead. | |
348 Note: the line number may be off; please fix all instances of Return(NULL).""" | |
349 return _GenericDiagnoser('NRNULL', 'Need to use ReturnNull', | |
350 regex, diagnosis, msg) | |
351 | |
352 | |
353 _TTB_DIAGNOSIS = """ | |
354 In a mock class template, types or typedefs defined in the base class | |
355 template are *not* automatically visible. This is how C++ works. Before | |
356 you can use a type or typedef named %(type)s defined in base class Base<T>, you | |
357 need to make it visible. One way to do it is: | |
358 | |
359 typedef typename Base<T>::%(type)s %(type)s;""" | |
360 | |
361 | |
362 def _TypeInTemplatedBaseDiagnoser1(msg): | |
363 """Diagnoses the TTB disease, given the error messages by gcc. | |
364 | |
365 This version works when the type is used as the mock function's return | |
366 type. | |
367 """ | |
368 | |
369 gcc_4_3_1_regex = ( | |
370 r'In member function \'int .*\n' + _FILE_LINE_RE + | |
371 r'error: a function call cannot appear in a constant-expression') | |
372 gcc_4_4_0_regex = ( | |
373 r'error: a function call cannot appear in a constant-expression' | |
374 + _FILE_LINE_RE + r'error: template argument 1 is invalid\n') | |
375 diagnosis = _TTB_DIAGNOSIS % {'type': 'Foo'} | |
376 return (list(_GenericDiagnoser('TTB', 'Type in Template Base', | |
377 gcc_4_3_1_regex, diagnosis, msg)) + | |
378 list(_GenericDiagnoser('TTB', 'Type in Template Base', | |
379 gcc_4_4_0_regex, diagnosis, msg))) | |
380 | |
381 | |
382 def _TypeInTemplatedBaseDiagnoser2(msg): | |
383 """Diagnoses the TTB disease, given the error messages by gcc. | |
384 | |
385 This version works when the type is used as the mock function's sole | |
386 parameter type. | |
387 """ | |
388 | |
389 regex = (_FILE_LINE_RE + | |
390 r'error: \'(?P<type>.+)\' was not declared in this scope\n' | |
391 r'.*error: template argument 1 is invalid\n') | |
392 return _GenericDiagnoser('TTB', 'Type in Template Base', | |
393 regex, _TTB_DIAGNOSIS, msg) | |
394 | |
395 | |
396 def _TypeInTemplatedBaseDiagnoser3(msg): | |
397 """Diagnoses the TTB disease, given the error messages by gcc. | |
398 | |
399 This version works when the type is used as a parameter of a mock | |
400 function that has multiple parameters. | |
401 """ | |
402 | |
403 regex = (r'error: expected `;\' before \'::\' token\n' | |
404 + _FILE_LINE_RE + | |
405 r'error: \'(?P<type>.+)\' was not declared in this scope\n' | |
406 r'.*error: template argument 1 is invalid\n' | |
407 r'.*error: \'.+\' was not declared in this scope') | |
408 return _GenericDiagnoser('TTB', 'Type in Template Base', | |
409 regex, _TTB_DIAGNOSIS, msg) | |
410 | |
411 | |
412 def _WrongMockMethodMacroDiagnoser(msg): | |
413 """Diagnoses the WMM disease, given the error messages by gcc.""" | |
414 | |
415 regex = (_FILE_LINE_RE + | |
416 r'.*this_method_does_not_take_(?P<wrong_args>\d+)_argument.*\n' | |
417 r'.*\n' | |
418 r'.*candidates are.*FunctionMocker<[^>]+A(?P<args>\d+)\)>') | |
419 diagnosis = """ | |
420 You are using MOCK_METHOD%(wrong_args)s to define a mock method that has | |
421 %(args)s arguments. Use MOCK_METHOD%(args)s (or MOCK_CONST_METHOD%(args)s, | |
422 MOCK_METHOD%(args)s_T, MOCK_CONST_METHOD%(args)s_T as appropriate) instead.""" | |
423 return _GenericDiagnoser('WMM', 'Wrong MOCK_METHODn Macro', | |
424 regex, diagnosis, msg) | |
425 | |
426 | |
427 def _WrongParenPositionDiagnoser(msg): | |
428 """Diagnoses the WPP disease, given the error messages by gcc.""" | |
429 | |
430 regex = (_FILE_LINE_RE + | |
431 r'error:.*testing::internal::MockSpec<.* has no member named \'' | |
432 r'(?P<method>\w+)\'') | |
433 diagnosis = """ | |
434 The closing parenthesis of ON_CALL or EXPECT_CALL should be *before* | |
435 ".%(method)s". For example, you should write: | |
436 EXPECT_CALL(my_mock, Foo(_)).%(method)s(...); | |
437 instead of: | |
438 EXPECT_CALL(my_mock, Foo(_).%(method)s(...));""" | |
439 return _GenericDiagnoser('WPP', 'Wrong Parenthesis Position', | |
440 regex, diagnosis, msg) | |
441 | |
442 | |
443 _DIAGNOSERS = [ | |
444 _IncompleteByReferenceArgumentDiagnoser, | |
445 _MockObjectPointerDiagnoser, | |
446 _NeedToReturnNothingDiagnoser, | |
447 _NeedToReturnReferenceDiagnoser, | |
448 _NeedToReturnSomethingDiagnoser, | |
449 _NeedToUseReturnNullDiagnoser, | |
450 _NeedToUseSymbolDiagnoser, | |
451 _OverloadedFunctionActionDiagnoser, | |
452 _OverloadedFunctionMatcherDiagnoser, | |
453 _OverloadedMethodActionDiagnoser1, | |
454 _OverloadedMethodActionDiagnoser2, | |
455 _TypeInTemplatedBaseDiagnoser1, | |
456 _TypeInTemplatedBaseDiagnoser2, | |
457 _TypeInTemplatedBaseDiagnoser3, | |
458 _WrongMockMethodMacroDiagnoser, | |
459 _WrongParenPositionDiagnoser, | |
460 ] | |
461 | |
462 | |
463 def Diagnose(msg): | |
464 """Generates all possible diagnoses given the gcc error message.""" | |
465 | |
466 diagnoses = [] | |
467 for diagnoser in _DIAGNOSERS: | |
468 for diag in diagnoser(msg): | |
469 diagnosis = '[%s - %s]\n%s' % diag | |
470 if not diagnosis in diagnoses: | |
471 diagnoses.append(diagnosis) | |
472 return diagnoses | |
473 | |
474 | |
475 def main(): | |
476 print ('Google Mock Doctor v%s - ' | |
477 'diagnoses problems in code using Google Mock.' % _VERSION) | |
478 | |
479 if sys.stdin.isatty(): | |
480 print ('Please copy and paste the compiler errors here. Press c-D when ' | |
481 'you are done:') | |
482 else: | |
483 print 'Waiting for compiler errors on stdin . . .' | |
484 | |
485 msg = sys.stdin.read().strip() | |
486 diagnoses = Diagnose(msg) | |
487 count = len(diagnoses) | |
488 if not count: | |
489 print '\nGcc complained:' | |
490 print '8<------------------------------------------------------------' | |
491 print msg | |
492 print '------------------------------------------------------------>8' | |
493 print """ | |
494 Uh-oh, I'm not smart enough to figure out what the problem is. :-( | |
495 However... | |
496 If you send your source code and gcc's error messages to | |
497 googlemock@googlegroups.com, you can be helped and I can get smarter -- | |
498 win-win for us!""" | |
499 else: | |
500 print '------------------------------------------------------------' | |
501 print 'Your code appears to have the following', | |
502 if count > 1: | |
503 print '%s diseases:' % (count,) | |
504 else: | |
505 print 'disease:' | |
506 i = 0 | |
507 for d in diagnoses: | |
508 i += 1 | |
509 if count > 1: | |
510 print '\n#%s:' % (i,) | |
511 print d | |
512 print """ | |
513 How did I do? If you think I'm wrong or unhelpful, please send your | |
514 source code and gcc's error messages to googlemock@googlegroups.com. Then | |
515 you can be helped and I can get smarter -- I promise I won't be upset!""" | |
516 | |
517 | |
518 if __name__ == '__main__': | |
519 main() | |
OLD | NEW |