Skip to content
This repository was archived by the owner on Jun 30, 2023. It is now read-only.

Conversation

@adalon
Copy link
Collaborator

@adalon adalon commented Jul 25, 2019

  • Legacy API shim
  • CallBase support
  • CodeFixer/CodeGen improvements

kzu and others added 21 commits July 23, 2019 05:04
Add support for v4 APIs implemented in terms of the new API, in non-browsable
mode so that new code can leverage the new idioms instead.

Removes the `Sdk.Times` struct from Moq's public `Verify` APIs to avoid conflicts
with the legacy `Times`, since it is not necessary given the fluent occurrence
constraints methods (`Once`, `Never`, `AtLeastOnce` and `Exactly`).
Annotating the constructor methods and modifying the source for the type
arguments is enough for now.
This ensures that the underlying pipeline is registered right when `mock.Setup(..)` is called, which would in turn eventually setup default pipeline behaviors as needed (i.e. for the upcoming `CallBase`).
When creating the legacy Mock<T> we need to make sure to use the
"external" calling assembly when passing arguments between the
ctor overloads.
This allows extending the execution pipeline by dynamically specifying
that certain behaviors (by type) should be excluded from execution for
the current invocation. This will allow adding `CallBase` support for
example, by allowing StrictBehavior to skip throwing and let the base
implementation in a base class to run.
When the pipeline is run in a `SetupScope`, we should not run the entire pipeline, but rather just the behaviors we know are safe for execution during setup, specifically `MockContextBehavior` (to set up the `MockContext.CurrentInvocation` and `MockContext.CurrentSetup`) and `DefaultValueBehavior` to return a default value for further fluent API invocations to work on.

This simplifies the behaviors since they don't need to check and bail during active setup scopes, and makes them more self-contained.
This allows simplified lambdas for setup, so that

```
mock.Setup(x => x.TurnOn());
```
becomes
```
mock.Setup(() => mock.TurnOn());
```

A future version could also add `Setup(() ...)` to Syntax for the same effect.
Instead of duplicating behavior and having the Behavior property modify the actual pipeline, leverage the new mechanism we added to allow skipping behaviors dynamically for the current invocation.

With this new feature at the pipeline level, we can create a new configuration behavior that can check the `MockBehavior` and simply skip the `StrictMockBehavior` if it's not `MockBehavior.Strict`. This greatly simplifies the implementation and makes both default and strict behaviors always present on the pipeline, but dynamically skipped based on configuration.

This requires introducing a mock state-level "field", which we now save with the key `IMoq.Behavior`, which should be the guideline for extensions that need to persist state in the mock: use the `[Type].[Property]` syntax to minimize chances of collision.
This reverts commit 0c2ff79.

That commit broke the analyzer for ref/out delegate generation since
the new overloads would match some of those setups. We need to think
better how that will work in the future, since overload resolution in
the compiler will not distinguish between two signatures that differ
only in parameter mode (ref/out).
The behaviors are actually the ones initialized by `MockInitializer` so it
makes sense that the one that sets which ones to skip also lives there instead
of the Moq.Sdk.
We don't want to accidentally pollute the public visible API.
Since it's already part of the default processors but we're
adding it again after we run the Mock-specific processors.
The NuGet asset resolving target already adds the metadata to the `Analyzer` items inferred from the `analyzer` folder in the package, but the `CodeFix` we provided did not contain this metadata. This makes it harder for a consumer to actually filter/remove them if they want to customize codegen in advanced way.

This would also allow a project to replace the package-provided analyzers and code fixers with locally built ones directly from the bin directories of the Moq solution.
Based on the ASP.NET Core solution, we added the following optimizations:

* Add a FixAll provider to support batch code fixing of all mocks/stunts that need codegen
* Do not get *all* diagnostics from *all* analyzers when getting code fixes, but only those from analyzers that produce diagnostics the desired code fix provider can actually fix (otherwise, we were also getting a ton of diagnostics that might have nothing to do with the code fix provider and intended code fix name)

