Mr. Cluey : Library : Recover


The Recovery System

I've never been particularly happy with the testcase recovery system that Segue originally recommended. The syntax of the call to recover base state was needlessly cumbersome - but more important than that, it lacked flexability.

Using RecoverBaseState, as originally implemented, locked test scripts into hitting the same state on three different occassions; scriptEnter, testcaseEnter, testcaseExit. Most of the time, that is fine, but on occassion you will want to write a script that does not recover during the script Enter phase. Or perhaps it is important, for whatever reason, that testcase Enter and testcaseExit leave the system in different states.

Recover.inc is a module which incorporates this additional functionality into the recovery system. While we are at it, we'll take a few swings at cleaning up the commonly used function declarations so that adding new ones becomes something less of a chore.

Cleaning Up OutputMessage

Does this code fragment look familiar?

OutputMessage (STRING sMessage, STRING sWhere, BOOLEAN bReportErrors, LIST lInfo optional)

Yup - the ugly syntax that you must use each time you write a new RBS function can all be blamed on this guy. This implementation would be fine if, in a particular RBS call, you expected to need the flexability of writing one message as an error and the next as a status message. But of course, that functionality never got used - all of the calls to OutputMessage were passed the same sWhere and bReportErrors parameters by RecoverBaseState, which was just passing those values along from one of the Enter or Exit functions.

So let's write a cleaner version. We'll call the function RecoveryMessage (admittedly not a great name for a function). To this function we pass only the information that changes from message to message - sMessage and lInfo. The other two values we are going to leave in variables declared in this module. The values of these variables will be set by the calling function.

private boolean bReportErrors ;
private string sWhere ;

RecoveryMessage (STRING sMessage, LIST lInfo optional)
{
   // code to report messages goes here - you can use the same
   // code that was used inside OutputMessage or something new.
}

scriptEnter ()
{
   bReportErrors = False ;
   sWhere = "scriptEnter" ;

   RecoverBaseState () ;
}

// testcaseEnter, testcaseExit follow same idea.

Note the critical points. RBS no longer requires parameters at all, which makes it easier to write new functions. But more important than that, the information of interest only to the recovery system stays in the module.

Of course, we still will have a bunch of legacy code that uses OutputMessage. If time permitted, you could simply remove OutputMessage from the library entirely. Obsolete code will no longer compile. However, if you are making this change to testware during production, you need to find a different answer.

My suggestion is that you leave OutputMessage in place, but change the implementation of it. First, you convert OutputMessage to a wrapper for RecoveryMessage. In addition, you add an obnoxious message to the code, so that each time a script using the legacy code is used, a nasty message appears to remind you. The updated version of OutputMessage looks like this:

OutputMessage (STRING sMessage, STRING sWhere, BOOLEAN bReportErrors, LIST lInfo optional)
{
   LogError ("OutputMessage is no longer supported.  Please use RecoveryMessage()");
   RecoveryMessage (sMessage, lInfo);
}

Flexable Base States

Our recovery system still calls only one function (RBS) though, so we are still locked into the same one state prison that we had before. Time to design our way out of it.

The goal here is to be able to customize each phase of the recovery independently, without being required to do so. Furthermore, it would be nice to not require any recovery at all, so that you can write throwaway testware without bothering to write an RBS function each time. So we want to call the specific function, if it exists; if it doesn't, then call the generic function. If that too is missing, do nothing.

private _RunRecoverBaseState( string funcRBS ) 
{
   if CallFunctionIfExists( funcRBS ) return ;
   if CallFunctionIfExists( "RecoverBaseState" ) return ;
}

CallFunctionIfExists is described in more detail in Function. The general gist should be clear. We call _RunRecoverBaseState with the name of some function ("Foo"). If the function has been defined, it runs. If not, our old friend RBS gets fired up. Of course, if he is also absent, then the recovery system does nothing, which is what we want.

Taking the liberty of cleaning up the code a touch (by moving the RecoveryMessage variable assignment to a function), we get entry points to the recovery system which look like this:

scriptEnter ()
{
   SetupRecoverReporting ("scriptEnter", RPT_REC_MESSAGE);
   _RunRecoverBaseState( "RecoverScriptEnter" ) ;
}

// similarly for tcEnter and tcExit

Suddenly, customizing our testware recovery couldn't be easier. If, for example, you were writing a script that needed to do something special when exiting a testcase, you might write:

// begin testmain.t
use "recover.inc" ;

RecoverBaseState ()
{
   print( "RecoverBaseState - generic") ;
   // add recover code here
}
RecoverTestCaseExit ()
{
   print("RecoverTestcaseExit") ;
   // add exit specific code here
}

main()
// etc....

Hey Magic! it just works.

Other Odds and Ends

bRecoveringFromError () is moderately handy. This function provides a means of determining if anything went wrong during the test case. This can be handy if you want your application to completely restart if something went wrong, but not if everything seems to be fine.

CheckMemoryLoss and CheckResourceLoss I had high hopes for. I still think that having Partner automatically monitor for different types of leaks is a good idea. Alas, the data I generated wasn't the least bit useful. See Memory and Resources for more details.


Mr. Cluey : Library : Recover

Return to ATS Automated Testing Resources Page