| [ THE CUSTOM TESTING CODE DESCRIBED BELOW WAS REMOVED in r1302567 ] |
| |
| For the 1.7 release, ra_serf grew a new internal feature to "pause" |
| the parsing of (large) XML response bodies. This file intends to |
| document the strategy for testing this new feature. |
| |
| [ We've simply shipped more invasive and difficult code before. |
| However, due to the current attempts to stabilize, and the near-ness |
| of our 1.7.x branch... it seems warranted to apply a bit of |
| testing. ] |
| |
| |
| TESTING STRATEGY |
| |
| It may be possible to arrange for writing a white box test, but I'll |
| leave that to somebody with a more masochistic bent. This section will |
| outline the different scenarios to test, and then how we can adjust |
| the various control parameters to make that happen. |
| |
| There are seven states to the PENDING structure: |
| |
| 1) pending == NULL |
| |
| No pause has (ever) occurred, OR no content has arrived since |
| the parser was paused. |
| |
| 2) pending->head == NULL && pending->spill == NULL |
| |
| This should only happen when some data has been placed into the |
| pending membuf structure, then emptied. |
| |
| The parser may be paused and no content has arrived (yet), or |
| the parser is not in a paused state. |
| |
| 3) pending->head != NULL && pending->spill == NULL |
| |
| A pause occurred, and some content was placed into the |
| membuf. Not enough to spill to a file, however. |
| |
| The parser may be paused, or not-paused. |
| |
| 4) pending->head != NULL && pending->spill != NULL. content in file |
| |
| Enought content has orrived during a paused state that it was |
| spilled into a file. Playback of the pending content *may* |
| have occurred, but it has not (yet) emptied the memory buffer. |
| |
| The parser may be paused, or not-paused. |
| |
| 5) pending->head != NULL && pending->spill != NULL. no content in file |
| |
| THEORETICAL. |
| |
| If a spill file gets created, then *some* content will be |
| written into the file. The content will not be read/removed |
| from the file until the memory buffer is exhausted. Thus, this |
| state is not possible since the spill file could not be |
| emptied since the membuf has not been emptied. |
| |
| Also, once the spill file has been created, we will never |
| write into the memory buffer (for ordering reasons). Thus, we |
| cannot empty both membuf and spill file, and place more |
| content into the memory buffer. |
| |
| At some point in the future, we may decide to place arriving |
| content back into the membuf after the spill file has been |
| exhausted. The code does not do this today. |
| |
| The parser may be paused, or not-paused. |
| |
| 6) pending->head == NULL && pending->spill != NULL. content in file |
| |
| At some point, enough content arrived to construct a spill |
| file. Since that point, the memory buffer contents have been |
| injected into the parser, emptying the membuf. |
| |
| The parser may be paused, or not-paused. |
| |
| 7) pending->head == NULL && pending->spill != NULL. no content in file |
| |
| At some point, enough content arrived to construct a spill |
| file. Since that point, all content (from memory and file) has |
| been injected into the parser. |
| |
| The parser may be paused, or not-paused. |
| |
| Note that all states are doubled, based on the PAUSED flag. |
| |
| There are four operations that occur: |
| |
| 1) network content is present |
| a) If parser is paused, then append content to PENDING. All six(*) |
| PAUSED states must be considered. |
| b) If parser is NOT paused, then: |
| i) If PENDING contains data, then append content to |
| PENDING. Three of the NOT-PAUSED states must be |
| considered: (3), (4), (6) |
| ii) PENDING is empty, so inject content into the parser |
| |
| 2) network content is not present [at this time] |
| a) Exit network processing. The PENDING states are irrelevant. |
| |
| 3) network content completed |
| a) Exit network processing. The PENDING states are irrelevant. |
| |
| 4) process content from the pending structures |
| a) When parser is NOT paused, and PENDING contains data, then |
| take content from the start of PENDING and inject it into the |
| parser. Three of the NOT-PAUSED states must be considered: |
| (3), (4), (6) |
| |
| |
| (*) we don't need to test state (5). |
| |
| |
| |
| INDIVIDUAL TESTS |
| |
| Normal operation will cover: 1(b)(ii), (2), and (3). Thus, we must |
| arrange to test: |
| |
| 1) operation 1(a) with six states |
| 2) operation 1(b)(i) with three states |
| 3) operation 4(a) with three states |
| |
| |
| TEST 1.1 |
| |
| Force the parser to pause the first time, then have arriving content |
| saved to the pending data. |
| |
| Exits in state (3). |
| |
| |
| TEST 1.2 |
| |
| We need to force the parser to pause, then we need content saved (to |
| reach state (3)), then unpause the parser and inject all the content, |
| returning to state (2). Then force the parser to pause again, and |
| stash some data to pending. |
| |
| Exits in state (3). |
| |
| |
| TEST 1.3 |
| |
| Two blocks of content from the network must arrive while the parser is |
| paused, and assume no spill file. The first block moves us from state |
| (1) or (2) into state (3). The second block performs this test. |
| |
| Exits in state (3) or (4). |
| |
| Note: while technically, we want to test the transition to *both* |
| states (3) and (4), the setup requirements for 1.4 tests the second |
| condition. We merely want to add more memory content while already in |
| state (3). |
| |
| |
| TEST 1.4 |
| |
| Pause the parser, then force enough content into pending to create a |
| spill file (now in state (4)). Then have one more block arrive. |
| |
| Exits in state (4). |
| |
| |
| TEST 1.6 |
| |
| Pause the parser, then force enough content into pending to create a |
| spill file (state (4)). Then unpause the parser and force the |
| injection of the memory content (but not the spill file) into the |
| parser to move to state (6). Then pause the parser again and get one |
| more block of content. |
| |
| Exits in state (6). |
| |
| |
| TEST 1.7 |
| |
| Pause the parser, then force enough content into pending to create a |
| spill file (state (4)). Then unpause the parser and force the |
| injection of ALL that content (memory and disk) to move into state |
| (7). Then pause the parser again and save one more block to pending. |
| |
| Exits in state (6). |
| |
| |
| TEST 2.3 |
| |
| Pause the parser, then save one block of content to move to state |
| (3). Unpause the parser and receive one more block of content (before |
| injecting the saved content). |
| |
| Exits in state (3). |
| |
| |
| TEST 2.4 |
| |
| Pause the parser, then save enough content to state (4). Unpause the |
| parser and receive one more block of content. |
| |
| Exits in state (4). |
| |
| |
| TEST 2.6 |
| |
| Pause the parser, then save enough content to reach state (4). Unpause |
| the parser and inject all the memory content, moving to state |
| (6). Stop the injection, then receive one block of content from the |
| network. |
| |
| Exits in state (6). |
| |
| |
| TEST 3.3 |
| |
| Pause the parser, then save enough content to reach state (3). Unpause |
| the parser and inject some content. |
| |
| Exits in state (2) or (3). |
| |
| |
| TEST 3.4 |
| |
| Pause the parser, then save enough content to reach state (4). Unpause |
| the parser and inject some content. |
| |
| Exits in state (2), (3), (4), (6), or (7). |
| |
| Note: if we merely inject from memory, then we exit in (4) or (6). |
| |
| |
| TEST 3.6 |
| |
| Pause the parser, then save enough content to reach state (4). Unpause |
| the parser and inject all memory content to reach state (6). Then |
| inject some disk content. |
| |
| Exits in state (6) or (7). |
| |
| |
| OVERALL TEST MECHANISM |
| |
| If we can get a "large enough" [update] report, then we could create |
| one large internal test that will move through all the necessary |
| states to perform each of the operations. We need control over |
| incoming network blocks, the pause/unpause, and when to process |
| PENDING data. These controls are typically based on the number of |
| outstanding requests in the upate processing. These variables can be |
| precisely controlled except for the network content. Thankfully, serf |
| does tend to "spill out" of serf_context_run() often enough that we |
| may be able to sequence through the test as desired. |
| |
| At the moment, serf states that a response handler (e.g our handler |
| that shoves the incoming data into the XML parser) must consume all |
| content found on the network. A future version of serf may allow the |
| repsonse handler to push back on that. For now, we're applying the |
| push-back as application-level logic. |
| |
| We can implement the entire test suite in libsvn_ra_serf/util.c as a |
| sequence of actions to take for each step. This can be controlled by a |
| global variable to track the step, and an array of actions to take at |
| each step. The code will use a special #define to enable it, and the |
| test will be run manually by a developer by building the appropriate |
| subversion, then executing an "svn checkout". When the steps conclude, |
| the logic will revert to normal. |
| |
| Following are the list of steps. We attempt to minimize this list in |
| order to get the system tested with as few steps/transitions as |
| possible. |
| |
| 1. paused. content arrival is TEST 1.1. step on state (3). |
| 2. paused. content arrival is TEST 1.3. [no spill] |
| 3a. network: unpaused. content arrival is TEST 2.3. [no spill] |
| 3b. loop: no injection |
| 4a. network: unpaused. extra content is TEST 2.3. [no spill] |
| 4b. loop: unpaused. inject all memory content. TEST 3.3. step on state (2). |
| 5. paused. content arrival is TEST 1.2. step on state (3). |
| 6. paused. content arrival is spilled. step on state (4). |
| 7. paused. content arrival is TEST 1.4. |
| 8a. network: unpaused. content arrival is TEST 2.4. |
| 8b. loop: no injection |
| 9a. network: unpaused. extra content is TEST 1.4. |
| 9b. loop: unpaused. inject all memory content. TEST 3.4. step on state (6) |
| 10. paused. content arrival is TEST 1.6. |
| 11a. network: unpaused. content arrival is TEST 2.6. |
| 11b. loop: no injection |
| 12a. network: unpaused. extra content is TEST 2.6. |
| 12b. loop: unpaused. inject all disk content. TEST 3.6. step on state (7) |
| 13. paused. content arrival is TEST 1.7. step on state (6) |
| 14. return to default/normal processing |
| |
| note: "extra content" means that TEST has been previously tested, so |
| we don't really need this particular test. but since the network |
| content is uncontrolled, we may end up (re)testing. |
| note: we advance the step when reaching a particular state. if the |
| state is not mentioned, then we advance once new content has |
| been received (which actually means the same state as before), |
| or we advance after injection to the parser. |
| note: we cannot detect whether content exists in the spill file due to |
| the information we (currently) record. thus, we have to do the |
| transition manually: |
| step 6: writing the spill moves us to state (4) |
| step 9b: injecting all moves us to state (6) |
| step 12b: injecting all moves us to state (7) |
| step 13: appending to spill moves us to state (6) |
| note: after each injection of pending content into the parser, we |
| will pause the parser. that will immediately cause the |
| processing of the PENDING data to stop. |
| |
| |
| IMPLEMENTATION NOTES |
| |
| * gotta figure out low-impact (this can all live in util.c) |
| * array of structures with: paused, inject, force_spill |
| * set ->paused on each step increment since we don't know if the next |
| entry point will be the network or the processing loop |
| * set ->paused on each return from the xml parser callbacks so that we |
| do not have to invade update.c (which wouldn't know when we turn off |
| the debugging at step 14) |
| * the processing loop may clear ->paused and call the pending |
| processing function, which can then reset ->paused and exit if |
| necessary for the current step |
| * there are three content injection steps. transition to next step is |
| straight-forward after injection is complete |
| * transition based on network has three transitions from the unpaused |
| state. |
| * when paused, there are seven network transitions. exit upon reaching |
| a state? yes. occurs *after* network content is saved (the saving of |
| the content is of of the TEST scenarios) |
| * when force_spill is defined, the pending content goes right to disk, |
| independent of the memory_size. since we will put further pending |
| data into the spill file, we don't need to check force_spill ever |
| again. oh... we can just look for step==6 rather than a flag. |
| * transitions occur on five of the seven states (not 1 or 5) |
| |