QTP VBScript type mismatch error in function without arguments - vbscript

I have run into an annoying problem in QTP (Quick test pro) using VBScript.
I have defined this sub in VBScript (I have also tried defining it as a function with the same result):
Sub KillProcess()
KillprocessIE = "The process i want to kill"
Set ProcessList2 = GetObject("winmgmts://.").InstancesOf("win32_process")
For Each Process In ProcessList2
If Process.Name = KillProcessIE Then
Process.Terminate
Exit for
End If
Next
End Sub
But when I try to run it either by using
call KillProcess()
KillProcess()
KillProcess
I get an error saying "Typer stemmer ikke overens: 'KillProcess'" with translated from Danish means something like "Types does not match: 'KillProcess'. I am guessing it is a type mismatch error but due to translation I cant be sure.
What types is it talking about? I have no arguments in my function call and I am not assigning any values to anything?
It should also be said that if I run the exact same code directly without defining it as a function, it works without issue.

Whenever you call a sub or function that is not defined, you get a type mismatch error.
Even though this is a miracle per se (for which I could find a reasonable reasoning for only for functions, not for subs) it indicates that in your specific scenario, KillProcess was not known in the script in which you called it.
Possible causes:
The KillProcess declaration was in a function library which was not listed in the
associated function library settings dialog.
The KillProcess declaration was active, but your call(s) contained one or more typos,
like "KillProccess", or similar stuff.
As Motti indicated, the code snippet you posted looks fine, so there must be some other glitch.
Another hint regarding subs versus functions: Be aware that you usually never call a Sub with brackets for the arguments. If you do specify them, they are considered to be part of a term to be evaluated, and since
X term
is the same as
X (term)
this consequently does not yield an error message in this case.
However, for Subs with two or more arguments, specifying all actual arguments in one bracket pair, like in
Y (term1, term2)
yields an error, something like "cannot call a Sub with arguments in brackets", or so. This is hard to understand if you consider that one argument indeed can be specified in brackets.
(For a more complete overview about the paranthesis topic, see ByRef and ByVal in VBScript and linked questions).
General suggestion: Use
option explicit
at the top of all scripts (especially function libraries) all the time. RTFM this if needed. It might look like more work (because it forces you to declare all variables before you can use them), but it is useful to avoid subtle bugs.

I think you have run into the strange behavior QTP but though logic when you understand why.
The reason for why this occurs is probably because you "run from step" call KillProcess() and having the function defined above like:
Sub KillProcess()
.
.
.
End Sub
call KillProcess()
If you run the code from "Call KillProcess()" QTP will return the "Run Error" "Type Mismatch"
If instead let the function be defined below the executing statement like this
call KillProcess()
Sub KillProcess()
.
.
.
End Sub
Then QTP "knows" the function that you are calling and will execute the script like it should.
The reason for this is, that when using "Run from step" only reads the line of codes from the step and below and not what you have written above.

Related

Is Call MagicFunction(intData1, intData2, Dim intData3) a valid statement in vbs?

