refactoror / SelBlocks

SelBlocks extension for Selenium IDE
10 stars 7 forks source link

When calling a function, it gets all variables from the higher scope #5

Open peter-lyons-kehl opened 10 years ago

peter-lyons-kehl commented 10 years ago

When running a function, it inherits all variables from the higher scope.

Command Target Value
call outer variable='Hello'
function outer variable
call inner
endFunction
function inner
getEval LOG.warn( 'inner: variable: ${variable}' )
endFunction

Se IDE 2.5.0, SelBlocks 2.0.1.

refactoror commented 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.

peter-lyons-kehl commented 10 years ago

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.

refactoror commented 10 years ago

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:

The current implementation

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.

Lexical scoping might extend the above as follows:

Suspension: Locals are saved and deactivated. Resuming: Locals are reactivated.

Consequences:

Thoughts? Comments? From Peter or anyone else...

peter-lyons-kehl commented 10 years ago

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.

refactoror commented 10 years ago
A few languages for comparison

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.

What model of lexical scoping is achievable/useful?

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.

matthewkastor commented 9 years ago

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