trompeloeil icon indicating copy to clipboard operation
trompeloeil copied to clipboard

Feature request: Sequencing synchronization

Open mlimber opened this issue 8 years ago • 3 comments

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?

mlimber avatar Mar 03 '17 14:03 mlimber

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.

rollbear avatar Mar 03 '17 15:03 rollbear

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()).

mlimber avatar Mar 03 '17 16:03 mlimber

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.

rollbear avatar Mar 03 '17 16:03 rollbear