How can I assert in VBScript scripts? - vbscript

What is a good way to use asserts in VBScript scripts?
Is there built-in functionality for it or will it have to be emulated? What is best practice?
One application is to test for objects being Nothing during development.

An operational answer (for others that may need it) is to define this function, from Rosetta Code:
sub Assert( boolExpr, strOnFail )
if not boolExpr then
Err.Raise vbObjectError + 99999, , strOnFail
end if
end sub
Invocation:
Set obj2 = Nothing
Assert Not obj2 Is Nothing, "obj2 is Nothing!"
Output:
someScript.vbs(17, 3) (null): obj2 is Nothing!

Sadly, I don't believe VBScript has anything built-in. You'll need to define your own Assert method and probably use a pre-processor build script or something to remove them when building a release copy of your script. (Actually removing -- at least commenting out -- the Assert call is best, rather than just making the body of the Assert not do anything in the released code.)

Related

'OR' Syntax in (g)Lua

I have limited knowledge of lua and would like to make an or statement.
However, I don't know the exact syntax.
Would the code below work correctly?
if text == "/teamspeak" or text == "/ts" then
If not please let me know on the correct syntax of the statement.
Yes, the statements are correct. You do not have any syntactical errors there, though you might want to check whether text contains only the command or the whole string (as is the case with ptokax). You might also want to check that the command is uppercase/lowercase or mixed-casing.
local sCmd = text:lower()
if sCmd == "/ts" or sCmd == "/teamspeak" then
...
end
Lua uses the keyword or for or statements.
I recommend reading the Lua language reference.
Your code would work correctly if you terminate the if then statement with end.
Best way is to try it yourself. If you do not have Lua installed you can use http://www.lua.org/demo.html
And please note that nil is not the same as false! Many Lua beginners have problems here.
That statement should work, though I suggest converting the string to lowercase first, as jhpotter92 already suggested.
A typical problem in cases like this is when the order the language deals with operands is not the one you'd expect; if, for example, lua were to evaluate the or before the == operator (which it doesn't, see reference) that code would not work. Therefore it is never a bad idea to write your code like this
if (text == "/teamspeak") or (text = "/ts") then <...> end
just to be sure lua does things in the correct order.
If you ever find yourself in this kind of situation again, and you don't want to wait for someone to respond to your question, you can just start lua in interactive mode (assuming you have lua installed on your system, which is very helpful for everyone who wants to learn/code in lua) and type something like
> text = "/teamspeak"
> if text == "/teamspeak" or text == "/ts" then print "true ♥" end
In this example, the console will output "true ♥". Repeat this with text="/ts" and text="some other string" and see if the line of code behaves as it should.
This shouldn't take you longer than 5 minutes (maybe +5 minutes to install lua first)

Reference a variable in a test script

I have created a deck class and have defined one of the functions to insert a card at the beginning of the array. However everytime I try to test it in my test script I receive `add_to_bottom': wrong number of arguments (1 for 0) (ArgumentError). Can someone please help me I know I am close to figuring it out.
Deck Class
def add_to_bottom
#cards.insert(0, c)
end
Test Script
d = Deck.new
c = Card.new(7, "S")
d.add_to_bottom(c)
print d, "\n"
add_to_bottom has c in the method body, which is neither a method nor a variable. If that is meant to be an argument passed, then you need to write def add_to_bottom(c). If you do that, then that would also resolve your error.
Your add_to_bottom expect no arguments while in the test code you provide an argument. Did you forget to declare it in your function declaration?
I'm not sure how your Deck or Card classes are implemented, so it is hard to say. One very obvious problem is what #sawa and #VuMinhTan have already pointed out. So, as they said, you definitely should be taking in parameter c in the method you defined (add_to_bottom).
It seems evident to me that Card.new(7, "S") is making a 7 of Spades. This is just my curiosity, and nothing "wrong" with your code, but I'm also wondering how one would consider index 0 the "bottom". Of course, it really makes no difference, but that seems to be the "top" to me.

Debug Mode In VB 6?

How can I do something similar to the following C code in VB 6?
#ifdef _DEBUG_
// do things
#else
// do other things
#end if
It's pretty much the same as the other languages that you're used to. The syntax looks like this:
#If DEBUG = 1 Then
' Do something
#Else
' Do something else
#End If
It's easy to remember if you just remember that the syntax is exactly the same as the other flow-control statements in VB 6, except that the compile-time conditionals start with a pound sign (#).
The trick is actually defining the DEBUG (or whatever) constant because I'm pretty sure that there isn't one defined by default. There are two standard ways of doing it:
Use the #Const keyword to define the constant at the top of each source file. The definition that you establish in this way is valid throughout the entire source module. It would look something like:
#Const DEBUG = 1
Set the constant in the project's properties. This would define a constant that is valid throughout the entire project (and is probably what you want for a "Debug" mode indicator).
To do this, enter something like the following in the "Conditional Compilation Constants" textbox on the "Make" tab of the "Project Properties" dialog box:
DEBUG = 1
You can define multiple constants in this dialog by separating each of them with a colon (:):
DEBUG = 1 : VERSION2 = 1
Remember that any constant which is not defined is assumed to be 0.
Cody has told you about conditional compilation. I'd like to add that if you want different behaviour when debugging on the IDE (e.g. turn off your own error handling so that the IDE traps errors) you don't need conditional compilation. You can detect the IDE at runtime like this.
On Error Resume Next
Debug.Print 1/0
If Err=0 then
'Compiled Binary
Else
'in the IDE
End if
This works because Debug.Print is omitted in the compiled EXE.
EDIT Remember to turn off On Error Resume Next !
EDIT You can wrap the check in a function like this (thanks CraigJ)
To achieve the same effect as MarkJ, but with error handling, you can use the following code.
Public Function GetRunningInIDE() As Boolean
Dim x As Long
Debug.Assert Not TestIDE(x)
GetRunningInIDE = x = 1
End Function
Private Function TestIDE(x As Long) As Boolean
x = 1
End Function
When you are running from within the IDE, there will be an extra overhead of calling a function (which is ridiculously small). When compiled, this evaluates to a simple number comparison.
This is my short and stable code. I think it is better than conditional constants, because you don't need to change it every complication time.
Public Function InIDE() As Boolean
On Error Resume Next
Debug.Print 0 / 0
InIDE = Err.Number <> 0
End Function

QTP VBScript type mismatch error in function without arguments

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.

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.

Resources