Assert Functions

The Assert function can be used to place assertions in the MAXScript code. It was made possible by the introduction of line number support in the MAXScript evaluation tree. It is available in 3ds Max 2010 and higher.

   

Additionally, an advanced set of assert functions and an AssertReporter message log system have been added to 3ds Max 2012 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.

If an assertion evaluates to false at run-time, an "Assertion fault" is generated which typically causes execution to abort. This draws attention to the location at which the logical inconsistency is detected and can be preferable to the behavior that would otherwise result.

   

Syntax:

assert <expression> [message:<string>] [options:#( #dialog | #listener | #debug_output_window | #debugBreak | #c_break | #all)] [showstack:<bool>]

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 will be 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 will be 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 of them all.

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].

A possible alternative approach in such code would be to add an IF statement to check that theCount is greater than zero.

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()

A possible alternative approach in such code would be to add an IF statement to check that theCount is greater than zero.

In this particular case though, the developer has opted to include an assert() call which postulates that theCount is expected to always be 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, too.

In this example, this is done for demonstration purposes only to illustrate how the assert() function works, not because it makes more sense over using an IF test which could prevent the whole division by zero altogether.

In real world practice though, 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 since the developer could not anticipate what the possible cases to test for could even be. In addition, the assert() has several report modes that could be useful and cannot be easily emulated with an IF test (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

 

Test Framework Assert Functions

A number of assert functions and an AssertReporter struct to manage their messages have been implemented in 3ds Max 2012 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:

  1. They report the file
  2. They report the line number
  3. They report the calling function
  4. They report the expected and actual values

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 <No Stack frame>, at line 1" unless the optional 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 <No Stack frame>, at line 1" unless the optional 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 <No Stack frame>, at line 1" unless the optional 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 1, got 1 - (No File) in frame <No Stack frame>, at line 1" unless the optional 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 <No Stack frame>, at line 1" unless the optional 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 <No Stack frame>, at line 1" unless the optional message: keyword argument is provided.

   

<boolean>assert_float <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 <No Stack frame>, at line 1" unless the optional message: keyword argument is provided.

   

<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 <No Stack frame>, at line 1" unless the optional 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 <No Stack frame>, at line 1" unless the optional 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 <No Stack frame>, at line 1" unless the optional 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.

   

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.

   

See Also