This *significantly* improves the performance of the code generation with real world solutions.
This turned out to consume an inordinate amount of time for the benefit it provides, so we don't do it anymore. We still do the namespace sorting since it's cheaper.
@adalon adalon requested a review from kzu as a code owner July 25, 2019 13:57
// Once with message
Assert.Throws<VerifyException>(() => calculator.Verify(x => x.TurnOn(), 1, "Should have been called!"));
// Times.Once with message
Assert.Throws<VerifyException>(() => calculator.Verify(x => x.TurnOn(), Times.Once, "Should have been called!"));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For legacy support Once should be a method

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, but it never made sense as a method in the first place, since it has no parameters :(.

The good thing is that this is the new API overload. The old API overloads could still provide backwards compatible members, I think.

kzu added 5 commits July 28, 2019 21:12
Adds support for mocking generic types, which wasn't working before.
We previously used the very rudimentary `MetadataName` property to determine the type
to generate a stunt for. This was error prone since it does not contain the full metadata
for generic types (or nullable types) and basically you cannot resolve an `INamedTypeSymbol`
from it.

Since we need to support a more reliable and robust way to communicate back from an
analyzer to its code fix what is the target type(s) to generate stunts for, we implement our
own formatting (leveraging the `SymbolDisplayFormat` and then implementing a resolver
that parses the C#-like generic expression (i.e. `IEnumerable<KeyValuePair<string, int?>>`)
and resolves it back to a fully constructed generic `INamedTypeSymbol`.

We leverage the Superpower library for the parsing which is fast, provides great error reporting
and is a breeze to use and understand.
This code fix should only load in our own workspace, since csc does not contain the
required dependencies for it to load properly. This was causing transient build errors
when csc.exe would try to inspect the types via MEF and fail in the static initialization
of the RoslynInternals class which even if not used since the code fix was not exported,
was nevertheless initialized statically when reflected by the MEF composition.

By moving it to the CodeFix assembly, we also make it work transparently like all other
code fixers, which are referenced just by name. We were already doing that from the
C# and VB scaffold document processors, so no changes were necessary there.
Leave the generated class visibility up to the consumer, who can declare a partial class
and make it public that way. This ensures we can successfully mock internals which
have IVT with the test project.
kzu added 6 commits August 6, 2019 00:16
By moving the code to its own class with its own state, we avoid using lambda
state capturing which could be problematic since we're running all of the fixers
in parallel to boost performance.
At some point these tests were not working, but as part of testing the new relocated
override-all code fix, we found out they actually do work now again :). Made the
dynamic stunt disposable to isolate better from one test to the next anyway, which
should also make them more resilient by disposing the created ad-hoc workspace.
Code generation support for generics introduced the `Of..` name to
append the generic types being mocked (i.e. `IEnumerableOfStringMock`).
This was not reflected in the run-time naming classes so some tests
were failing.

In addition, there was quite some duplication between MockNaming and
StuntNaming, which is now unified with the former delegating to the
latter all behavior and just providing different DefaultNamespace and
DefaultSuffix values.

The new implementation is more aligned with the NamingConvention used
for codegen now.
kzu added 2 commits August 8, 2019 02:34
Instead of the quite complicated implementation that tried replacing the
pipeline on a mock, instantiating a new one, cloning its state, etc.,
we will just provide an analyzer+codefix that will expose the `T` as
part of the generic `new Mock<T...>` constructor, which means the code
generator doesn't need to get any smarter, and we can actually help
migrate people to the newer pattern where all types are known to the
factory from the beginning.

In order to do this, we also brought back the constraint on the `T`
for `IMock<T>`, which aligns with v4 and also allows us to cast from
within the `As<T>` implementation. This constraint was added to all
APIs that exposed `IMock<T>`.
@kzu kzu merged commit 37319a4 into master Aug 11, 2019
@kzu kzu deleted the dev/legacy branch August 11, 2019 15:22
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants