Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reconstruct SetupSet and VerifySet expressions from delegates #767

Merged
merged 10 commits into from
Mar 6, 2019

Commits on Mar 5, 2019

  1. Initial impl for LINQ expression reconstructor

    Moq needs to work around LINQ expression tree limitations of the .NET
    compilers by using delegates instead in some places (e.g. `SetupSet`).
    The current approach for turning those back into setups is not ideal.
    
    This commit adds a new component to Moq: the `ExpressionReconstructor`
    abstract type, along with an early version of a concrete implementa-
    tion (`ActionObserver`). The latter is based on a similar principle as
    `AmbientObserver`, but it is completely divorced from the `Mock` type.
    It uses an independent interceptor type whose sole raison d'être is
    to record invocations and to proxy return types.
    
    This is what we'll be able to do with it:
    
     1. Eventually install it in `SetupSet`, `VerifySet` and a few other
        places so that these can operate on LINQ expression trees, like
        the rest of the Moq API.
    
     2. Then remove those code paths from the `Mock` interception pipeline
        that are specific to `AmbientObserver` and performed the same
        function that `ExpressionReconstructor` can do in a more focused
        manner.
    
    These changes might also give us easier-to-understand code and
    possibly better runtime performance.
    stakx committed Mar 5, 2019
    Configuration menu
    Copy the full SHA
    84e3973 View commit details
    Browse the repository at this point in the history
  2. Add support for matchers to ActionObserver

    Reconstructing argument matchers and placing them in the right place
    is achieved as follows:
    
     * We execute our delegate while an `AmbientObserver` is active. (This
       is where invoked matchers register themselves, and from whence we
       can retrieve them.)
    
     * We augment `AmbientObserver` such that it timestamps each observa-
       tiion with a sequential number.
    
     * The recorder proxies timestamp their own creation (using the same
       sequence), as well as the moment when they receive an invocation.
    
     * Now we deduce that all matchers observed by the `AmbientObserver`
       between a recorder proxy's creation and invocation time "belong" to
       that recorder's invocation. That's because an expression such as
       the following gets evaluated in this order:
    
        |    X.Y(a, b, c)
        |
        |
        |    X               1. The proxy on which an invocation occurs
        |                       is associated with a recorder creation.
        v        a  b  c     2. Arguments are evaluated.
       time   .Y(       )    3. A proxy member gets invoked.
    
     * Finally, we try to distribute matchers over the invoked method's
       parameters. Only positions are considered where a non-`default`
       argument value was received (as matchers by convention return `def-
       ault`). These slots are filled with available matchers "from left
       to right" using a simple back-tracking algorithm.
    
    This is still a little rough around the edges. Let's refine later.
    stakx committed Mar 5, 2019
    Configuration menu
    Copy the full SHA
    ab47af9 View commit details
    Browse the repository at this point in the history
  3. Configuration menu
    Copy the full SHA
    0b9937d View commit details
    Browse the repository at this point in the history
  4. Configuration menu
    Copy the full SHA
    a37af62 View commit details
    Browse the repository at this point in the history
  5. Change "default values" slot selection strategy

    So far, matchers only got placed where a parameter received its own
    default value. This won't work in a case such as this:
    
        object Property { get; set; }
    
        obj => obj.Property = It.IsAny<int>();
    
    because this will invoke `set_Property(object)` with the value 0, i.e.
    the default value of the matcher but not of the property type.
    
    If we change the matcher parameter selection rule such that parameters
    get chosen whose argument is not equal to a *matcher's* default value.
    stakx committed Mar 5, 2019
    Configuration menu
    Copy the full SHA
    28aecac View commit details
    Browse the repository at this point in the history
  6. Convert SetupSet, VerifySet to expressions

    Using the recently added `ExpressionReconstructor` for `SetupSet` and
    `VerifySet` means they can now operate on expressions, too; and they
    can plug into the new recursive setup/verification algorithm like the
    other API methods.
    
    However, some additional work is needed here to get custom matchers
    working as expected. It would be inaccurate of `ExpressionReconstruc-
    tor` to just embed a custom matcher's `RenderExpression` into the ex-
    pression, as that property is only meant to be used for diagnostic
    purposes (e.g. in error messages). Instead, the `Match` itself should
    be embedded in the expression. Fortunately there's already `MatchEx-
    pression` for just that purpose; it just needs to be improved a little
    along with `ExpressionComparer` and `ExpressionStringBuilder`. An ex-
    pression containing `MatchExpression`s should never be compiled and
    executed. To ensure that, we declare this node type as irreducible.
    stakx committed Mar 5, 2019
    Configuration menu
    Copy the full SHA
    ac52c4b View commit details
    Browse the repository at this point in the history
  7. Configuration menu
    Copy the full SHA
    7c505e0 View commit details
    Browse the repository at this point in the history

Commits on Mar 6, 2019

  1. Configuration menu
    Copy the full SHA
    b7a298c View commit details
    Browse the repository at this point in the history
  2. Configuration menu
    Copy the full SHA
    36c5e4c View commit details
    Browse the repository at this point in the history
  3. Update the changelog

    stakx committed Mar 6, 2019
    Configuration menu
    Copy the full SHA
    af778b5 View commit details
    Browse the repository at this point in the history