gmock_doctor.py revision 3345a6884c488ff3a535c2c9acdd33d74b37e311
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 36import re 37import 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 131def _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 138def _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 157def _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 = """ 164You are using an Return() action in a function that returns a reference. 165Please use ReturnRef() instead.""" 166 return _GenericDiagnoser('NRR', 'Need to Return Reference', 167 regex, diagnosis, msg) 168 169 170def _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 = """ 178You are using an action that returns void, but it needs to return 179*something*. Please tell it *what* to return. Perhaps you can use 180the pattern DoAll(some_action, Return(some_value))?""" 181 return _GenericDiagnoser('NRS', 'Need to Return Something', 182 regex, diagnosis, msg) 183 184 185def _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 = """ 193You are using an action that returns *something*, but it needs to return 194void. Please use a void-returning action instead. 195 196All actions but the last in DoAll(...) must return void. Perhaps you need 197to 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 202def _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 = """ 209In order to mock this function, Google Mock needs to see the definition 210of type "%(type)s" - declaration alone is not enough. Either #include 211the header that defines it, or change the argument to be passed 212by pointer.""" 213 return _GenericDiagnoser('IBRA', 'Incomplete By-Reference Argument Type', 214 regex, diagnosis, msg) 215 216 217def _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 = """ 223The argument you gave to Truly() is an overloaded function. Please tell 224gcc which overloaded version you want to use. 225 226For example, if you want to use the version whose signature is 227 bool Foo(int n); 228you should write 229 Truly(static_cast<bool (*)(int n)>(Foo))""" 230 return _GenericDiagnoser('OFM', 'Overloaded Function Matcher', 231 regex, diagnosis, msg) 232 233 234def _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 = """ 240You are passing an overloaded function to Invoke(). Please tell gcc 241which overloaded version you want to use. 242 243For example, if you want to use the version whose signature is 244 bool MyFunction(int n, double x); 245you 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 251def _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 = """ 258The second argument you gave to Invoke() is an overloaded method. Please 259tell gcc which overloaded version you want to use. 260 261For example, if you want to use the version whose signature is 262 class Foo { 263 ... 264 bool Bar(int n, double x); 265 }; 266you 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 272def _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 = """ 279The first argument to ON_CALL() and EXPECT_CALL() must be a mock *object*, 280not a *pointer* to it. Please write '*(%(mock_object)s)' instead of 281'%(mock_object)s' as your first argument. 282 283For example, given the mock class: 284 285 class %(class_name)s : public ... { 286 ... 287 MOCK_METHOD0(%(method)s, ...); 288 }; 289 290and the following mock instance: 291 292 %(class_name)s* mock_ptr = ... 293 294you 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 301def _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 = """ 307The second argument you gave to Invoke() is an overloaded method. Please 308tell gcc which overloaded version you want to use. 309 310For example, if you want to use the version whose signature is 311 class Foo { 312 ... 313 bool Bar(int n, double x); 314 }; 315you 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 321def _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. 328Did 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 337def _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 = """ 346You are probably calling Return(NULL) and the compiler isn't sure how to turn 347NULL into the right type. Use ReturnNull() instead. 348Note: 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 = """ 354In a mock class template, types or typedefs defined in the base class 355template are *not* automatically visible. This is how C++ works. Before 356you can use a type or typedef named %(type)s defined in base class Base<T>, you 357need to make it visible. One way to do it is: 358 359 typedef typename Base<T>::%(type)s %(type)s;""" 360 361 362def _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 382def _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 396def _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 412def _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 = """ 420You 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, 422MOCK_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 427def _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 = """ 434The 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(...); 437instead 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 463def 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 475def 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 """ 494Uh-oh, I'm not smart enough to figure out what the problem is. :-( 495However... 496If you send your source code and gcc's error messages to 497googlemock@googlegroups.com, you can be helped and I can get smarter -- 498win-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 """ 513How did I do? If you think I'm wrong or unhelpful, please send your 514source code and gcc's error messages to googlemock@googlegroups.com. Then 515you can be helped and I can get smarter -- I promise I won't be upset!""" 516 517 518if __name__ == '__main__': 519 main() 520