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