[llvm-dev] RFC: Reconsidering adding gmock to LLVM's unittest utilities
Philip Reames via llvm-dev
llvm-dev at lists.llvm.org
Wed Jan 4 22:16:23 PST 2017
No strong opinion, but certainly not opposed. If it makes testing pass
manager changes easier, SGTM.
Philip
On 01/04/2017 06:11 AM, Chandler Carruth via llvm-dev wrote:
> A long time ago I suggested that we might want to add gmock to
> compliment the facilities provided by gtest in LLVM's unittests. It
> didn't go over well:
>
> 1) There was concern over the benefit vs. the cost
> 2) Also concern about what the facilities would look like in practice
> and whether they would actually help
> 3) At the time, I didn't have good, large examples of what these
> things might look like or why they might be attractive
> 4) I didn't provide any real explanation of what gmock *did* and so it
> was vague and unclear.
>
> Since then, a lot has changed. We have more heavy use of unit testing
> in the project with more developers finding benefit from it. And I
> think I have compelling examples.
>
> ## Matchers
>
> To start off, it is important to understand that there are two
> components to what gmock offers. The first has very little to do with
> "mocks". It is actually a matcher language and system for writing test
> predicates:
>
> EXPECT_EQ(expected, actual);
> EXPECT_NE(something, something);
>
> Become instead:
>
> EXPECT_THAT(actual, Eq(expected));
> EXPECT_THAT(actual, Ne(not-expected));
>
> This pattern moves the *matcher* out of the *macro*, giving it a
> proper C++ API. With that, we get two huge benefits: extensibility and
> composability. You can easily write a matcher that summarizes
> concisely the expectation for custom data types. And you can compose
> these matchers in powerful ways. I'll give one example here:
>
> EXPECT_THAT(MyDenseMap, UnorderedElementsAre(Eq(key1, value1),
> Eq(key2, value2), Eq(key3, value3)));
>
> Here I'm composing equality matchers inside a matcher that can handle
> *unordered* container element-wise comparison for generic, arbitrary
> containers. With a small patch, I've even extended it to support
> arbitrary iterator ranges! Combine this with custom matchers for the
> elements, and it becomes a very expressive an declarative way to write
> expectations in tests.
>
> I wanted to give a realistic and compelling example so I rewrote an
> entire test: https://reviews.llvm.org/D28290 Note that I moved *every*
> EXPECT to the new syntax so this is essentially worst-case. It also
> involves a non-trivial custom matcher. Despite this, the code is
> shorter, easier to read and easier to maintain. It has fewer
> unnecessary orderings enforced. And it is much easier to extend. Also,
> the error messages when it fails are substantially improved because
> these composed matchers have logic to carefully explain *why* they
> failed to match.
>
> I hope folks find this compelling. I think this alone is worth
> carrying the gmock code in tree -- it is just used by tests and not
> substantially larger than gtest. Even if we decide we want nothing to
> do with mocks, I would very much like to have the matchers.
>
>
> ## Mocks
>
> So, now let's consider mocks. First off, what are mocks? I'll give a
> fairly casual definition here: they are test objects which implement
> some API and allow the test to explicitly set expectations on how that
> API is used and how it in turn should behave. For a more detailed
> vocabulary see [1] and for a more lengthy discussion see [2].
>
> As came up in the original discussion, LLVM relatively infrequently
> has a need to test API interactions in this way. Usually we're in the
> business of translating things from format A to B (instructions,
> metadata, whatever) and can write down one format and write checks
> against the other format for tests. This is a wonderful world to live
> in with tests. I never want LLVM to *decrease* how much we leverage this.
>
> But we *do* have API interactions that we need to test. We have plugin
> APIs, and hookable interfaces, ranging from Clang frontend actions to
> JIT listeners. We also have *generic* code in ADT that is all about
> API interactions. Most generic code in fact is -- we want it to work
> for *any* T that behaves in a certain way, so we need to give it
> interesting Ts to test it.
>
> My immediate example is the pass manager. We plug in a bunch of passes
> to it, and expect it to run them in a precise way over specific bits
> of IR. When testing this, it is extremely cumbersome to write a test
> pass which does this in interesting and yet controllable and
> comprehensible ways. Let's look at a concrete example:
>
> https://github.com/llvm-project/llvm-project/blob/master/llvm/unittests/IR/PassManagerTest.cpp#L481-L509
>
> Here we have over 20 lines of code spent testing that the correct set
> of things happened the correct number of times. I had to write a long
> comment just to explain what these numbers mean. And I still never
> understand whether a change in the numbers really means a good or bad
> thing.
>
> Now, we *have* detailed logging based tests use FileCheck which is the
> primary way to avoid this in LLVM. But it isn't enough. In these tests
> we want to carefully *permute* the behavior of very specific runs of
> individual passes. A simple example of this can be seen here where we
> have somewhat magical state in a pass to flip-flop its behavior:
> https://github.com/llvm-project/llvm-project/blob/master/llvm/unittests/IR/PassManagerTest.cpp#L138-L139
>
> And it gets more complicated if you want statefulness like triggering
> on the *3rd* run of the pass.
>
> But this is exactly the kinds of scenarios that I needed to write
> tests for in order to get the code to be correct. I have consistently
> found and been able to fix bugs throughout the pass manager by writing
> careful unittests.
>
> Mocks with GoogleMock are, IMO, a *tool to create interesting and
> debuggable test objects*. These objects can then be fed into an API to
> exercise it in ways that are hard or impossible to control from a
> command line in sufficient granularity and precision. While doing this
> is never fun and should be avoided where possible, when we need to do
> this I think it provides a powerful tool for the job.
>
> Here is how it works at the highest level:
> 1) Create a class with a MOCK_METHOD*(...) API. This API is then
> hookable by gmock.
> 2) Use some APIs to register default behaviors for the APIs.
> 3) Setup the *minimal* amount of expected API interactions for a given
> test. IE, for this test to pass, X has to happen and in response to
> that my code needs to do Y.
> 4) Feed this class, or a wrapper around it if you need a copyable
> object, into the system you are testing and run it.
>
> If the expected interactions don't occur, you get a trace of which
> ones failed and why. These traces are somewhat verbose and hard to
> read, but they actually have the information needed to debug the
> system which saves you from building infrastructure to extract that
> over and over again.
>
> But a concrete example will likely work better. I've used gmock to
> build the unit tests for a major revision of the LoopPassManager in
> the new pass manager. This is a substantial redesign that now handles
> inserting new loops, deleting loops, and invalidating analyses. The
> tests for it are, IMO, dramatically more readable than the test linked
> above. And they are substantially more thorough and precise:
>
> https://reviews.llvm.org/D28292
>
> I hope this is compelling for folks. Just writing and debugging this
> one test was extremely compelling for me. I ended up with much better
> coverage and precision than I would have using any other technique
> without a tremendous amount of plumbing essentially re-inventing a
> framework to build test pass objects that work exactly the way these
> mock pass handles do.
>
> That said, all is not perfect. For instance, gmock suffers from being
> designed in C++98 world. It has relatively poor support for move and
> value semantics, which resulted in my using a wrapper around the mock
> interfaces in the above patch to let a pimpl idiom provide the value
> semantics I wanted. However, that idiom works well, and this didn't
> substantially impede my use of the infrastructure.
>
> Also, I remain very sympathetic to the idea that this kind of testing
> apparatus should be relatively rarely needed. We shouldn't be writing
> new complex unit tests for APIs every week. But even a few use cases
> such as to test ADTs and generic tools like the pass manager seem to
> justify the cost to me, and I'm happy to help draw up fairly
> restrictive guidance around mocks for the coding standards.
>
>
> Thanks, and sorry for the long email, but I wanted to try and lay out
> the issues in a way folks could understand, and the examples, while
> hopefully useful, are quite large and complex.
>
> Please don't hesitate to ask questions if stuff isn't clear.
> -Chandler
>
> [1]: https://en.wikipedia.org/wiki/Test_double
> [2]: http://martinfowler.com/articles/mocksArentStubs.html
>
>
>
> _______________________________________________
> LLVM Developers mailing list
> llvm-dev at lists.llvm.org
> http://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20170104/aa4f2b4d/attachment.html>
More information about the llvm-dev
mailing list