trompeloeil
trompeloeil copied to clipboard
Feature request: Sequencing synchronization
I'm using multiple sequences as described in your blog post. I'm wishing I had something to allow me to insert sequencing points without allowing/requiring/forbidding calls on my mocks. It comes up mostly when I want to synchronize between blocks of events but don't have a good end point (like your reader->available() expectation) to attach all the sequences to and want the next expectation (like your appendReadBytes()) to be in an arbitrary order with its subsequent call.
Within the current release, I could create a dummy mock object and forbid/allow a call on it with all the sequences to synchronize, OR I could create yet another sequence to be the metasequence for all of them. But it seems like having something like SYNCRONIZE( seq1, seq2, seq3 ) would make it clearer and easier.
Or maybe there's already a way to do this that I haven't thought of, or maybe I'm doing it wrong.
What do you think?
I've had some thoughts along the same line, but since I haven't found a real world need I haven't given it priority.
Would 'join' be a better word than 'synchronize'?
I'll need to think about how to implement.
Join or synchronize, whatever. I tried the workarounds I mentioned, and didn't have success. I tried something like this:
struct Synchronize
{
MAKE_MOCK0( Sync, void() );
};
struct Foo
{
MAKE_MOCK0( f, void() );
MAKE_MOCK0( g, void() );
MAKE_MOCK0( h, void() );
MAKE_MOCK0( i, void() );
};
auto seq1 = sequence{};
auto seq2 = sequence{};
auto foo = std::make_unique<Foo>();
auto bar = std::make_unique<Foo>();
auto sync = std::make_unique<Synchronize>();
REQUIRE_CALL( *foo, f() ).IN_SEQUENCE( seq1 );
REQUIRE_CALL( *foo, g() ).IN_SEQUENCE( seq1 );
REQUIRE_CALL( *bar, h() ).IN_SEQUENCE( seq2 );
REQUIRE_CALL( *bar, i() ).IN_SEQUENCE( seq2 );
ALLOW_CALL( *sync, Sync() ).IN_SEQUENCE( seq1, seq2 );
REQUIRE_CALL( *foo, f() ).IN_SEQUENCE( seq1 );
REQUIRE_CALL( *bar, h() ).IN_SEQUENCE( seq2 );
// trigger calls to foo and bar
Trigger1();
Trigger2();
Alas, the ALLOW_CALL was not sufficient to require both sequences to synchronize and FORBID_CALL with an IN_SEQUENCE() was forbidden by the framework as not making sense. I could have inserted REQUIRE_CALL() instead and then added sync->Sync() in the appropriate places in my code that was triggering the expected behavior, but since I could do that, I realized I could just break up the sequences into scoped paragraphs and get the same effect:
{
REQUIRE_CALL( *foo, f() ).IN_SEQUENCE( seq1 );
REQUIRE_CALL( *foo, g() ).IN_SEQUENCE( seq1 );
REQUIRE_CALL( *bar, h() ).IN_SEQUENCE( seq2 );
REQUIRE_CALL( *bar, i() ).IN_SEQUENCE( seq2 );
// trigger calls to foo and bar
Trigger1();
}
{
REQUIRE_CALL( *foo, f() ).IN_SEQUENCE( seq1 );
REQUIRE_CALL( *bar, h() ).IN_SEQUENCE( seq2 );
// trigger more calls to foo and bar
Trigger2();
}
That wouldn't work in the general case (e.g., in your blog post on sequences) because all the actions are triggered by a single indivisible line (there, to dispatcher.readMessage()).
Yes, I know. It's not quite that simple. Sequences are driven by things that happen to them, and the synchronization barrier is not an event that's triggered, it's a rule governing their joint behaviour. Like I mentioned, I need to think a bit about how to implement this.
Using scopes is often the right thing, but as you so correctly observe, it's not always possible.