Akien MacIain, Senior Automation Engineer, Delta Dental of
Table Of Contents
More Notes About Events And Queues
Introduction
Most of the time in test automation, we try to work with very predictable workflows, where each event happens in a predictable order. The worst case we usually have to deal with is expected error conditions, at which point in our workflow, we add code to detect the error, and direct our code to an error handling path.
However, it’s also the case that getting these workflows correct can often take a lot of trial and error. In addition, there are usually unexpected error conditions, that when encountered, necessitate adding additional pathways into the code. Sometimes those unexpected errors can happen in multiple locations, such as loss of backend connectivity. And sometimes the workflows just change, requiring code changes, and sometimes additional branching if we need to support multiple versions with our test.
Every one of these conditions requires additional maintenance work. And that work usually adds new pathways and, in particular, extra complexity to the test code being constructed.
However, this is not how a human being responds to software. We get a stimulus from it, such as the appearance of a dialog, we digest that, and come up with an appropriate response out of our experience of the software. Our brains can literally process hundreds of possible responses very quickly to find the right one, if we have broad enough experience with the software.
Modeling this kind of approach gives us test software that responds intelligently to the software under test. If the order of steps in a workflow changes, we can deal with it without any code changes. If a new error is detected, we don’t have to add complexity, just add another stimulus/response case.
Assumptions
This project was implemented using HP’s Quick Test Pro, which uses VBScript as it’s main scripting language. One of the limitations of VBScript is it’s lack of true object support. However, for another project, I had built a true object engine called VBScript++, documented elsewhere (TBD). It is assumed that the reader is familiar with QTP, VBScript and most importantly VBScript++.
Overview
The core of this implementation are the paired ideas of events and queues. Each is implemented as a VBScript++ BagClass. The test automation engineer creates groups of these event objects, and places them into queue objects. Queues can contain events, or other queues, or both.
Once a queue (or group of queues) is populated, the root most queue is executed. It cycles through all it’s children until a timeout condition is reached, or until one of the children marks the queue as complete.
Each event represents a condition to watch for. And if that condition is met, and the event is triggered, then it’s code can respond to the condition that has occurred. To give an example, if you wished to log in to a web site, you’d go through several steps:
1) Launch the browser
2) Enter the URL
3) Wait for the login page to be displayed
4) Enter your user name
5) Enter your password
6) Click OK
In traditional test automation, if an unexpected message box came up, such as a printer error message, the script would either halt, or it would have to contain numerous checks at different places in case such a window came up. Some testing tools allow you to set "traps" that will respond to simple message boxes by specifying the message box and the button to click, but if your tool lacks that, or if it’s more complex to deal with the dialog than simply pressing a dismiss button, then the script will be unable to respond to it, and the test will fail for reasons completely unrelated to the test.
In addition, if the order of the steps changes, then the script must be updated. If there are multiple different orders to the same steps, based on product versions, that all must be supported at the same time, then it gets even more complicated.
On the other hand, in the event based system, we can create events that watch for specific conditions and respond to them, regardless of the order they happen in, like so:
Table 1, Login Event List
|
Event Name |
Stimuli |
Response |
Verification |
|
LaunchBrowser |
No browser window is displayed |
Launch browser |
Verify browser is running |
|
NavBrowser |
Browser is open but not to login page |
Enter login URL |
Verify login page is displayed |
|
EnterUser |
Login page is displayed, and user name field is blank |
Enter user name |
Verify user name field has correct value |
|
EnterPass |
Login page is displayed, and password field is blank |
Enter password |
Verify password field has correct value |
|
SubmitLogin |
Login page is displayed, and user name and password fields are not blank |
Click OK |
Verify that browser moves to logged in page |
|
LoggedIn |
Logged in page is displayed |
Mark this test as passed and this queue as complete |
Verification is inherent in the stimuli |
|
LoginError1 |
Credentials do not match error message is displayed |
Fail test, mark queue as complete |
Verification is inherent in the stimuli |
|
PrintMsg |
Print error message is displayed |
Bring print error message to the front Dismiss print error message |
Verify print error message disappears |
In this case, if the print error is displayed after the browser is launched, and the test is unable to populate the fields, the queue will simply keep going until it gets to the event that dismisses the print error, then it will loop around and start at the top again, picking up where it left off, and completing the login process.
As new situations that the automation must deal with occur, we simply add more events to the system. We call this "compartmentalized complexity". While we are in fact adding more complexity, it’s in a compartmentalized manner, and no event impinges on any other.
Implementation
Let’s Start With Events
The core of the system is the event. Each of the lines in the table above represents one event. In our code, an event is a bagclass, and you’ll create a baginstance for each event. Each event has 3 methods that are the core of event functionality. They are: "HasContextStart", "Transform", and "HasContextEnd".
Method:HasContextStart is called during each cycle of the queue, and looks to see if that event’s stimuli exists. So in the example table above, the first entry, named "LaunchBrowser", the stimuli is "No browser window is displayed", and Method:HasContextStart will return True if that condition is met. Functions implemented in HasContextStart *do not wait* for a window to appear. It’s either there now, or it’s not. This keeps the whole queue of events moving quickly.
Method:Transform is where the work of the event happens. In the case of LaunchBrowser, the transform method will launch the browser.
Method:HasContextEnd then will verify that the transform happened correctly. If it did, HasContextEnd will mark the event as complete. If it hasn’t happened correctly, HasContextEnd may throw an error (more on this later), or may take some other action. This is where the bulk of synchronization happens. If we did a transform step, then we wait to see if the window it invoked has appeared in this function. If it doesn’t in a timely manner, this is where we note the error.
In addition to the above, queues contain a plethora of other methods and keys, most of which we’ll cover in more detail later. However, there are a couple of items we do need to cover before going on.
Object Key:Name – Every event must have a unique name. If you reuse the name, the first instance to use that name will get overwritten by the second event to use that name. Name is usually assigned when the object is instantiated. We’ll create one who’s name is LaunchBrowser, like so:
Set temp = ClassEvent.NewObject("name=>LaunchBrowser",NULL)
Object Key:Complete – this object data item is initially set to False. It is used to show that the event has not successfully completed it’s behavior. As long as Key:Complete is False, that event’s HasContextStart will keep being polled during processing. As soon as it’s marked as complete, HasContextStart will no longer be called. Once the event *is* complete, HasContextEnd would set the event to complete, like so:
self("complete") = True
Method:Attach – Used to attach this event to a queue. Like so:
self.Method "attach", array("item",GlobalProcessQueue)
I know that all of this is sort of meaningless without some good examples, but before we can get to the examples, we need to know a just a little bit about queues…
A Quick Word On Queues
The "scaffold" the system rests in are bagobjects called queues (implemented as ClassProcessQueue). These are container objects which contain the events to be triggered. Queues can also contain other sub queues, and any data to be shared amongst the items in that queue.
The system comes with one queue already instantiated once the file is loaded into QTP. That baginstance is called GlobalProcessQueue. Other queues are generally spawned from the global queue. They can also be generated as stand alone queues for specific needs.
The most critical element of queues is Method:Process. This is used to kick off a queue. Once it’s started, it will loop through all the events in the queue that are relevant until either one of the events marks the queue as complete, or until it’s looped a certain number of times with no events triggered (a timeout condition).
Now For An Example
Let’s start with the basics. We’re going to create a variable to put our instance in, called temp. It’s called that specifically because in later examples, we are going to reuse the variable. After that, we’re going to create an event called LaunchBrowser to put into it. (Please note: In QTP, the methods for the events cannot be in the test, they *must* be in a library file.) So we will create an instance, then attach it’s methods to it. After that, we’ll attach the event to the queue, and execute the queue.
One quick note on VBScript++, one of the features of this library is that we can attach methods to instances. In some languages, to attach a method to an instance you’d first have to create a subclass with the new method, and then instantiate that. With VBScript++, this isn’t necessary.
Now let’s see how this looks in code:
Dim temp
Set temp = ClassEvent.MakeObject ("name=>LaunchBrowser", NULL)
Now we take advantage of one of the fact that we can add methods directly to an instance:
Function Event_LaunchBrowser_HasContextStart(self, args)
Event_LaunchBrowser_HasContextStart = Browser().Exist(0)
End Function
temp.ApplyMethod "HasContextStart","vector=> Event_LaunchBrowser_HasContextStart"
Function Event_LaunchBrowser_Transform(self, args)
Browser().Launch()
End Function
temp.ApplyMethod "Transform","vector=> Event_LaunchBrowser_Transform"
Function Event_LaunchBrowser_HasContextEnd(self, args)
If Browser(0).Exist(60) Then
self("complete") = True
End If
End Function
temp.ApplyMethod "HasContextEnd","vector=> Event_LaunchBrowser_HasContextEnd"
Having attached all the methods to the event BagObject, we can now attach that to a queue:
temp.Method "Attach", Array("item", GlobalProcessQueue)
… add additional events to the queue in the same manner here …
And finally, we execute the queue:
GlobalProcessQueue.Method "Process", NULL
And that’s the most basic setting up and executing an event!
In this example, the queue will execute the event, and then loop around for a while until it times out. The reason for this: We haven’t specified a queue ending condition.
The queue can be marked complete in the same manner as the event. For instance:
Set temp = ClassEvent.MakeObject ("name=>LoggedIn", NULL)
Function Event_LoggedIn_HasContextStart(self, args)
Event_LoggedIn_HasContextStart = Browser().Page("Logged In OK").Exist(0)
End Function
temp.ApplyMethod "HasContextStart","vector=> Event_LoggedIn_HasContextStart"
Function Event_LoggedIn_Transform(self, args)
' this next line marks the current queue complete.
self("queue")("complete") = True
End Function
temp.ApplyMethod "Transform","vector=> Event_LoggedIn_Transform"
Function Event_LoggedIn_HasContextEnd(self, args)
self("complete") = True
'also mark test or test step successfully completed here
End Function
temp.ApplyMethod "HasContextEnd","vector=> Event_LoggedIn_HasContextEnd"
temp.Method "Attach", Array("item", GlobalProcessQueue)
This code adds a new event, one from table 1 above, which detects the successful completion of the event group listed in the table. Which is to say, we’re now logged in successfully. Once that condition has been detected, we mark the queue complete, which halts further event polling.
To complete this login procedure, we simply keep adding event BagObjects until we’ve covered all the events in the table.
A Note On This Design
I’ve been doing test automation for a long time, and I’ve noticed a couple of things. An awful lot of folks do not verify each logical test step. They assume that if they populated the field without an error, then the test step completed ok. Which sometimes means the test fails in some location several steps past the actual error.
In addition, many folks fall into the trap of writing their test in such a way that the test is assumed to have passed until there is an explicit failure. Best practices in many other technical fields dictate with good reason that a test should be assumed to fail until all the key validations have passed.
The reason for 3 key routines in the events is to explicitly separate the validation from the transform. Do the transform in Method:Transform. Then verify that it worked in the Method:HasContextEnd. And only then mark it completed. This helps us adhere to the best practices without having to think too much about the structure for doing it.
Advanced Topics
More Notes About Events And Queues
Making a New Queue
There are two ways to make a new queue. The most common one for simply defining a static group of events in subqueues is like this:
Set mySubQueue = GlobalProcessQueue.Method ("Spawn", NULL)
The other way is used when making stand alone queues:
Set myStandAloneQueue = ClassProcessQueue.NewObject("name=>myQueue",NULL)
On Speed
The Method:HasContextStart functions get called over and over again. This is how we look for events in this system, because there isn’t a way for actions of the application under test to push events into a queue. So, we model the human response, we look for what we’re familiar with. And we do it by writing code in HasContextStart which basically says "does our world look like this? No? How about this?" and so on. Every one of those could get called hundreds or even thousands of times depending on how we design our queues.
Given all these calls, it’s imperative that there are no delays in HasContextStart, nor really anything that isn’t absolutely necessary. If you find yourself thinking of closing a window in a HasContextStart, don’t. Move it to it’s own event.
On Designing HasContextStart
One needs to keep in mind that this is a kind of rules engine. HasContextStart is an embodiment of rules in code. So it’s important to have enough checks in your HasContextStart that you can be sure that it is only responding to the condition you want. So for instance this is not a good idea:
Function Event_Foo_HasContextStart(self, args)
Event_Foo_HasContextStart = Browser().Page("logged in").Exist(0)
End Function
Because your logged in page might well appear right after logging in, but if this event is not marked complete, and you move on to some other page, such as ordering a product, and the logged in page is no longer displayed, this event will be triggered again.
On Referencing Each Other
First off, it’s important to remember that every queue and event must always have a name that is unique within it’s parent queue. Reusing a name will cause an event or queue to be overwritten, as the name is used as the key in the queue storage dictionary.
Events can send messages to their queue’s by referencing the key:queue (which gets populated at instantiation). In the example below, we’re going to return any error tags that have been posted to the queue. Remember that all these bagobjects are essentially turbocharged dictionaries, so the event class contains a key:queue, that references the queue bagobject, which in turn contains key:error_tags. We’d reference it like so:
t = self("queue")("error_tags")
Queue’s contain a key:data, which is the actual dictionary of items in the queue. If you need to refer to another event in the queue, you’d do that like this:
self("queue")("data")("otherEventName").Method "reset", NULL ' resets the event named "otherEventName"
Queues can contain queues can contain queues can…. You get the idea. You might have something that looks like this:
self("queue")("queue")("queue")("data")("otherEventName")
It’s important to remember that the Key:queue (common to both events and queues) is set *at instantiation time*. Also set at attachment time is the Key:master_queue. It’s set to the *rootmost* queue at the time of attachment. If you create a queue, and populate it, and then attach it to another queue, the Key:master_queue in the contained events will not point at the new rootmost queue. So sometimes we want to do this:
Set x = self.Method("FindMaster", NULL)
And then from there navigate to the item we want.
On Completion
Any event which may be reused, such as the event in table 1 called PrintMsg, which dismisses a print error message, should NOT mark itself complete when done. That way, if it’s condition is met again, it can respond again.
At the same time, we can base a HasContextStart on the completion of another event. Like so:
Function Event_Foo_HasContextStart(self, args)
Event_Foo_HasContextStart = self("queue")("data")("bar")("complete") AND Dialog("x").Exist(0)
End Function
In addition, there are two ways to mark something completed. One is via key:complete, as described above, the other is via the Method:Complete. Let’s just look at several examples:
self("complete") = True ' marks me complete
self.Method "complete", NULL ' marks me complete
self("queue")("data")("otherEventName")("complete") = True ' completes event named "otherEventName"
self("queue")("data")("otherEventName").Method "complete", NULL ' completes event named "otherEventName"
self("queue")("complete") = True ' marks the queue complete, but not it’s children
self("queue").Method "complete", NULL ' marks the queue and all it’s children complete
An important thing to note about those last two, once the queue is marked complete, it will stop executing, whether all it’s children are marked complete or not.
In the same way that there are method:complete for both queues and events, there’s also the complimentary method:reset for both as well, which "uncompletes" them.
On Data
In general, all the data that you’ll be using that is shared between the events in a queue should be stored in the queue itself. For instance, in our login example in table 1 above, we probably will need login credentials. So before we invoke the queue, we would set up the data like so:
loginQueue("username") = userNamePassedInFromOutside
loginQueue("password") = passwordPassedInFromOutside
loginQueue.Method "Process", NULL
And within the events, we’d do something like this:
Function Event_EnterUser_Transform(self, args)
Browser(n).Page("Login").WebEdit("userid").Set self("queue")("username")
End Function
In addition to posting data to the queue from outside, events can post data to each other in their queue object as well.
Errors And Error Handling
There are two kinds of events. Ones for handling errors and ones for regular events. Regular events are NOT polled when an error has been raised, and when an error has been raised, ONLY error handlers are polled.
It’s important to note that the error information is stored at the queue level, not the event level. That said, all the *methods* are relevant to both the queue and the event bagobjects.
It’s also important to note that there *are no error handling events* in table 1. Those are all normal events. Before we look more in depth at error handling events, let’s create an example. Table 2 below adds events to the queue in Table 1.
Table 2, Login Retry
|
Stimuli |
Event Name |
Error handler |
Transform |
Verification |
|
Login provided is already in use error |
LoginBusy |
No |
Throw error with tag loginbusy |
None needed |
|
Error raised w tag loginbusy |
CredentialUpdate |
Yes |
Have we used all the available credentials? YES: Throw new error with tag allloginsbusy NO: update to next credentials, mark login events not complete, clear error |
None needed |
|
Error raised w tag allloginsbusy |
AllLoginsBusy |
Yes |
Log a test abort condition and note in the log that we could not complete the test because all available logins were busy. Mark queue as complete. |
None needed |
Creating Error Handlers
This categorization between normal events and error handling events is controlled by the ClassEvent key:error_handler. To instantiate an error handler, we do this:
Set temp = ClassEvent.MakeObject ("name=>CredentialUpdate|error_handler=><Eval>True", NULL)
Throwing An Error
To throw an error, we do this:
self.Method "ErrorThrow", "tags=>loginbusy|description=>userid was already in use"
The key:tags can contain any string you wish to use. Common ones include fatal, fail, warn, info, and debug. But any string may be used. Commas are reserved characters, and used as the separator.
The key:tags can contain multiple tags, which allows more than one event to respond to an error condition. Once an error has been thrown, any error handler event may watch for that error. In general, a good practice is to use the tags in your HasContextStart condition:
Function Event_CredentialUpdate_HasContextStart(self, args)
Event_CredentialUpdate_HasContextStart = Contains(self("queue")("error_tags"),"loginbusy")
End Function
Reraise
If an error handler determines that the error cannot be handled at this level, it can reraise the error, bouncing it up to the next queue in the tree. This is done like so:
self.Method "ErrorReraise", NULL
This causes the whole queue, and all it’s events, to be marked complete.
If the error is reraised all the way to the root, and no handler intercepts it, then a fatal error is raised by the root queue.
Clearing An Error
Once an error handler has successfully *cleared* an error, it can use Method:ErrorClear to clear the error state:
self.Method "ErrorClear", NULL
Any error handler which is *self contained*, which is to say, can clear the error on it’s own, and the error is one that may recur again; should not mark itself complete when it’s done.
Error Handlers Resetting Completed Events
In general, if any error handler creates a condition where some steps that had already been done should be repeated, we use the Method:Reset to clear the completed state. Depending on whether you’re resetting the current event, another event, or the whole queue, your code will look something like this:
self.Method "reset", NULL ' resets the current event
self("queue").Method "reset", NULL ' resets the queue
self("queue")("data")("otherEventName").Method "reset", NULL ' resets the event named "otherEventName"
Detailed Queue Processing
One of the last pieces of complexity around this model is how queues are processed. There are a number of parameters that control execution of a queue. Most of them are detailed later, but there’s one aspect that merits it’s own section. And that’s how queues are processed.
The most obvious way to process events is to simply put them all in one queue and run through them again and again until they’re all done with their workflow, and that’s what we’ve looked at so far. However, there are a number of other possibilities:
The External Called Queue
Sometimes you might notice something happen, and need to take a complex action in response to it, but don’t want to necessarily put all your events in your main queue. One way to do this is to create a second queue, and call it from within one of the first queue’s events. When you do this, processing on your main queue stops, and your external called queue executes to completion, at which time execution picks up on the next line of your event. Like so:
Function Event_LoginToVPN_Transform(self, args)
VPNLoginQueue.Method "Process",Array("username",self("queue")("username"),_
"password",self("queue")("password"))
End Function
This will cause the VPNLoginQueue to be executed and completed before resuming execution on the main queue. This allows us to break up our test code into discrete, reusable components.
Serial And Parallel
If you’re only using one queue, then each event will be polled, then it’s successor, and so on until all the queue is either complete or has timed out.
But sometimes, you need to have finer control than that. An example might be if you wanted to watch for a range of possible responses from an application, which aren’t determinable in advance. These might be based on real world impacts on the test environment, for instance.
So the way we manage that is to have different queue execution modes. These modes are called "serial" and "parallel". The default is parallel. This mode selection is controlled by the queue object key:stepping_type. Let’s look at an example:
Root queue — queue 1
Event 1.1
Event 1.2
Event 1.3
queue 2
Event 2.1
Event 2.2
Event 2.3
So if everything is set to it’s defaults, and we tell the root queue to execute itself, the *events* will be processed in this order:
Event 1.1
Event 2.1
Event 1.2
Event 2.2
Event 1.3
Event 2.3
So the first sub-queue gets one processing cycle (though the exact number is controllable, more on that later), and then the second one. And so on.
Now let’s take a different example. Let’s say you want to run through a series of checks for conditions not related to your test, after each test related event. To do this, using the example above, we’ll change the behavior of queue2:
MasterQueue("data")("queue2")("stepping_type") = "serial"
Now the execution will look like this:
Event 1.1
Event 2.1
Event 2.2
Event 2.3
Event 1.2
Event 2.1
Event 2.2
Event 2.3
Event 1.3
Event 2.1
Event 2.2
Event 2.3
So after one event from queue1, all the events from queue2 are run. And then we go back to queue1 for it’s next event.
Tips, Tricks And Warnings
Loops
We can build loops into the system as well. We could have an event which does the following psudocode:
HasContextStart
If last event is done OR queue variables are empty
Transform
Read data from spreadsheet
If we’re at end, mark queue complete
Else populate the queue variables
And reset all the events
HasContextEnd
Do nothing!
And that will let us implement a data set test that runs in a loop until it runs out of data. Note that this event never marks itself complete. It doesn’t need to.
Classes
One of the possible useful things to do with this framework is build event templates. You can create a new subclass of event that has all the methods defined in it. You can define the queue based subclass to "build out" it’s list of events based on data passed to the queue. Then you instantiate this class, give it it’s data, and run it. Looking at our login example again, I’ve created an example below. let’s say we did it this way:
Dim ClassMyLoginQueue
Set ClassMyLoginQueue = classBase.MakeClass("ClassMyLoginQueue",NULL,"self.is_virtual=><eval>False")
Function ClassMyLoginQueue_BuildAndRun(self, args)
' you should check here to see that the queue has the correct data keys
' and report out however you want to design it of that failure case
' in our case, it’s the username and password fields
' with that said, on with the show…
Dim results
Set results = BagDictMake(NULL,NULL) ' to put results in
reults("returncode") = 0
Dim temp
Set temp = ClassEvent.MakeObject ("name=>LaunchBrowser", NULL)
temp.ApplyMethod "HasContextStart","vector=>Event_LaunchBrowser_HasContextStart"
temp.ApplyMethod "Transform","vector=> Event_LaunchBrowser_Transform"
temp.ApplyMethod "HasContextEnd","vector=> Event_LaunchBrowser_HasContextEnd"
self.Method "Attach", Array("item", temp)
' now i’m not going to list every event in this example. We still have to have defined
' the methods, but we reuse the same ones for every instance of this ClassMyLoginQueue
' so you just imagine all the rest of the events being defined and added here
' and we’ll move on to the next part of the game
self.Method "process", NULL
' and more code here to copy your result information into the result object
Set ClassMyLoginQueue_BuildAndRun = results
End Function
Dim myInstance, result
Set myInstance = ClassMyLoginQueue.MakeObject("name=>LoginAttempt1", NULL)
myInstance("username")="bob"
myInstance("password")="foo"
Set result = myInstance.Method("BuildAndRun",NULL)
One could do this so many other ways as well. We could flesh out the events in the constructor, instead of this method, we could verify the login keys and do the run in this method. Or even do everything in the constructor, which would give us a "throw away instance" of the queue, just keeping the result data, like so:
Dim result
Set result = ClassMyLoginQueue.MakeObject_
("name=>LoginAttempt1|username=>bob|password=>foo", NULL)("constructor.result")
Don’t Reuse Event Instances In Multiple Queues
If you need to reuse an event in multiple queues, create a class, and create multiple instances of the class. Each event carries a key:queue that points to the queue it’s attached to. You can attach it to multiple queues, but if you do, it will overwrite this key, and you’ll lose the pointer to the original queue.
Complete Reference
Alpha Order
ClassEvent Key:Completed
Returns boolean of "is this event completed". The initial value is set in the constructor.
ClassEvent Key:Enabled
Returns boolean of "is this event enabled". The initial value is set in the constructor.
ClassEvent Key:Error_handler
Non error events are executed as long as the error flag is False, which is the default. Only error events are executed if the error flag is set True.
ClassEvent Key:Name
The name of this event, must be unique.
ClassEvent Method:Attach
Attaches the specified item to the current queue.
ClassEvent Method:Complete
Marks this event as complete.
ClassEvent Method:Constructor
Initializes the instance.
ClassEvent Method:ErrorClear
Clears the error flags.
ClassEvent Method:ErrorReraise
Marks this queue complete, and bounces the error up to the next queue.
ClassEvent Method:ErrorThrow
Sets the error flags.
ClassEvent Method:FindMaster
Returns the current highest level master queue (as distinct from the Key:master_queue which is the master as of attachment time).
ClassEvent Method:HasContextEnd
Verifies that Transform was successful and marks this event complete if that’s what’s appropriate (this stub is intended to be overwritten).
ClassEvent Method:HasContextStart
Checks to see if the start condition exists (this stub is intended to be overwritten).
ClassEvent Method:Process_Items
Used internally. Processes this event.
ClassEvent Method:QueuePingRecieverForContextStart
Used internally, this is where the determination is made about whether to call HasContextStart based on key:enabled key:complete and queue key:error_flag.
ClassEvent Method:Reset
Marks this event as NOT complete
ClassEvent Method:Transform
Does the event action (this stub is intended to be overwritten).
ClassProcessQueue Key:Complete
Flag created by constructor which flags this event as complete. Default is False.
ClassProcessQueue Key:Enabled
Flag created by constructor which flags this event as enabled. Default is True.
ClassProcessQueue Key:Error_flag
Boolean indicator of an error having been thrown.
ClassProcessQueue Key:Error_tags
Descriptive tags about the error for other events to process.
ClassProcessQueue Key:Error_description
Descriptive message about the error. Intended for human reader.
ClassProcessQueue Key:Item_counter
The current record being processed in the queue.
ClassProcessQueue Key:Last_item_counter_checked
The index number of the last item executed.
ClassProcessQueue Key:Last_item_name
The name of the last event executed.
ClassProcessQueue Key:Loops_before_halting
The number of loops that can occur with no actions having been taken before the processing halts.
ClassProcessQueue Key:Loops_since_last_triggering
The number of times the entire set has executed since the last event found a triggering stimuli.
ClassProcessQueue Key:Master_queue
Set by Method:Attach and Method:Spawn, sets the root owning queue *at the time of attach*.
ClassProcessQueue Key:Name
The name of the queue.
ClassProcessQueue Key:Queue
Set by Method:Attach and Method:Spawn, points to the owning queue.
ClassProcessQueue Key:Seconds_to_pause_at_end
Default zero. Number of seconds to delay at the end of each queue before running the first event again.
ClassProcessQueue Key:Stepping_length
The number of items to be processed during each "interval". Default is 1.
ClassProcessQueue Key:Stepping_type
String of "serial" or "parallel", refers to how the queue will be processed at each "process" method call (referred to as an "interval"). Can process the number of events specified in "stepping_length" when set to parallel, and will process all the events in the queue once if set to "serial"
ClassProcessQueue Method:Attach
Attaches the specified item to the current queue.
ClassProcessQueue Method:Complete
Marks all the items attached to this queue as complete.
ClassProcessQueue Method:Constructor
Sets up the queue item, key:complete, and key:enabled.
ClassProcessQueue Method:ErrorClear
Clears the error flags.
ClassProcessQueue Method:ErrorReraise
Marks this queue complete, and bounces the error up to the next queue.
ClassProcessQueue Method:ErrorThrow
Sets the error flags.
ClassProcessQueue Method:FindMaster
Returns the current highest level master queue (as distinct from the Key:master_queue which is the master as of attachment time).
ClassProcessQueue Method:Process
Executes this queue.
ClassProcessQueue Method:Process_Items
Processes a subset of the items attached to this queue (how many items is set by the key:stepping_length and by the key:stepping_type).
ClassProcessQueue Method:Reset
Marks all the items attached to this queue as NOT complete.
ClassProcessQueue Method:Spawn
Creates a new queue which is attached to this queue.
Summary
This framework component lets me build workflows that are not order dependent. It also lets me componentize parts of workflows in reusable ways. It’s an idea I’ve been thinking about for a very long time, and am glad I finally got the chance to build it.
You’re welcome to use this framework, subject to the GNU General Public License version 3 (GPLv3) (http://www.opensource.org/licenses/gpl-3.0.html). I am up for answering questions as time permits, but offer no promises about it’s fitness for any particular purpose, nor do I commit to issuing bug fixes (though if you tell me of one, I probably will fix it).
Attachments
The download for this article contains one file (aggregations.logging.vbs), which is an aggregation of several files:
· ExtensionsBase.vbs: A general library of extensions to VBScript. Handy in lots of ways and is mostly needed for the VBScript++ implementation.
· ExtensionsStrings.vbs: A general library of string specific extensions to VBScript. Also handy.
· ExtensionsFiles.vbs: File related VBScript extensions.
· ExtensionsDates.vbs: Date string manipulation extensions.
· VBScript++.vbs: The actual VBScript++ implementation, including the BagDict class, a number of required support functions, and the basic objects we’ve defined so far.
· vbs++_ActionsAndEvents.vbs: The event manager library, defining the event and processqueue classes.
Posted in Using Classes, VBScript Techniques


Yaron Assa




March 12th, 2010 at 12:17 pm
Wow, this looks very promising. A very nice implementation of your ‘dictbag pattern’. I’d like to experiment with it when I can find some time. Unfortunately, the download link disappeared from your article. Can you please add it (as a comforting .txt file like you did last time)?
April 13th, 2010 at 9:59 pm
Sorry I didn’t see this sooner. *grin* I’ll pass that along to the webmaster (I can’t edit it directly, he posted it for me).
April 13th, 2010 at 10:02 pm
Fixed!