You can use the Assert function to place assertions in the MAXScript code. Additionally, an advanced set of assert functions and an AssertReporter message log system are available, and are being used for test suites by Autodesk during the development of 3ds Max.
Overview:
An assertion is a predicate (a true|false statement) placed in the code to indicate that the developer thinks the predicate always evaluates to true at that place.
Assertions are used to help reason about program correctness.
Syntax:
assert <expression> [message:<string>] [options:#( #dialog | #listener | #debug_output_window | #debugBreak | #c_break | #all)] [showstack:<boolean>]
Asserts that the result of the expression passed as the first argument to the function must be true at the given place in the code.
The expression must resolve to a Boolean value, otherwise a MAXScript exception is thrown.
If the expression returns true
, the code continues normally.
If the expression returns false
, the execution will be interrupted depending on the following options:
message:
- If this optional keyword is supplied, the string is displayed as the assert's message.
options:
- An array of the following MAXScript name options. If the array is empty, it defaults to #listener
.
#dialog
- brings up a modal dialog that halts execution of the application.
#listener
- prints out the failure info to theMAXScript Listener
#debug_output_window
- prints out to the Visual Studio output debugger window, (if present)
#debugBreak
- Halts execution in the MAXScript Debugger. If specified, the #dialog option is ignored. This option is only valid if quiet mode is false. Otherwise nothing happens.
#c_break
- Breaks execution in Visual Studio under the Debugger. This only works if running a debug build of 3ds Max under the Debugger. If specified, it first turns on the #debug_output_window
flag internally to aid in debugging. The #debugBreak
and #dialog
option will be ignored and it always occurs last to allow the other options to output their data.
#all
- All of the above, except #dialog
and #c_break
showstack:
- defines whether to include the MAXScript stack traceback in the printouts. This only applies for the #listener
and #debug_output_window
options. For all other options the stack information is ignored. Default is false.
EXAMPLE:
In the following example, the function loops through all objects in the scene and accumulates their positions. At the end, it divides the resulting value by the number of objects to calculate the average position.
The script developer has assumed that the scene will never be empty, thus the final result would be a valid position. If the number of objects is zero, the division by zero would not crash the program, but would produce a Point3 value of[-1.#IND,-1.#IND,-1.#IND].
fn averageObjectPosition =
(
theSum = [0,0,0]
theCount = objects.count
for i = 1 to theCount do
(
theSum += objects[i].pos
)
assert (theCount > 0) message: "theCount expected to be positive, but was found not to be!" options:#(#all) showstack:true
theSum /= theCount
)
averageObjectPosition()
The developer has opted to include an assert()
call, which postulates that theCount
is expected to be always greater than zero and if that is not the case, the execution of the code will break in the debugger and will show the stack in the Listener.
In this example, this is done for demonstration and to illustrate how the assert()
function works.
In real world, the assert()
would be typically used to ensure the correctness of values going through much more complex calculations where catching irregularities with IF tests would be difficult because the developer cannot anticipate what can be the possible cases to test. In addition, the assert()
has several report modes that could be useful (like breaking in the C++ Debugger when running a Debug build of 3ds Max).
If the above function is executed with an empty scene, the result would be
A number of assert functions and an AssertReporter
struct to manage their messages have been implemented and are now being used for internal testing at Autodesk.
These functions are available to all MAXScript users and can be employed in custom test frameworks to ensure the correctness of the program flow.
These Test Framework functions have the following advantages:
All asserts are reported automatically.
This new system should significantly reduce the visual clutter of the asserts as developers have the option of NOT having to construct massive error strings for the message of the asserts (although they still can provide custom messages where needed). Also, these asserts run quietly and gather their messages in a dedicated AssertReporter
to be queried and analyzed after running the code without interrupting the actual test run.
The assert functions are:
<boolean>assert_true <expression>expectedToBeTrue [message:<string>]
Returns false and logs an assert failure message with the AssertReporter
if the return value of the expression is not true.
Will log a default message in the form of *"Assert - Expected true, got false - (No File) in frame message:
keyword argument is provided.
<boolean>assert_false <expression>expectedToBeFalse [message:<string>]
Returns false and logs an assert failure message with the AssertReporter
if the return value of the expression is not false.
Will log a default message in the form of *"Assert - Expected false, got true - (No File) in frame message:
keyword argument is provided.
<boolean>assert_equal <value>expected <expression>actual [message:<string>]
Returns false and logs an assert failure message with the AssertReporter
if the return value of the expression passed as second argument is not equal to the expected value passed as first argument.
Will log a default message in the form of *"Assert - Expected 1, got 2 - (No File) in frame message:
keyword argument is provided.
<boolean>assert_not_equal <value>expected <expression>actual [message:<string>]
Returns false and logs an assert failure message with the AssertReporter
if the return value of the expression passed as second argument is equal to the expected value passed as first argument.
Will log a default message in the form of *"Assert - Expected value not equal to message:
keyword argument is provided.
<boolean>assert_defined <expression>expectedToBeDefined [message:<string>]
Returns false and logs an assert failure message with the AssertReporter
if the returns value of the expression is undefined.
Will log a default message in the form of *"Assert - Expected "defined", got undefined - (No File) in frame message:
keyword argument is provided.
<boolean>assert_undefined <expression>expectedToBeUndefined [message:<string>]
Returns false and logs an assert failure message with the AssertReporter
if the returns value of the expression is not undefined.
Will log a default message in the form of *"Assert - Expected undefined, got 1 - (No File) in frame message:
keyword argument is provided.
<boolean>assert_float <float>expected <expression>actual [tolerance:<float>delta] [message:<string>]
<boolean>assert_float_equal <float>expected <expression>actual [tolerance:<float>delta] [message:<string>]
Returns false and logs an assert failure message with the AssertReporter
if the returns value of the expression passed as second argument is not the expected float value.
If the tolerance:
optional keyword argument is provided, the assert will return false if the actual return value is different from the expected one beyond the tolerance delta value.
Will log a default message in the form of *"Assert - Expected 1.0, got 2 - (No File) in frame message:
keyword argument is provided.
The assert_float_equal()
version is identical to assert_float()
, but is provided as it has a more meaningful name that matches the other assert functions.
Available in 3ds Max 2021.1 Update and higher.
<boolean>assert_string_equal <string>expected <exression>actual [ignorecase:<boolean>val]
[message:]
Returns false if the return value of the expression passed as second argument is not equal to the expected string.
The default for the optional ignorecase:
keyword argument is true. It should be supplied as false to perform case sensitive comparison.
Will log a default message in the form of *"Assert - Expected "A", got "b" - (No File) in frame message:
keyword argument is provided.
<boolean>assert_point3_equal <point3>expected <expression>actual [tolerance:<float>delta]
[message:]
Returns false if the return value of the expression passed as second argument is not equal to the expected Point3 value.
If the tolerance:
optional keyword argument is provided, the assert will return false if any of the actual return value's components is different from the expected ones' corresponding component beyond the tolerance delta value.
Will log a default message in the form of *"Assert - Expected [1,2,3], got [1.1,2.1,3.1] - (No File) in frame message:
keyword argument is provided.
<boolean>assert_matrix_equal <matrix>expected <matrix>actual [tolerance:<float>delta]
[message:]
Returns false if the return value of the expression passed as second argument is not equal to the expected matrix value.
If the tolerance:
optional keyword argument is provided, the assert will return false if any of the components of the actual return value is different from the expected ones' corresponding component beyond the tolerance delta value.
Will log a default message in the form of *"Assert - Expected (matrix3 [1,0,0] [0,1,0] [0,0,1] [0,0,0]), got (matrix3 [1,0,0] [0,1,0] [0,0,1] [42.1234,43.8329,0]) - (No File) in frame message:
keyword argument is provided.
Note that due to the limited precision of numbers representation in computers, values at the exact edge of the tolerance delta might or might not cause an assert to be logged when using the assert_float()
and assert_point3_equal()
methods.
EXAMPLE:
floatVariable = 1.0
assert_float 1.1 floatVariable tolerance:0.1
-->"Assert - Expected 1.1, got 1.0 ..."
But when using the following code, there is no assert:
point3Variable = [1,2,3]
assert_point3_equal [1.0,2.0,3.1] point3Variable tolerance:0.1
The explanation for the above results is that the first method checks whether the difference of the expected and actual values is less than the tolerance value and in this case, it is not. The two values are converted to doubles internally and the first 7 decimal places are identical (all zeros), so the difference is NOT less than the tolerance delta.
In the second case though, the Point3 value [1.0,2.0,3.1] is actually represented internally as [1.0,2.0,3.0999999]. Thus the difference between 3.0 and 3.0999999 is just below the tolerance and due to this precision error, the assert passes as true.
<boolean>assert_array_equal expected actual [message:<string>] [tolerance:<boolean>] [ignoreCase:<boolean>]
Returns false if the return value of the expression passed as second argument is not equal to the expected array.
Available in 3ds Max 2021.1 Update and higher.
The default for the optional ignorecase:
keyword argument is true. It should be supplied as false to perform case sensitive comparison for arrays that contain strings.
If the tolerance:
optional keyword argument is provided, the assert will return false if any of the numeric elements of the actual return value is different from the expected ones' corresponding elements beyond the tolerance delta value.
EXAMPLE:
The following will all return false:
assert_array_equal #(1) #(1.)
assert_array_equal #(1) #(1,2)
assert_array_equal #(1,2) #(1)
assert_array_equal #(1) #(2)
assert_array_equal #(1.) #(2.)
assert_array_equal #([1,2]) #([1,3])
assert_array_equal #([1,2,3]) #([1,2,4])
assert_array_equal #([1,2,3,4]) #([1,2,3,5])
assert_array_equal #(matrix3 1) #(transmatrix [1,1,1])
assert_array_equal #("aa") #("ab")
assert_array_equal #(1.) #(2.) tolerance:0.5
assert_array_equal #("aa") #("AA") ignoreCase:false
<boolean>assert_matchpattern expected actual [message:<string>] [ignoreCase:<boolean>]
Returns false if the return value of the expression passed as second argument is not equal to the expected string. The second argument (actual
) can be a match pattern, where ? represents a single character and * represents one or more characters.
The default for the optional ignorecase:
keyword argument is true. It should be supplied as false to perform case sensitive comparison for the strings.
EXAMPLE:
The following will all return false:
s="text1"
assert_matchpattern "tex?" s
assert_matchpattern "T*" s ignoreCase:false
<boolean>assert_bitarray_equal expected_bitarray actual_bitarray [message:<string>]
Returns false if the return value of the expression passed as second argument is not equal to the expected array.
Available in 3ds Max 2021.1 Update and higher.### The AssertReporter
Struct
The global AssertReporter
C++ struct collects the results of the asserts and provides methods for managing and accessing the results after a test run.
General Methods:
<void>AssertReporter.Clear()
Removes all assertion entries from the AssertReporter
and resets all failures and exceptions results to zero.
Reporting Methods:
<string array>AssertReporter.GetAssertFailures()
Returns an array of strings containing the assert failures stored in the AssertReporter
.
<integer>AssertReporter.GetAssertFailuresCount()
Returns the count of the assert failures stored in the AssertReporter
. This is faster than calling (AssertReporter.GetAssertFailuresCount()).count
<string array>AssertReporter.GetExceptionFailures()
Returns an array of strings containing any exceptions logged with the AssertReporter
.
<integer>AssertReporter.GetExceptionFailuresCount()
Returns the count of the exception failures stored in the AssertReporter
. This is faster than calling (AsserReporter.GetExceptionFailuresCount()).count
<array of strings>AssertReporter.GetMessages()
Returns an array of strings containing any messages that were logged through the AssertReporter
, regardless of their type.
Logging Methods:
<void>AssertReporter.LogMessage <string>Message
Saves a message with the AssertReporter
. The messages can be retrieved later using AssertReporter.GetMessages()
<void>AssertReporter.LogException <string>Message
Saves a message as an exception with the AssertReporter
. The exception messages can be retrieved later using AssertReporter.GetExceptionFailures()
<void>AssertReporter.LogAssertFailure <string>Message
Saves a message as an assert failure with the AssertReporter
. The failure messages can be retrieved later using AssertReporter.GetAssertFailures()
State Methods:
The following two methods can be used for saving some sort of state during test runs, and is only provided for future flexibility.
<void>AssertReporter.SetUserData <string>UserData
Saves a custom user defined string in the AssertReporter
.
<void>AssertReporter.GetUserData <value>argument
Gets a custom user defined string from the AssertReporter
.
EXAMPLE:
The following example demonstrates the functionality of the assert functions and the AssertReporter in an artificial test situation where a number of variable have been defined in the local scope of a function and each variable is tested by an assert to verify its value (and of course most of these tests were designed to produce actual assert messages!)
fn TestAsserts =
(
local undefinedValue
local booleanTrueVariable = true
local booleanFalseVariable = false
local floatVariable = 1.0
local stringVariable = "MAXScript"
local point3Variable = [1,2,3]
local matrix3Variable = matrix3 [1,0,0] [0,1,0] [0,0,1] [100,200,-300]
--this is line 10
AssertReporter.Clear()
AssertReporter.LogMessage "Asserts Demo Start..."
AssertReporter.SetUserData ("Tested Values Were:"+ undefinedValue as string + "," + booleanTrueVariable as string + "," + booleanFalseVariable as string + "," + floatVariable as string + ","+stringVariable+","+point3Variable as string + ","+matrix3Variable as string)
assert_true booleanFalseVariable
assert_false booleanTrueVariable
assert_equal 2.0 floatVariable
assert_not_equal 2.0 floatVariable
assert_defined undefinedValue
assert_undefined floatVariable
assert_float 1.1 floatVariable tolerance:0.01
assert_float 1.1 floatVariable tolerance:0.2
assert_string_equal "Python" stringVariable
assert_string_equal "maxscript" stringVariable ignorecase:false message:(" MAXScript is usually case-insensitive, but this assert isn't!")
assert_point3_equal [1.0,2.0,3.1] point3Variable
assert_point3_equal [1.0,2.0,3.1] point3Variable tolerance:0.1
assert_matrix_equal (matrix3 1) matrix3Variable
if floatVariable > 0.0 do AssertReporter.LogAssertFailure "FloatVariable seems to be positive, I am upset!"
AssertReporter.LogException "Nothing Really Worked!"
AssertReporter.LogMessage "Asserts Demo End."
--this is line 30
format "--All Logged Messages:\n"
print (AssertReporter.GetMessages())
format "--User Data:\n"
print (AssertReporter.GetUserData 0)
format "--Failures: %\n" (AssertReporter.GetAssertFailuresCount())
print (AssertReporter.GetAssertFailures())
format "--Exceptions: %\n" (AssertReporter.GetExceptionFailuresCount())
print (AssertReporter.GetExceptionFailures())
)
TestAsserts()
After saving the above code to a file called "c:\temp\TestAsserts.ms", evaluating the script produced the following output:
TestAsserts()
--All Logged Messages:
"Asserts Demo Start..."
"Assert - Expected true, got false - (C:\Temp\TestAsserts.ms) in frame TestAsserts, at line 14"
"Assert - Expected false, got true - (C:\Temp\TestAsserts.ms) in frame TestAsserts, at line 15"
"Assert - Expected 2.0, got 1.0 - (C:\Temp\TestAsserts.ms) in frame TestAsserts, at line 16"
"Assert - Expected "defined", got undefined - (C:\Temp\TestAsserts.ms) in frame TestAsserts, at line 18"
"Assert - Expected undefined, got 1.0 - (C:\Temp\TestAsserts.ms) in frame TestAsserts, at line 19"
"Assert - Expected 1.1, got 1.0 - (C:\Temp\TestAsserts.ms) in frame TestAsserts, at line 20"
"Assert - Expected "Python", got "MAXScript" - (C:\Temp\TestAsserts.ms) in frame TestAsserts, at line 22"
"Assert - MAXScript is usually case-insensitive, but this assert isn't! - Expected "maxscript", got "MAXScript" (C:\Temp\TestAsserts.ms) in frame TestAsserts, at line 23"
"Assert - Expected [1,2,3.1], got [1,2,3] - (C:\Temp\TestAsserts.ms) in frame TestAsserts, at line 24"
"Assert - Expected (matrix3 [1,0,0] [0,1,0] [0,0,1] [0,0,0]), got (matrix3 [1,0,0] [0,1,0] [0,0,1] [100,200,-300]) - (C:\Temp\TestAsserts.ms) in frame TestAsserts, at line 26"
"FloatVariable seems to be positive, I am upset!"
"Nothing Really Worked!"
"Asserts Demo End."
--User Data:
"Tested Values Were:undefined,true,false,1.0,MAXScript,[1,2,3],(matrix3 [1,0,0] [0,1,0] [0,0,1] [100,200,-300])"
--Failures: 11
"Assert - Expected true, got false - (C:\Temp\TestAsserts.ms) in frame TestAsserts, at line 14"
"Assert - Expected false, got true - (C:\Temp\TestAsserts.ms) in frame TestAsserts, at line 15"
"Assert - Expected 2.0, got 1.0 - (C:\Temp\TestAsserts.ms) in frame TestAsserts, at line 16"
"Assert - Expected "defined", got undefined - (C:\Temp\TestAsserts.ms) in frame TestAsserts, at line 18"
"Assert - Expected undefined, got 1.0 - (C:\Temp\TestAsserts.ms) in frame TestAsserts, at line 19"
"Assert - Expected 1.1, got 1.0 - (C:\Temp\TestAsserts.ms) in frame TestAsserts, at line 20"
"Assert - Expected "Python", got "MAXScript" - (C:\Temp\TestAsserts.ms) in frame TestAsserts, at line 22"
"Assert - MAXScript is usually case-insensitive, but this assert isn't! - Expected "maxscript", got "MAXScript" (C:\Temp\TestAsserts.ms) in frame TestAsserts, at line 23"
"Assert - Expected [1,2,3.1], got [1,2,3] - (C:\Temp\TestAsserts.ms) in frame TestAsserts, at line 24"
"Assert - Expected (matrix3 [1,0,0] [0,1,0] [0,0,1] [0,0,0]), got (matrix3 [1,0,0] [0,1,0] [0,0,1] [100,200,-300]) - (C:\Temp\TestAsserts.ms) in frame TestAsserts, at line 26"
"FloatVariable seems to be positive, I am upset!"
--Exceptions: 1
"Nothing Really Worked!"
OK
Note that the Assert on line 21 did not output a failure because the tolerance was set to 0.2, allowing 1.0 to pass a test against 1.1, which the Assert on the previous line 20 did not allow.
Line 23 had a custom message prepended to the default message.
Line 25 also passed due to a higher tolerance delta.
As you can see, with these assert functions you can collect data about the state of variables with various value types or the return values of expressions and collect the information in the AssertReporter to analyze after a test run of the code or dump to a log file for later processing.