Open peter-lyons-kehl opened 10 years ago
This is the intended behavior, per documentation: "The only thing special about SelBlocks parameters is that they are activated and deactivated as execution flows into and out of blocks". This also means that if you had assigned some value to variable before calling outer, that value is restored upon completion of outer. In other words, variable scoping is recursive, where as you may have been assuming lexical scoping.
The current behavior may save some work, especially when packaging existing steps into functions (or splitting functions, copying parts between functions and the main scope etc.). However, it also allows side effects, if the code depends on a variable not being set. E.g. the code checks whether $myLocalVariable===undefined which it handles in a special way. This is especially useful for optional parameter(s) of functions (when a call with undefined optional parameter indicates that it should use some default value, or it should perform or skip some optional functionality).
Even worse, any variables set within a function (other than its actual passed parameters) are kept around after it returns. That also includes variables which were set outside of the function’s scope, before it was called. This allows highly confusing conflicts when using functions created by other people or created by oneself but some time ago (after the person forgot the names of variables used in the function other than its parameters).
Lexical scoping would promote cleaner functions, thus more robust, easier to share and manageable tests.
This is an interesting idea. It would nudge things further in the direction of a real language. It would also make language translation more seamless. That is, exporting Selenese to other languages. Here are some thoughts:
When a function is entered, parameters are "activated" as regular Selenium variables, and then "deactivated" upon completion, whether via return or via try/catch/finally bubbling.
Activation: Existing variables having the same names as parameters are saved on the call stack, and then each parameter variable is initialized. Deactivation: Each parameter variable is deleted, and then saved variables if any are restored from the call stack.
This same mechanism also manages loop control variables.
Suspension: Locals are saved and deactivated. Resuming: Locals are reactivated.
Thoughts? Comments? From Peter or anyone else...
I agree with it all, except for the following (but some of it may be already covered, or it's not clear to me).
It would be safer to save & deactivate global variables when entering a function (1st on the stack - called from the global scope). If the functions can access global variables, that still allows complicated side effects: When a function uses a variable, it doesn't declare it as local/global (and I don't think we'd need such complexity). So someone can have a function that uses a variable which doesn't exist in the global scope - a local variable. If later on the person (or someone else) introduces a variable with the same name in the global scope, it creates conflicts/side effects.
Regarding for loops - it would be practical to keep iterator variable(s) in the current scope, even after the closing endFor (i.e. in the scope just outside of end..endFor). They often serve in post-loop logic. Also, Selenese is related to Javascript, which allows using for(..) variables after the loop, so the developers/testers may expect it.
Batch/shell scripts generally have a global variable space. Java has block scope, plus inheritance. JavaScript has function scope, plus prototype chain (including the global object).
By comparison Selenese is like batch scripting, with global variables only.
SelBlocks adds loop control variables and function parameters. Neither are lexically scoped, but rather recursively activated, hence this discussion.
As for loops, most languages limit the scope of loop control variables. JavaScript is no exception, assuming you use the var keyword, which is best practice. Without var, JavaScript gives you a global (aka "pathological") variable. SelBlocks, which doesn't have a var keyword, always uses block-level loop var(s). Note that if you want a wider scoped variable, just initialize it outside of the loop.
Given that JavaScript expressions are already foundational to SelBlocks, function scope seems to be the natural choice. (Note that SelBlocks expressions also participate in the underlying JavaScript prototype chain, but that is a kind of meta level relative to Selenium variables.)
Because SelBlocks is limited in how it can extend Selenium, essentially intercepting the stream of processing, the only reasonable definition of variable "declaration" is to detect when they have been first set. Hence the approach of saving and restoring at block boundaries. By checking what variables are present upon entry to a block, that state can be restored upon exit.
I arrive at the same conclusion that adding the suspend/resume mechanism for sub-function calls provides a useful and traditional definition of local variable scope. Globals are what they are, with the special case that they can be temporarily hidden by a local variable of the same name.
Reviewing this part of the SelBlocks design has been a helpful exercise.
Muahahahaha
var x = 10;
function outer (x) {
console.log("outer: " + x);
x = x + 1;
inner(x);
globaler();
}
function inner (x) {
console.log("inner: " + x);
}
function globaler () {
console.log("globaler: " + x);
}
outer();
x = x + 1;
outer(5);
Logs
outer: undefined
inner: NaN
globaler: 10
outer: 5
inner: 6
globaler: 11
When running a function, it inherits all variables from the higher scope.
Se IDE 2.5.0, SelBlocks 2.0.1.