I'm not really sure how to phase it any other way.
The thing is, i'm trying to merge functions with the same name in vbs. Sometimes, the function appears in different forms in other parts of the system. If they are too different, I regretfully leave them as they are. However, if the differences are minor (like having one of the functions only having one variable more than the others, which i can then check for in-function), I'd like to add a variable that would be a stand in.
I already know that Optional variables are not possible in vbs, and I've already had experience with passing an array of variants (works like a charm), but I believe this case is a bit different.
Dim is not correct here. You can do for example:
Public Function MagicFunction(intData1, ByRef intData2, ByVal intData3)
' some code
End Function
and to call it:
MagicFunction 3, iCount, ""
to have "optional arguments", you can only use an array an parse it (for example using UBound(aTab) to select the correct case
Public Function MagicFunction(ByVal aTab)
Select Case UBound(aTab)
Case 1: MagicFunction1 aTab(1)
Case 2: MagicFunction2 aTab(1), aTab(2)
Case Else: MsgBox "function called with more than 2 args" '<-- Should never go there
End Select
End Function
With different version of your function depending on the number of argument, MagicFunction1, MagicFunction2... It's ugly but do the trick!
Another possibility is to use empty strings as argument, and define how your function ignore a part when the string is empty (or to be more accurate, call with a specific key, like "IGNORE_KEY")
I hope I'm answering your question!

Pass a string as a variable name QTP

I have a function that does the same operation for all of my scripts, but only the variable in which the Pass-Fail value is stored, would change.
For example, in one script -> the status is stored in Envrionment.Value("Current_Status")
in another script -> the status is stored in DataTable.Value("Status",1)
in another script -> the status is stored in objRS("AddCriteria_Status").Value
So i am trying to make a function in which i pass on these parameters as strings and then later use them as variable names. Here is the sample code:
Envrionment.Value("Current_Status") = "none"
Environment.Value("Fail_text") = "none"
Call AddCriteria("Environment.Value(""Current_Status"")","Environment.Value(""Fail_text"")")
Pubic Function AddCriteria(varStatus,varActual)
varTemp = ""
Execute(varStatus+ "=InProgress") 'change status to InProgress by the time execution is done
Execute(varActual + "=not_defined") 'this will have the reason the case failed
....code
If varTemp = "FAIL" Then
Execute(varStatus+ "=PASS")
Execute(varActual + "=PASS")
Else
Execute(varStatus+ "=FAIL")
Execute(varActual + "=Criteria did not get added")
End If
End Function
On calling the sub-routine i want the value of Environment.Value("Current_Status") to change from "none" to "InProgress" and then to "PASS"
But after the "Execute" command is executed, the Environment variable become empty.
Since CVar is not supported in VBScript, i cannot use it.
I tried Eval, but it doesn't work in the other direction i.e.:
If you change the value of Environment.Value("Current_Status"), then the value Eval(varStatus) changes, but I could not find a way to change the value of Eval(varStatus) so that the value of Environment.Value("Current_Status") changes.
Please help out. I am stuck at this for a week.
!!!What I'm trying to accomplish!!!
In a .vbs file, pass on any string to a function as a parameter; and convert it into a variable name in that function. Simple example: pass a string "abc" as a parameter to a function -> and within that function, convert the string to a variable name to store value [say, abc = "PASS"]
!!!How I attempt to do it!!!
I tried using Execute command as that is a solution that I got from a previous post
[vbscript Eval a string to a Variable in a loop?
Using "CVar" is a way but that is not supported in VBScript. So I ran out of ideas
!!!Problems that I faced!!!
Honestly, I didn't understand the logic of using "Execute", but i tried it nevertheless. Sadly, it didn't work out. When using execute command (as mentioned in the code), the environment variables become empty.
Ideas:
Use ExecuteGlobal to execute the assignment you want to execute --
if that´s what you want. Eval and especially Execute have subtle limitations regarding the scope they live in.
The target variable (i.e. the variable that receives a value in the
assignment that is evaluated by ExecuteGlobal) must be a global
variable.
If the ExecuteGlobal call happens on an Action's global scope, the
target variable must be declared there, too. (I think.)
If the ExecuteGlobal call happens in a routine in a function
library, the target variable must be declared there, too. (I know that for sure. But read on.)
To further help you, I'd need an update on your question because it is not clear what you want to accomplish, and what problems you see. Because -- Eval does not change values, it just evaluates an expression supplied as a string, and returns its value. If the expression has side-effects, like setting a global variable, then you might be out of luck because...well...it depends on where that global variable is declared, and initialized (if at all), and where the ExecuteGlobal call happens. Actions and libraries do NOT share one global scope, even if it looks like they do, and that can create a lot of strange behavior.
But as I said, if you clarify what you are trying to accomplish (got 90% of that), how you attempt to do it (got 40% of it), and what problems you face (got 10% of it), I´m sure I can update this answer so it approaches a solution.
** Update **
I use this library code for all runtime expression evaluation, be it from within a library or Action:
' Interpret (execute) a piece of VSH source code consisting of statements -- success?
' Code: String containing VBS source code. Passed by reference for performance reasons only
Public Function ExecCode (ByRef Code)
Dim ErrNumber
Dim ErrDescription
On error resume next ' Avoid getting kicked out by errors in the code contained in Code
ExecuteGlobal Code
ErrNumber=Err.Number
ErrDescription=Err.Description
On error goto 0 ' Re-enable RTE handling
If ErrNumber <> 0 Then
ExecCode=false
Print "Code execution failed ('" & ErrDescription & "'), code:" & vbNewline & Code & "<eof>"
else
ExecCode=true
End If
End Function
Dim GlobalVar
' Interpret (execute) a piece of VSH source code consisting of a single expression -- success?
' Expr; String containing a VBS expression. Passed by reference for performance reasons only.
' Target: Variable receiving the value to which the expression evaluates
Public Function EvalCodeAndAssign (ByRef Expr, ByRef Target)
' In order to force "Option explicit", we don´t use Eval, but ExecCode (and thus ExecuteGlobal):
Dim Code: Code="Option Explicit: GlobalVar=(" & Expr & ")"
Dim Result: Result=ExecCode (Code)
If Result Then
Target=GlobalVar
End If
EvalCodeAndAssign=Result
End Function
Update 2: if the statement that you pass to ExecuteGlobal contains quotes (which I think are missing in your code), the must be quoted, I.e. you must use double-quotes, like in
ExecuteGlobal "x=""This is a string"""
Because what ExecuteGlobal/Execute/Eval do is: take a string and interpret it as VBScript code. The code you are trying to use is not valid due to missing quotes.

Type mismatch when calling a function in qtp

I am using QTP 11.5 for automating a web application.I am trying to call an action in qtp through driverscript as below:
RFSTestPath = "D:\vf74\D Drive\RFS Automation\"
LoadAndRunAction RFStestPath & LogInApplication,"Action1",oneIteration
Inside the LogInApplication(Action1) am calling a login function as:
Call fncLogInApplication(strURL,strUsesrName,strPasssword)
Definition of fncLogInApplication is written in fncLogInApplication.vbs
When I associate the fncLogInApplication.vbs file to driverscript, am able to execute my code without any errors. But when I de-associate .vbs file from driverscript and associate it to LogInApplication test am getting "Type mismatch: 'fncLogInApplication'"
Can anyone help me in the association please. I want fncLogInApplication to be executed when I associate to LogInApplication not to the main driverscript.
Please comment back if you require any more info
There is only one set of associated libraries that is active at any one time: That is always the outermost test's one.
This means if test A calls test B, test B will be executed with the libraries loaded based upon test A´s associated libraries list, not B's.
This also means that if B depends on a library, and B associated this library, but is called from test A (which does not associated this library), then B will fail to call (locate) the function since the associated libraries of B are never loaded (only those from A are). (As would A, naturally.).
If you are still interested: "Type mismatch" is QTPs (or VBScript´s) poor way of telling you: "The function called is not known, so I bet you instead meant an array variable dereference, and the variable you specified is equal to empty, so it is not an array, and thus cannot be dereferenced as an array variable, which is what I call a 'type mismatch'."
This reasoning is valid, considering the syntax tree of VB/VBScript: Function calls and array variable dereferences cannot be formally differentiated. Syntactically, they are very similar, or identical in most cases. So be prepared to handle "Type mismatch" like the "Unknown function referenced" message that VB/VBScript never display when creating VBScript code.
You can, however, load the library you want in test B´s code (for example, using LoadFunctionLibrary), but this still allows A to call functions from that library once B loaded it and returned from A´s call. This, and all the possible variations of this procedure, however, have side-effects to aspects like debugging, forward references and visibility of global variables, so I would recommend against it.
Additional notes:
There is no good reason to use CALL. Just call the sub or function.
If you call a function and use the result it returns, you must include the arguments in parantheses.
If you call a sub (or a function, and don´t use the result it returns), you must not include the arguments in parantheses. If the sub or function accepts only one argument, it might look like you are allowed to put it in parantheses, but this is not true. In this case, the argument is simply treated like a term in parantheses.
The argument "bracketing" aspects just listed can create very nasty bugs, especially if the argument is byRef, also due (but not limited) to the fact that VBScripts unfortunately allows you to pass values for a byRef argument (where a variable parameter is expected), so it is generally a good idea to put paranthesis only where it belongs (i.e. where absolutely needed).

capture any error in VBScript?

I have a batch file that calls a VBScript (.vbs) program. After calling it, my batch script checks %errorlevel% to see if the .vbs program failed. I can signal failure with an exit code in the .vbs program with WScript.Quit(1).
However, I can only do that explicitly. If some unexpected run-time error happens, the .vbs quits with an error dialog box, however the exit code is zero so my batch file thinks it suceeded! How can I change that behavior?
And if you are thinking of saying, use on error goto, don't bother... that syntax is available in regular VB, but not in VBScript.
I thought of an out-of-the-box solution... Who says 0 has to mean success? VBScript sometimes returns a 0 return code for failures, so why not embrace that? Adopt 0 as (at least one possible) failure code and make up another number (e.g. 10) as the "success code".
At the end of the script, put WScript.Quit(10). That will only be hit if everything succeeded up to that point. Then instead of "if errorlevel 1" in the calling batch file, use "if %errorlevel% == 10"
EDIT : Having tentatively (see caveats) proposed this, I am rapidly beginning to think that it is a very bad idea, but I leave it here for posterity. The most compelling reason to not use this comes from Eric Lippert at Microsoft, who worked on the design & implementation of VBScript. He states, in answer to another question: VBScript does not make any guarantee that terminators always run. This can mean that this sometimes does not return a non-0 exit code in the case of an unhandled error.
I think I personally will use a 'wrapper batch file that subtracts 1 from the cscript exit code' solution in future.
I like the solution linked to by fmunkert, but I think it requires you to put your code in a particular Class_Initalize, which is clumsy at best. I've devised a related solution that does not require this; you simply "Commit" a successful result at the end of your code; if it's not called, any exception causes the ExitCodeHandler's Class_Terminate instance to set a non-zero exit code.
Option Explicit
Class ExitCodeHandler
private exit_code
Public Sub Commit()
exit_code = 0
End Sub
Private Sub Class_Initialize()
exit_code = -1 ' this exit code will be returned if Commit is never called
End Sub
Private Sub Class_Terminate()
if exit_code<>0 then WScript.Quit(exit_code)
End Sub
Public Sub Quit(exitCode)
Commit
WScript.Quit(exitCode) ' exit code will be respected since we have committed
End Sub
End Class
' create one of these at the start:
Dim ech: Set ech = New ExitCodeHandler
WSCript.StdOut.WriteLine "Hello"
s = "" ' undeclared variable causes runtime error - comment out to see success.
' WScript.Quit(-4) ' before a commit, -1 is returned due to the Class_Terminate
' Commit at the end
ech.Commit
' WScript.Quit(-5) ' after a commit, -5 is returned
Note that this idiom is used heavily in C++, where it is called RAII (Resource Acquisition Is Initialization)
You could of course embellish the class this to support other exit codes, error messages etc. You may want to put this in a common vbs file and use a mechanism for includes in vbscript to share it.
Caveats
I don't know the full details of downsides to calling WScript.Quit during stack unwinding due to an exeption in VBScript. I've disovered the following:
Use with caution. I have come up with this and poked around with it when I saw fmunkert's linked suggestion, not used it extensively.
If you explicitly call WScript.Quit(n), the ExitCodeHandler will replace n with its own exit code. The workaround is to either always call ExitCodeHandler.Commit before calling WScript.Quit, or call the supplied ExitCodeHandler.Quit instead which does it for you. However, relying on either of these methods may not always be practical/possible, and it is fairly non-idiomatic and may not be ovbious to maintainers.
If any other object with a Class_Terminate is terminated (i.e. after ExitCodeHandler's Class_Terminate calls WScript.Quit), you seem to get an error. You may get similar behaviour with any COM objects that are being destroyed. I don't know in what order VBScript destroys objects (or even if it's guaranteed), so I've asked about it in another question.
As you say, all that's available is On Error Resume Next, so your forced to use the pattern:
On Error Resume Next
ThingWithAChanceOfThrowingAnError ...
If (Err.number <> 0) then PrintErrorAndQuitWith1(Err.Description)
You could, if it's an option, use jscript instead which has better support for exception handling, including an easy way to return a non-zero exit code on any exception. See the solution to why does my JScript (windows script host) exit with 0 on an uncaught exception?
This is the #1 reason we're choosing jscript over vbscript (when we have to use one of the two!)
You might use the technique described in this article.
It requires you to wrap your script inside a VBScript class.

dumping the source code for an anonymous function

original (update follows)
I'm working with a lot of anonymous functions, ie functions declared as part of a dictionary, aka "methods". It's getting pretty painful to debug, because I can't tell what function the errors are happening in.
Vim's backtraces look like this:
Error detected while processing function NamedFunction..2111..2105:
line 1:
E730: using List as a String
This trace shows that the error occurred in the third level down the stack, on the first line of anonymous function #2105. IE NamedFunction called anonymous function #2111, which called anonymous function #2105. NamedFunction is one declared through the normal function NamedFunction() ... endfunction syntax; the others were declared using code like function dict.func() ... endfunction.
So obviously I'd like to find out which function has number 2105.
Assuming that it's still in scope, it's possible to find out what Dictionary entry references it by dumping all of the dictionary variables that might contain that reference. This is sort of awkward and it's difficult to be systematic about it, though I guess I could code up a function to search through all of the loaded dictionaries for a reference to that function, watching out for circular references. Although to be really thorough, it would have to search not only script-local and global dictionaries, but buffer-local dictionaries as well; is there a way to access another buffer's local variables?
Anyway I'm wondering if it's possible to dump the source code for the anonymous function instead. This would be a lot easier and probably more reliable.
update
I ended up asking about this a while back on the vim_use mailing list. Bram Moolenar, aka vim's BDFL, responded by saying that "You are not supposed to use the function number." However, a suitable alternative for this functionality has not been suggested, as of early September 2010. It's also not been explicitly mentioned whether or not this functionality will continue to work in subsequent vim releases. I've not tried to do this (or anything else, for that matter) in the recently released vim 7.3.
The :function command tries to stop you from specifying the numbered functions (their name is just a number) but you can trick it using the {...} dynamic function name feature, throw in some :verbose and you have a winner:
:verbose function {43}
function 43()
Last set from /home/peter/test.vim
1 throw "I am an exception"
endfunction
This was not at all obvious in the help docs.
I use the following workaround: I have one plugin that does some stuff like creating commands, global functions for other plugins. It also registers all plugins, so I have a large dictionary with lots of stuff related to plugins. If I see a error I search for a function that produces it using function findnr:
"{{{3 stuf.findf:
function s:F.stuf.findf(nr, pos, d)
if type(a:d)==2 && string(a:d)=~#"'".a:nr."'"
return a:pos
elseif type(a:d)==type({})
for [key, Value] in items(a:d)
let pos=s:F.stuf.findf(a:nr, a:pos."/".key, Value)
unlet Value
if type(pos)==type("")
return pos
endif
endfor
endif
return 0
endfunction
"{{{3 stuf.findr:
function s:F.stuf.findnr(nr)
for [key, value] in items(s:g.reg.registered)+[["load", {"F": s:F}]]
let pos=s:F.stuf.findf(a:nr, "/".key, value.F)
if type(pos)==type("")
return pos
endif
endfor
return 0
endfunction
Here I have this plugin functions in s:F.{key} dictionaries and other plugins' functions under s:g.reg.registered[plugname].F dictionary.

Resources