• 
      

    Add support for spy operations.

    Review Request #11153 — Created Aug. 30, 2020 and submitted — Latest diff uploaded

    Information

    kgb
    master

    Reviewers

    kgb

    A spy operation is an action or set of possible actions that are
    performed when a function is called. They're an alternative to
    overriding a function explicitly, and instead work as reusable shortcuts
    for common patterns used when spying. Examples would be raising an
    exception, or dispatching a function based on the arguments provided in
    the call.

    These can be thought of as glorified functions, and in fact they
    basically are, under the hood, but are designed to be extensible so that
    projects can more easily maintain common spy patterns.

    There are some built-in operations provided by kgb:

    • SpyOpMatchAny allows a caller to provide a list of all possible sets
      of arguments that may be in one or more calls, triggering spy behavior
      for the particular match (allowing call_original/call_fake to be
      conditional on the arguments). Any call not provided in the list will
      raise an UnexpectedCallError assertion.

    • SpyOpMatchInOrder is similar to SpyOpMatchAny, but the calls
      must be in the order specified (which is useful for ensuring an order
      of operations).

    • SpyOpRaise takes an exception instance and raises it when the
      function is called (preventing a caller from having to define a
      wrapping function).

    • SpyOpReturn takes a return value and returns it when the function is
      called (similar to defining a simple lambda, but better specifying the
      intent).

    These are set with an op= argument, instead of a call_fake=. For
    example::

    spy_on(pen.emit_poison, op=kgb.SpyOpRaise(PoisonEmptyError()))
    

    Or, for one of the more complex examples:

    spy_on(traps.trigger, op=kgb.SpyOpMatchAny([
        {
            'args': ('hallway_lasers',),
            'call_fake': _send_wolves,
        },
        {
            'args': ('trap_tile',),
            'call_fake': _spill_hot_oil,
        },
        {
            'args': ('infrared_camera',),
            'kwargs': {
                'sector': 'underground_passage',
            },
            'call_original': False,
        },
    ]))
    

    Unit tests pass on all supported versions of Python.

    Plugged each of these operations into some real-world code and made sure
    they worked as expected.

    Commits

    Files