diff --git a/coroutines/crf_constraints.md b/coroutines/crf_constraints.md new file mode 100644 index 000000000..c54809c9d --- /dev/null +++ b/coroutines/crf_constraints.md @@ -0,0 +1,32 @@ +## CRF Constraints + +### 1 +Test setup in coroutine => Test coroutine must run as soon as its constructed, so as to allow it to +create mock objects, watches etc before the mock source runs. Also in phase 2, coroutine can create mock +sources. + +### 2 +Mock args passed by ref => MUT must not do anything between making a mock call and the coroutine body +receiving the args. Note that the coroutine CAN run during this period, since the args are owned +by the mock source. + +### 3 +Mock return passed by ref => Test coroutine must not do anything between Return() and the mock source receiving the +return value. Mock source could in principle run during this period, since coro owns the return object, but +in practice it won't because it's waiting for the return value. + +### 4 +Updated cardinality state => coroutine must run so that it can do SATISFY(), RETIRE() or run to exit BEFORE +allowing GMock/CotestWatcher code to query eg IsSatisfied() etc. This is extra_iteration_requested reason +inside TestCoroutine::DestructionIterations(). + +### 5 +Global mock handler finding => After getting an event that is a mock call, test coroutines must call DROP() +ACCEPT() or RETURN() on the event before doing anything elase with the cotest UI. This is because the +process of deciding which expectation or coroutine will handle the call is global and must complete before +anything happens that could generate more mock calls. + +### 6 +True event sequence => Most of the time, a LAUNCH() or RETURN() should be followed by NEXT_EVENT() because +they will usually lead to an event for the test coro to pick up and handle. There is an exception in the +case of a second coroutine whose response is to exit. See test case ObserverQueue. diff --git a/coroutines/design_notes.md b/coroutines/design_notes.md new file mode 100644 index 000000000..ee58e6c44 --- /dev/null +++ b/coroutines/design_notes.md @@ -0,0 +1,13 @@ +## Design Notes + + - A "corouine" is officially a class or factory object bound to a coro body. + It creates an RAII object, so an instance must be created and should be in + a local scope (i.e. of the test case). Coroutines are implemented as lambdas + because a lambda can be a C++20 coroutine, and we can capture the mock objects. + This breeches S/S guidelines, but the UI is designed to make unsafe usafe difficult. + + - All cardinality functionality belongs interior to the coroutine. Therefore WillOnce(), Times() + etc are not supported when routing calls to a coroutine. That's why the interior UI + should not say "EXPECT". Filtering (ultimately) can be done in interior or exterior, + so we don't want to be too imperitive in the terminology here because the coro can just + drop the call. Call it WATCH_CALL(). diff --git a/coroutines/sheared-ordering.md b/coroutines/sheared-ordering.md new file mode 100644 index 000000000..d992ce13e --- /dev/null +++ b/coroutines/sheared-ordering.md @@ -0,0 +1,54 @@ +## Notes on hyper-flexible ordering + +This analysis is of a version of cotest that allows test corotuines more +flexibility, so that they can queue up incoming events and then +pull them from the queue out of order. + +### NEXT_EVENT_ONLY_FROM(DC) or BLOCK_EVENT() + - Consider launches A() and B() invoking mockA() and mockB() respectively. Suppose the + test case requires: + - A() must be called before B() eg because at least one has side effects and + they affect each other's behaviour. + - But mockA()'s handling in the test depends on an arg to mockB() + - Assume it isn't enough for mockB() to affect mockA()'s return value + (because we could leave mockA.RETURN() until after mockB.ACCEPT()) + - Assume it isn't enough to do checking using EXPECT_EQ() etc either + - This leaves the case where the decision of whether to accept or drop + mockA() depends on mockB()'s argument. + - Now, based on GMock design: + - In order to have mockB() arguments we need to have entered ShouldHandleCall() + on B(). If we look at B's PreMock, we might see arguments for a mock + call that is accepted by a higher prio expectation or watch. So + higher prio expectations/watches on mockB() can affect the arg values we see. + - We have to decide whether to accept or drop mockA() before we exit + ShouldHandleCall() on A(). Our decision here will obviously affect + the lower prio expectations/watches on mockA(). + - So we're stuck with searching mockB() higher priorities before mockA() + lower priorities, which is out of sequence. Consequences: + - Thread safety: we'd re-enter GMock's call hander search. At present this + causes a deadlock, but that could be changed. + - Expectations would be OK, if we checked eg mockA() highers, + mockB() highers, mockA() lowers, mockB() lowers. This generalises + to checking in sequence within each prio level. An expectation only + exists at one prio level, so it would see everything in the right order. + Call this a shear ordering. + - If another coro is watching mockA() at a lower prio and mockB() at + a higher prio, and it expects to see mockA() first, then we have + conflicting requirements. If the original coro gets its way, then + this coro will see mockB() and may drop it, fail a check, etc. + So shear ordering is not good enough for coroutines. + - Note that this is not an isue of side-effects inside ShouldHandleCall() + and purely relates to the order in which mock calls are passed to it. + +### Architectural interpretation + - Coroutines should be able to infer the order in which mock calls + are happening from the order in which they are passed to + ShouldHandleCall(). Shear ordering prevents this. + - Without threads, this can be well-defined because coroutines have + well-defined ordering. + - With threads, we'd probably want an overall serialisation/rendez-vous point. + - To provide for the above scenario without using shear ordering, + we need to intervene and change the global ordering as seen + by GMock search. This should be explicit, since it will have side + effects on other coroutines. + - Leads to the PreMockSynchroniser and CRF constraint #5