diff --git a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js
index f6ba739..e23f87a 100644
--- a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js
+++ b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js
@@ -157,16 +157,137 @@ function $X(xpath, contextNode, resultType) {
return null;
};
-
//=============== Call/Scope Stack handling ===============
var symbols = {}; // command indexes stored by name: function names
var blockDefs = null; // static command definitions stored by command index
var callStack = null; // command execution stack
+ var contextManager; // makes block scoped variables work
+ // intentionally global var
+ storedVarsGlobal = storedVars;
+ /**
+ * Manages variable scope for functions, blocks, etc.
+ * @class ContextManager
+ */
+ function ContextManager() {
+ // intentionally global var
+ storedVarsLocal = Object.create(storedVarsGlobal);
+ storedVars = storedVarsLocal;
+ this.contexts = [];
+ this.enter();
+ }
+ /**
+ * Enters a new variable context.
+ * @methodOf ContextManager.
+ */
+ ContextManager.prototype.enter = function enterContext() {
+ var _o = storedVars;
+ var context = {
+ here : Object.create(storedVars),
+ back : _o
+ };
+ this.contexts.push(context);
+ storedVars = context.here;
+ storedVarsLocal = context.here;
+ };
+ /**
+ * Exits to the previous variable context.
+ * @methodOf ContextManager.
+ */
+ ContextManager.prototype.exit = function exitContext() {
+ if (this.contexts.length > 0) {
+ var context = this.contexts.pop();
+ storedVars = context.back;
+ storedVarsLocal = context.back;
+ } else {
+ throw new Error("No context to exit from");
+ }
+ };
+ /**
+ * Resets to the top variable context.
+ * @methodOf ContextManager.
+ */
+ ContextManager.prototype.reset = function exitContext() {
+ if (this.contexts.length > 0) {
+ storedVars = storedVarsGlobal;
+ storedVarsLocal = storedVarsGlobal;
+ this.contexts = [];
+ this.enter();
+ } else {
+ throw new Error("No context to exit from");
+ }
+ };
+ /**
+ * Stores variable into the context chain in the last parent where it was
+ * defined. This allows values to bubble up to their relevant parent so that
+ * you don't have to pass everything in global scope. The variable must exist
+ * in a parent scope in order to be caught there, otherwise it will get to
+ * the global scope and be set there.
+ * @param {String} propName The name of the variable.
+ * @param {Mixed} val Any value you could set for a variable.
+ */
+ ContextManager.prototype.storeAtClosestContextWithPropName =
+ function storeAtClosestContextWithPropName (propName, val) {
+ var out = undefined;
+ var idx = this.contexts.length - 1;
+ while (idx >= 0 && out === undefined) {
+ if(this.contexts[idx].here.hasOwnProperty(propName)) {
+ this.contexts[idx].here[String(propName)] = val;
+ out = true;
+ }
+ idx--;
+ }
+ if(out === undefined) {
+ storedVarsGlobal[String(propName)] = val;
+ $$.LOG.warn(String(propName) + " not found, setting global variable");
+ }
+ };
+ contextManager = new ContextManager();
+ /**
+ * State information about the cache.
+ * currentCaseTitle is the current case displayed in the UI
+ * activeCaseTitle is the cashed case where commands are actually being pulled
+ * from.
+ * commandIndex is the current index of the commands array in the active case
+ * suites are the cached test suites commands, symbols, and blockDefs
+ */
+ var cachedCommandsData = {
+ initializing : true,
+ currentCaseTitle : '',
+ activeCaseTitle : '',
+ commandIndex : 0,
+ suites : Object.create(null),
+ /**
+ * Caches information about the current test case
+ */
+ cacheCommands : function cacheCacheCommands(symbols, commands, blockDefs) {
+ cachedCommandsData.initializing = false;
+ cachedCommandsData.suites[cachedCommandsData.currentCaseTitle] = {
+ 'symbols' : symbols,
+ 'commands' : commands,
+ 'blockDefs' : blockDefs
+ }
+ },
+ /**
+ * Starts a new suite cache.
+ */
+ init : function cacheInit() {
+ cachedCommandsData.initializing = true;
+ var _mytitle = testCase.title || "untitiled";
+ // the current case displayed in the IDE
+ cachedCommandsData.currentCaseTitle = String(_mytitle);
+ // the current case for looking up functions
+ cachedCommandsData.activeCaseTitle = String(_mytitle);
+ // the index used for executing commands, this is decoupled from the
+ // testCase.debugIndex because what's being executed doesn't always
+ // move the row cursor in the UI anymore.
+ cachedCommandsData.commandIndex = 0;
+ }
+ };
// the idx of the currently executing command
function idxHere() {
- return testCase.debugContext.debugIndex;
+ return cachedCommandsData.commandIndex;
}
// Command structure definitions, stored by command index
@@ -184,7 +305,13 @@ function $X(xpath, contextNode, resultType) {
// retrieve the blockDef at the given command idx
function blkDefAt(idx) {
- return blockDefs[idx];
+ var where;
+ if(cachedCommandsData.initializing === true) {
+ where = blockDefs[idx];
+ } else {
+ where = cachedCommandsData.suites[String(cachedCommandsData.activeCaseTitle)].blockDefs[idx];
+ }
+ return where;
}
// retrieve the blockDef for the currently executing command
function blkDefHere() {
@@ -242,33 +369,43 @@ function $X(xpath, contextNode, resultType) {
if (!this.started) {
this.started = true;
this.debugIndex = testCase.startPoint ? testCase.commands.indexOf(testCase.startPoint) : 0;
+ cachedCommandsData.commandIndex = 0 + this.debugIndex;
+ cachedCommandsData.activeCaseTitle = String(cachedCommandsData.currentCaseTitle);
}
else {
if (branchIdx !== null) {
$$.LOG.info("branch => " + fmtCmdRef(branchIdx));
- this.debugIndex = branchIdx;
+ if(cachedCommandsData.activeCaseTitle === cachedCommandsData.currentCaseTitle) {
+ this.debugIndex = branchIdx;
+ }
+ cachedCommandsData.commandIndex = branchIdx;
branchIdx = null;
}
else {
- this.debugIndex++;
+ if(cachedCommandsData.activeCaseTitle === cachedCommandsData.currentCaseTitle) {
+ this.debugIndex++;
+ }
+ cachedCommandsData.commandIndex++;
}
}
// skip over comments
- while (this.debugIndex < testCase.commands.length) {
- var command = testCase.commands[this.debugIndex];
+ while (cachedCommandsData.commandIndex < cachedCommandsData.suites[String(cachedCommandsData.activeCaseTitle)].commands.length) {
+ var command = cachedCommandsData.suites[String(cachedCommandsData.activeCaseTitle)].commands[cachedCommandsData.commandIndex];
if (command.type === "command") {
return command;
}
- this.debugIndex++;
+ if(cachedCommandsData.activeCaseTitle === cachedCommandsData.currentCaseTitle) {
+ this.debugIndex++;
+ }
+ cachedCommandsData.commandIndex++;
}
return null;
}
function setNextCommand(cmdIdx) {
- assert(cmdIdx >= 0 && cmdIdx < testCase.commands.length,
- " Cannot branch to non-existent command @" + (cmdIdx+1));
+ assert(cmdIdx >= 0 && cmdIdx < cachedCommandsData.suites[String(cachedCommandsData.activeCaseTitle)].commands.length,
+ " Cannot branch to non-existent command " + String(cachedCommandsData.activeCaseTitle) + " @" + (cmdIdx+1));
branchIdx = cmdIdx;
}
-
// Selenium calls reset():
// * before each single (double-click) command execution
// * before a testcase is run
@@ -278,6 +415,7 @@ function $X(xpath, contextNode, resultType) {
{
$$.LOG.trace("In tail intercept :: Selenium.reset()");
try {
+ contextManager.reset();
compileSelBlocks();
}
catch (err) {
@@ -303,6 +441,8 @@ function $X(xpath, contextNode, resultType) {
// Assemble block relationships and symbol locations
function compileSelBlocks()
{
+ cachedCommandsData.init();
+ symbols = {};
blockDefs = new BlockDefs();
var lexStack = new Stack();
var i;
@@ -487,6 +627,7 @@ function $X(xpath, contextNode, resultType) {
}
throw new SyntaxError(cmdErrors.join("; "));
}
+ cachedCommandsData.cacheCommands(symbols, testCase.commands, blockDefs);
//- command validation
function assertNotAndWaitSuffix(cmdIdx) {
assertCmd(cmdIdx, (testCase.commands[cmdIdx].command.indexOf("AndWait") === -1),
@@ -589,7 +730,7 @@ function $X(xpath, contextNode, resultType) {
notifyFatal("Invalid character(s) in " + desc + " name: '" + name + "'");
}
}
-
+
Selenium.prototype.doLabel = function() {
// noop
};
@@ -874,10 +1015,14 @@ function $X(xpath, contextNode, resultType) {
$$.LOG.warn("bubbleToTryBlock() called outside of any try nesting");
}
var tryState = unwindToBlock(_hasCriteria);
+ var ret;
while (!tryState && $$.tcf.nestingLevel > -1 && callStack.length > 1) {
var callFrame = callStack.pop();
$$.LOG.info("function '" + callFrame.name + "' aborting due to error");
- restoreVarState(callFrame.savedVars);
+ if (storedVars._result) { ret = storedVars._result; }
+ contextManager.exit();
+ storedVars._result = ret;
+ //restoreVarState(callFrame.savedVars);
tryState = unwindToBlock(_hasCriteria);
}
return tryState;
@@ -1189,6 +1334,7 @@ function $X(xpath, contextNode, resultType) {
activeBlockStack().push(loopState);
var localVars = _validateFunc(loopState);
loopState.savedVars = getVarState(localVars);
+ contextManager.enter();
initVarState(localVars); // because with-scope can reference storedVars only once they exist
_initFunc(loopState);
}
@@ -1210,8 +1356,13 @@ function $X(xpath, contextNode, resultType) {
assertRunning();
assertActiveScope(blkDefHere().beginIdx);
var loopState = activeBlockStack().top();
+ var ret;
if (loopState.isComplete) {
- restoreVarState(loopState.savedVars);
+ if (storedVars._result) { ret = storedVars._result; }
+ contextManager.exit();
+ storedVars._result = ret;
+
+ //restoreVarState(loopState.savedVars);
activeBlockStack().pop();
// done, fall out of loop
}
@@ -1261,38 +1412,57 @@ function $X(xpath, contextNode, resultType) {
// ================================================================================
Selenium.prototype.doCall = function(funcName, argSpec)
{
+ var funcIdx, fName, caseName;
assertRunning(); // TBD: can we do single execution, ie, run from this point then break on return?
if (argSpec) {
assertCompilable("var ", argSpec, ";", "Invalid call parameter(s)");
}
- var funcIdx = symbols[funcName];
- assert(funcIdx!==undefined, " Function does not exist: " + funcName + ".");
+ if(funcName.match(/[.]/)) {
+ caseName = String(funcName.split(".")[0]);
+ fName = String(funcName.split(".")[1]);
+ } else {
+ caseName = String(cachedCommandsData.activeCaseTitle);
+ fName = String(funcName);
+ }
+ funcIdx = cachedCommandsData.suites[caseName].symbols[fName];
+ assert(funcIdx!==undefined, " Function does not exist: " + funcName);
+ cachedCommandsData.activeCaseTitle = String(caseName);
var activeCallFrame = callStack.top();
if (activeCallFrame.isReturning && activeCallFrame.returnIdx === idxHere()) {
// returning from completed function
- restoreVarState(callStack.pop().savedVars);
+ cachedCommandsData.activeCaseTitle = String(cachedCommandsData.currentCaseTitle);
+ callStack.pop()
+ //restoreVarState(callStack.pop().savedVars);
}
else {
- // save existing variable state and set args as local variables
+ // pass supplied args to the function call through the call stack
var args = parseArgs(argSpec);
- var savedVars = getVarStateFor(args);
- setVars(args);
+ // saved vars will be populated by the function call
- callStack.push({ funcIdx: funcIdx, name: funcName, args: args, returnIdx: idxHere(),
- savedVars: savedVars, blockStack: new Stack() });
+ callStack.push({ funcIdx: funcIdx, name: fName, args: args, returnIdx: idxHere(),
+ savedVars: {}, blockStack: new Stack() });
// jump to function body
setNextCommand(funcIdx);
}
};
- Selenium.prototype.doFunction = function(funcName)
+ Selenium.prototype.doFunction = function(funcName, paramString)
{
assertRunning();
-
+ var params, savedVars;
var funcDef = blkDefHere();
var activeCallFrame = callStack.top();
if (activeCallFrame.funcIdx === idxHere()) {
- // get parameter values
+ contextManager.enter();
+ params = parseArgs(paramString, "suppress variable expansion");
+ // caching the values of all storedVars specified in function signature.
+ //savedVars = getVarStateFor(params);
+ //activeCallFrame.savedVars = savedVars;
+
+ // set default values supplied in function definition
+ setVars(params);
+ // overwrite local variables and defaults with the supplied values from
+ // the call
setVars(activeCallFrame.args);
}
else {
@@ -1300,11 +1470,11 @@ function $X(xpath, contextNode, resultType) {
setNextCommand(funcDef.endIdx);
}
};
- Selenium.prototype.doScript = function(scrName)
+ Selenium.prototype.doScript = function(scrName, paramString)
{
$$.LOG.warn("The script command has been deprecated and will be removed in future releases."
+ " Please use function instead.");
- Selenium.prototype.doFunction(scrName);
+ Selenium.prototype.doFunction(scrName, paramString);
};
Selenium.prototype.doReturn = function(value) {
returnFromFunction(null, value);
@@ -1322,15 +1492,19 @@ function $X(xpath, contextNode, resultType) {
if (transitionBubbling(Stack.isFunctionBlock)) {
return;
}
+ var ret;
var endDef = blkDefHere();
var activeCallFrame = callStack.top();
if (activeCallFrame.funcIdx !== endDef.funcIdx) {
// no active call, we're just skipping around a function block
}
else {
- if (returnVal) { storedVars._result = evalWithVars(returnVal); }
+ if (returnVal) { ret = evalWithVars(returnVal); }
+ contextManager.exit();
+ storedVars._result = ret;
activeCallFrame.isReturning = true;
// jump back to call command
+ cachedCommandsData.activeCaseTitle = String(cachedCommandsData.currentCaseTitle);
setNextCommand(activeCallFrame.returnIdx);
}
}
@@ -1347,7 +1521,63 @@ function $X(xpath, contextNode, resultType) {
// ========= storedVars management =========
+
+ Selenium.prototype.doStoreGlobal = function(value, varName) {
+ storedVarsGlobal[varName] = value;
+ };
+
+ Selenium.prototype.doStoreEvalGlobal = function(value, varName) {
+ var val = evalWithVars(String(value));
+ storedVarsGlobal[varName] = val;
+ };
+ Selenium.prototype.doStoreGlobalText = function(target, varName) {
+ var element = this.page().findElement(target);
+ storedVarsGlobal[varName] = getText(element);
+ };
+
+ Selenium.prototype.doStoreGlobalAttribute = function(target, varName) {
+ storedVarsGlobal[varName] = this.page().findAttribute(target);
+ };
+
+ Selenium.prototype.doStoreLocal = function(value, varName) {
+ storedVarsLocal[varName] = value;
+ };
+
+ Selenium.prototype.doStoreEvalLocal = function(value, varName) {
+ var val = evalWithVars(String(value));
+ storedVarsLocal[varName] = val;
+ };
+
+ Selenium.prototype.doStoreLocalText = function(target, varName) {
+ var element = this.page().findElement(target);
+ storedVarsLocal[varName] = getText(element);
+ };
+
+ Selenium.prototype.doStoreLocalAttribute = function(target, varName) {
+ storedVarsLocal[varName] = this.page().findAttribute(target);
+ };
+
+ Selenium.prototype.doStoreAt = function(value, varName) {
+ contextManager.storeAtClosestContextWithPropName(varName, value);
+ };
+
+ Selenium.prototype.doStoreEvalAt = function(value, varName) {
+ var val = evalWithVars(String(value));
+ contextManager.storeAtClosestContextWithPropName(varName, val);
+ };
+
+ Selenium.prototype.doStoreAtText = function(target, varName) {
+ var element = this.page().findElement(target);
+ var value = getText(element);
+ contextManager.storeAtClosestContextWithPropName(varName, value);
+ };
+
+ Selenium.prototype.doStoreAtAttribute = function(target, varName) {
+ var value = this.page().findAttribute(target);
+ contextManager.storeAtClosestContextWithPropName(varName, value);
+ };
+
function evalWithVars(expr) {
var result = null;
try {
@@ -1360,15 +1590,37 @@ function $X(xpath, contextNode, resultType) {
}
return result;
}
-
- function parseArgs(argSpec) { // comma-sep -> new prop-set
+ /**
+ * Parses a string of comma separated args that may contain assignments with
+ * equal signs.
+ * @param {String} argSpec The string representing arguments.
+ * @param {Null|Any} suppressEval If this is set to any truthy value then the
+ * values side of any arguments will not be evaluated with respect to the
+ * stored variables.
+ * @returns {Object} Returns an object whose properties are argument names and
+ * whose property values are the arguments values. If no value was supplied
+ * then the property will exist but it will be set to undefined.
+ * @example
+ * // storedVariables.bat = "XXXXXXX";
+ * parseArgs('foo="bar",baz=bat');
+ * // returns {foo:"bar", baz:"XXXXXXX"}
+ * parseArgs('foo="bar",baz=bat', "suppress eval");
+ * // returns {foo:"bar", baz:"bat"}
+ */
+ function parseArgs(argSpec, suppressEval) { // comma-sep -> new prop-set
var args = {};
var parms = iexpr.splitList(argSpec, ",");
var i;
for (i = 0; i < parms.length; i++) {
var keyValue = iexpr.splitList(parms[i], "=");
validateName(keyValue[0], "parameter");
- args[keyValue[0]] = evalWithVars(keyValue[1]);
+ // boolean values suck, let me put any arbitrary string as the arg
+ // like "suppress variable expansion" so I can be clear about my intent.
+ if(suppressEval) {
+ args[keyValue[0]] = keyValue[1];
+ } else {
+ args[keyValue[0]] = evalWithVars(keyValue[1]);
+ }
}
return args;
}
@@ -1377,11 +1629,20 @@ function $X(xpath, contextNode, resultType) {
var i;
for (i = 0; i < names.length; i++) {
if (!storedVars[names[i]]) {
- storedVars[names[i]] = null;
+ storedVars[names[i]] = undefined;
}
}
}
}
+ /**
+ * Sets the values of existing `args` properties to match `storedVars`
+ * @param {Object} args A simple value store where properties represent the
+ * the names of arguments and their values are the default values of those
+ * properties. If those same properties exist on `storedVars` they will be
+ * overwritten in the output object.
+ * @returns {Object} returns a simple object that can be used as `this` in
+ * `fn.call` and `fn.apply` etc.
+ */
function getVarStateFor(args) { // storedVars(prop-set) -> new prop-set
var savedVars = {};
var varname;
@@ -1390,6 +1651,13 @@ function $X(xpath, contextNode, resultType) {
}
return savedVars;
}
+ /**
+ * Gets links to the storedVars specified.
+ * @param {Array} names An array of strings representing the storedVars
+ * properties that you want to copy/link to.
+ * @returns {Object} returns a simple object that can be used as `this` in
+ * `fn.call` and `fn.apply` etc.
+ */
function getVarState(names) { // storedVars(names) -> new prop-set
var savedVars = {};
if (names) {
@@ -1400,6 +1668,14 @@ function $X(xpath, contextNode, resultType) {
}
return savedVars;
}
+ /**
+ * Overwrites values in storedVars with values supplied.
+ * @param {Object} args A simple value store where properties represent the
+ * the names of arguments and their values are the default values of those
+ * properties. If those same properties exist on `storedVars` they will be
+ * overwritten.
+ * @returns {Null} returns nothing.
+ */
function setVars(args) { // prop-set -> storedVars
var varname;
for (varname in args) {
diff --git a/sel-blocksTests/CallGlobalFunction.html b/sel-blocksTests/CallGlobalFunction.html
new file mode 100644
index 0000000..5066a9a
--- /dev/null
+++ b/sel-blocksTests/CallGlobalFunction.html
@@ -0,0 +1,55 @@
+
+
+
+
diff --git a/sel-blocksTests/try - bubble out of nested function calls.html b/sel-blocksTests/try - bubble out of nested function calls.html
new file mode 100644
index 0000000..bd12a70
--- /dev/null
+++ b/sel-blocksTests/try - bubble out of nested function calls.html
@@ -0,0 +1,180 @@
+
+
+
+
+
+
+try - bubble out of nested function calls
+
+
+
+
+
try - bubble out of nested function calls
+
+
+
log
+
"-- bubble out of nested function calls --"
+
+
+
+
resetEmitted
+
+
+
+
+
emit
+
"([/"
+
+
+
+
assertEval
+
selblocks.tcf.nestingLevel
+
-1
+
+
+
+
try
+
bub-invoke
+
+
+
+
emit
+
"trying"
+
+
+
+
emit
+
"calling"
+
+
+
+
call
+
subBub
+
n = 0
+
+
+
assertEval
+
"this command should not be reached, due error thrown in function"
+
+
diff --git a/sel-blocksTests/try - bubble up out of noop try to catch specific error.html b/sel-blocksTests/try - bubble up out of noop try to catch specific error.html
new file mode 100644
index 0000000..aee57d7
--- /dev/null
+++ b/sel-blocksTests/try - bubble up out of noop try to catch specific error.html
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+try - bubble up out of noop try to catch specific error
+
+
+
+
+
try - bubble up out of noop try to catch specific error
+
+
+
log
+
"-- bubble out of no-op try to catch specific error --"
+
+
+
+
resetEmitted
+
+
+
+
+
emit
+
"([/"
+
+
+
+
assertEval
+
selblocks.tcf.nestingLevel
+
-1
+
+
+
+
try
+
noop-outer
+
+
+
+
emit
+
"trying outer"
+
+
+
+
assertEval
+
selblocks.tcf.nestingLevel
+
0
+
+
+
+
try
+
op-inner
+
+
+
+
emit
+
"trying inner"
+
+
+
+
assertEval
+
selblocks.tcf.nestingLevel
+
1
+
+
+
emit
+
"throwing inner"
+
+
+
+
throw
+
"blamo"
+
+
+
+
assertEval
+
"this command should not be reached, due to throw"
+
+
diff --git a/sel-blocksTests/try - bubble up to catch ALL via finally.html b/sel-blocksTests/try - bubble up to catch ALL via finally.html
new file mode 100644
index 0000000..bb14827
--- /dev/null
+++ b/sel-blocksTests/try - bubble up to catch ALL via finally.html
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+try - bubble up to catch ALL via finally
+
+
+
+
+
try - bubble up to catch ALL via finally
+
+
+
log
+
"-- bubble up to catch ALL via finally --"
+
+
+
+
resetEmitted
+
+
+
+
+
emit
+
"([/"
+
+
+
+
assertEval
+
selblocks.tcf.nestingLevel
+
-1
+
+
+
+
try
+
t-allfin-outer
+
+
+
+
emit
+
"trying-outer"
+
+
+
+
assertEval
+
selblocks.tcf.nestingLevel
+
0
+
+
+
+
try
+
t-allfin-inner
+
+
+
+
emit
+
"trying"
+
+
+
+
throw
+
"blamo try-catch-finally"
+
+
+
+
assertEval
+
"this command should not be reached, due to throw"
+
+
diff --git a/sel-blocksTests/try - bubble up to catch ALL.html b/sel-blocksTests/try - bubble up to catch ALL.html
new file mode 100644
index 0000000..4a92a44
--- /dev/null
+++ b/sel-blocksTests/try - bubble up to catch ALL.html
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+try - bubble up to catch ALL
+
+
+
+
+
try - bubble up to catch ALL
+
+
+
log
+
"-- bubble up to catch ALL --"
+
+
+
+
resetEmitted
+
+
+
+
+
emit
+
"([/"
+
+
+
+
assertEval
+
selblocks.tcf.nestingLevel
+
-1
+
+
+
+
try
+
t-all-outer
+
+
+
+
emit
+
"trying-outer"
+
+
+
+
assertEval
+
selblocks.tcf.nestingLevel
+
0
+
+
+
+
try
+
t-all-inner
+
+
+
+
emit
+
"trying"
+
+
+
+
throw
+
"blamo try-catch-finally"
+
+
+
+
assertEval
+
"this command should not be reached, due to throw"
+
+
diff --git a/sel-blocksTests/try - bubble up to catch specific error via finally.html b/sel-blocksTests/try - bubble up to catch specific error via finally.html
new file mode 100644
index 0000000..ce43952
--- /dev/null
+++ b/sel-blocksTests/try - bubble up to catch specific error via finally.html
@@ -0,0 +1,141 @@
+
+
+
+
+
+
+try - bubble up to catch specific error via finally
+
+
+
+
+
try - bubble up to catch specific error via finally
+
+
+
log
+
"-- bubble up to catch specific error via finally --"
+
+
+
+
resetEmitted
+
+
+
+
+
emit
+
"([/"
+
+
+
+
assertEval
+
selblocks.tcf.nestingLevel
+
-1
+
+
+
+
try
+
bub-spec-outer
+
+
+
+
emit
+
"trying outer"
+
+
+
+
assertEval
+
selblocks.tcf.nestingLevel
+
0
+
+
+
+
try
+
bub-spec-inner
+
+
+
+
emit
+
"trying inner"
+
+
+
+
assertEval
+
selblocks.tcf.nestingLevel
+
1
+
+
+
emit
+
"throwing inner"
+
+
+
+
throw
+
"blamo"
+
+
+
+
assertEval
+
"this command should not be reached, due to throw"
+
+
diff --git a/sel-blocksTests/try - bubble up to catch specific error.html b/sel-blocksTests/try - bubble up to catch specific error.html
new file mode 100644
index 0000000..a7af661
--- /dev/null
+++ b/sel-blocksTests/try - bubble up to catch specific error.html
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+try - bubble up to catch specific error
+
+
+
+
+
try - bubble up to catch specific error
+
+
+
log
+
"-- bubble up to catch specific error --"
+
+
+
+
resetEmitted
+
+
+
+
+
emit
+
"([/"
+
+
+
+
assertEval
+
selblocks.tcf.nestingLevel
+
-1
+
+
+
+
try
+
bub-spec-outer
+
+
+
+
emit
+
"trying outer"
+
+
+
+
assertEval
+
selblocks.tcf.nestingLevel
+
0
+
+
+
+
try
+
bub-spec-inner
+
+
+
+
emit
+
"trying inner"
+
+
+
+
assertEval
+
selblocks.tcf.nestingLevel
+
1
+
+
+
emit
+
"throwing inner"
+
+
+
+
throw
+
"blamo"
+
+
+
+
assertEval
+
"this command should not be reached, due to throw"
+
+
diff --git a/sel-blocksTests/try - catch but no error.html b/sel-blocksTests/try - catch but no error.html
new file mode 100644
index 0000000..86c37a3
--- /dev/null
+++ b/sel-blocksTests/try - catch but no error.html
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+try - catch but no error
+
+
+
+
+
try - catch but no error
+
+
+
log
+
"-- try/catch, but no error --"
+
+
+
+
resetEmitted
+
+
+
+
+
emit
+
"([/"
+
+
+
+
assertEval
+
selblocks.tcf.nestingLevel
+
-1
+
+
+
+
try
+
tc0
+
+
+
+
emit
+
"trying"
+
+
+
+
catch
+
+
+
+
+
assertEval
+
"should NEVER enter this catch block due to no error"
+
+
+
+
endTry
+
+
+
+
+
+
assertEval
+
selblocks.tcf.nestingLevel
+
-1
+
+
+
emit
+
"/])"
+
+
+
+
assertEmitted
+
"([/~trying~/])"
+
+
+
+
+
+
diff --git a/sel-blocksTests/try - catch specific error then finally.html b/sel-blocksTests/try - catch specific error then finally.html
new file mode 100644
index 0000000..bc7c505
--- /dev/null
+++ b/sel-blocksTests/try - catch specific error then finally.html
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+try - catch specific error then finally
+
+
+
+
+
try - catch specific error then finally
+
+
+
log
+
"-- catch specific error, then finally --"
+
+
+
+
resetEmitted
+
+
+
+
+
emit
+
"([/"
+
+
+
+
assertEval
+
selblocks.tcf.nestingLevel
+
-1
+
+
+
+
try
+
t-spec-outer
+
+
+
+
emit
+
"trying outer"
+
+
+
+
assertEval
+
selblocks.tcf.nestingLevel
+
-1
+
+
+
emit
+
"([/"
+
+
+
+
+
try
+
t-spec-inner
+
+
+
+
emit
+
"trying inner"
+
+
+
+
throw
+
"blamo inner"
+
+
+
+
assertEval
+
"this command should not be reached, due to throw"
+
+
diff --git a/sel-blocksTests/try - exitTest with finally processing.html b/sel-blocksTests/try - exitTest with finally processing.html
new file mode 100644
index 0000000..a7f0082
--- /dev/null
+++ b/sel-blocksTests/try - exitTest with finally processing.html
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+try - exitTest with finally processing
+
+
+
+
+
try - exitTest with finally processing
+
+
+
log
+
"-- exitTest w/finally processing --"
+
+
+
+
resetEmitted
+
+
+
+
+
emit
+
"([/"
+
+
+
+
assertEval
+
selblocks.tcf.nestingLevel
+
-1
+
+
+
+
try
+
abort
+
+
+
+
emit
+
"trying"
+
+
+
+
emit
+
"exitTest ..."
+
+
+
+
exitTest
+
+
+
+
+
finally
+
+
+
+
+
emit
+
"finallying"
+
+
+
+
assertEmitted
+
"([/~trying~exitTest ...~finallying"
+
+
+
+
resetEmitted
+
+
+
+
+
endTry
+
abort
+
+
+
+
+
assertEval
+
"this command should not be reached, due to exitTest above"
+
+
+
+
+
+
diff --git a/sel-blocksTests/try - finally with no error.html b/sel-blocksTests/try - finally with no error.html
new file mode 100644
index 0000000..ec93f97
--- /dev/null
+++ b/sel-blocksTests/try - finally with no error.html
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+try - finally with no error
+
+
+