Chapter 3

Preliminaries

3.1  Libraries

The framework provides these libraries:

3.2  Mixins

The framework relies heavily on mixins. A mixin is a class parameterization modeled on a paper published by Flatt, Felleisen, and Krishnamurthi, available at http://www.cs.rice.edu/CS/PLT/Publications/#ffk-pldi97. The implementation of these mixins in MzScheme is with the combination of lambda and class. The framework provides a macro to simplify the checking and implementation of these mixins. It's syntax is very similar to the syntax for class*, section 3 in PLT MzScheme: Language Manual. The shape of a mixin is:

(mixin (interface-expr ...) (interface-expr ...)
  instance-variable-clause ...)

This macro expands into a procedure that accepts a class. The argument passed to this procedure must match the interfaces of the first interface-exprs expressions. The procedure returns a class that is derived from its argument. This result class must match the interfaces specified in the second interface-exprs section; it has clauses specified by instance-variable-clauses. The syntax of the initialization-variables and instance-variable-clause are exactly the same as class*/names, section 3.3 in PLT MzScheme: Language Manual.

The mixin macro does some checking to be sure that variables that the instance-variable-clauses refer to in their super class are in the interfaces. That checking and the checking that the input class matches the declared interfaces aside, the mixin macro's expansion is something like this:

(mixin (i<%> ...) (j<%> ...)
  clause ...)
=
(lambda (%)
  (class* % (j<%> ...)
    clause ...))

The mixin macro is provided by

(require (lib "macro.ss" "framework"))

3.3  Contracts

The framework's contract.ss library defines new forms of expression that specify contracts and new forms of expression that attach contracts to values.

This section describes two classes of contracts: contracts for functions (described in section 3.3.1) and contracts for flat values (described in section 3.3.2). A contract for a flat value is merely a predicate that accepts the value and returns a boolean indicating if the contract holds. In addition, this section describes are two forms for establishing a contract on a value (described in section 3.3.3).

3.3.1  Flat Contracts

This subsection describes helper functions for building contracts on flat values, i. e., predicates.

3.3.2  Function Contracts

This section describes the contract constructors for function contracts. This is their shape:

expr ::== ...
 | (case-> arrow-contract-expr ...)
 | arrow-contract-expr

arrow-contract-expr ::== 
 | (-> expr ... expr)
 | (-> expr ... any)
 | (->* (expr ...) expr (expr ...))
 | (->* (expr ...) (expr ...))
 | (->d expr ... expr)
 | (->*d (expr ...) expr)
 | (->*d (expr ...) expr expr)
 | (opt-> (expr ...) (expr ...) expr)
 | (opt->* (expr ...) (expr ...) (expr ...))

The -> contract is for functions that accept a fixed number of arguments and return a single result. The last argument to -> is the contract on the result of the function and the other arguments are the contracts on the arguments to the function. Each of the arguments to -> must be another contract expression or a predicate. For example, this expression:

(integer? boolean? . -> . integer?)

is a contract on functions of two arguments. The first must be an integer and the second a boolean and the function must return an integer. (This example uses MzScheme's infix notation, section 14.3 in PLT MzScheme: Language Manual so that the -> appears in a suggestive place).

If any is used as the last argument to ->, no contract checking is performed on the result of the function, and tail-recursion is preserved.

The ->* expression is for functions that return multiple results and/or have rest arguments. If two arguments are supplied, the first is the contracts on the arguments to the function and the second is the contract on the results of the function. If three arguments are supplied, the first argument contains the contracts on the arguments to the function (excluding the rest argument), the second contains the contract on the rest argument to the function and the final argument is the contracts on the results of the function.

The ->d and ->*d contract constructors are like their d-less counterparts, except that the result portion is a function that accepts the original arguments to the function and returns the range contracts. The range contract function for ->*d must return multiple values: one for each result of the original function. As an example, this is the contract for sqrt:

(number? 
 . ->d .
 (lambda (in)
   (lambda (out)
     (and (number? out)
          (abs (- (* out out) in) 0.01)))))

It says that the input must be a number and that the difference between the square of the result and the original number is less than 0.01.

The case-> expression constructs a contract for case-lambda function. It's arguments must all be function contracts, built by one of ->, ->d, ->*, or ->*d.

The opt-> expression constructs a contract for an opt-lambda function. The first arguments are the required parameters, the second arguments are the optional parameters and the final argument is the result. Each opt-> expression expands into case->.

The opt->* expression constructs a contract for an opt-lambda function. The only difference between opt-> and opt->* is that multiple return values are permitted with opt->* and they are specified in the last clause of an opt->* expression.

3.3.3  Attaching Contracts to Scheme Values

There are two special forms that add contract specifications, provide/contract and contract. A provide/contract form has this shape:

(provide/contract (id expr) ...)

and can only appear at the top-level of a module, section 5 in PLT MzScheme: Language Manual. As with provide, each identifier is provided from the module. In addition, clients of the module must live up to the contract specified by expr.

The contract special form is the primitive mechanism for attaching a contract to a value. Its purpose is as a target for the expansion of some higher-level contract specifying form.

The contract form has this shape:

(contract expr to-protect-expr positive-blame negative-blame contract-source)

The contract expression adds the contract specified by the first argument to the value in the second argument. The result of a contract expression is the result of the to-protect-expr expression, but with the contract specified by contract-expr enforced on to-protect-expr. The expressions positive-blame and negative-blame must be symbols indicating how to assign blame for positive and negative positions of the contract specified by contract-expr. Finally, contract-source, if specified, indicates where the contract was assumed. If absent, it defaults to the source location of the contract expression.

3.3.4  Contract Utility

The procedure contract? returns #t if its argument was constructed with one of the arrow constructors described earlier in this section, or if its argument is a procedure of arity 1.

-> ->d ->* ->*d case->

3.4  GUI Test Suite Utilities

The framework provides several new primitive functions that simulate user actions, which may be used to test applications. You use these primitives and combine them just as regular MzScheme functions. For example,

  (begin
    (test:keystroke #\A)
    (test:menu-select "File" "Save"))

sends a keystroke event to the window with the keyboard focus and invokes the callback function for the ``Save'' menu item from the ``File'' menu. This has the same effect as if the user typed the key ``A'', pulled down the ``File'' menu and selected ``Save''.

It is possible to load this portion of the framework without loading the rest of the framework. See the libraries section for more details.

Currently, the test engine has primitives for pushing buttons, setting check-boxes and choices, sending keystrokes, selecting menu items and clicking the mouse. Many functions that are also useful in application testing, such as traversing a tree of panels, getting the text from a canvas, determining if a window is shown, and so on, exist in MrEd.

3.4.1  An example

Here is an example program that enters a factorial procedure and computes (fact 4). To run this program, start DrScheme, click on the ``Console'' button, load this program and run (go). Then bring the DrScheme window to the front and click the mouse in the DrScheme window.

  (define go
    (lambda ()
      (sleep 3)
      (test:new-window (get-panel '(0 0 0 1)))       ; definitions canvas
      (test:menu-select "Edit" "Select All")
      (test:menu-select "Edit" "Delete")
      (type-line "(define fact")
      (type-line "(lambda (n)")
      (type-line "(if (zero? n)")
      (type-line "1")
      (type-line "(* n (fact (sub1 n))))))")
      (test:button-push (get-panel '(0 0 0 0 5 0)))  ; check-syntax button
      (test:button-push (get-panel '(0 0 0 0 5 3)))  ; execute button
      (sleep 3)
      (type-line "(fact 4)")
      (sleep 1)
      (printf "Test complete. Pending actions: ~s~n" 
              (test:number-pending-actions))))

  (define type-line
    (lambda (str)
      (for-each test:keystroke (string->list str))
      (test:keystroke #\return)))

  (define get-panel
    (lambda (path)
      (let loop ([path  path] 
                 [panel (send (test:get-active-frame) get-top-panel)])
        (if (null? path)
            panel
            (loop (cdr path) 
                  (list-ref (ivar panel children) (car path)))))))

3.4.2  Actions and completeness

The actions associated with a testing primitive may not have finished when the primitive returns to its caller. Some actions may yield control before they can complete. For example, selecting ``Save As...'' from the ``File'' menu opens a dialog box and will not complete until the ``OK'' or ``Cancel'' button is pushed.

However, all testing functions wait at least a minimum interval before returning to give the action a chance to finish. This interval controls the speed at which the test suite runs, and gives some slack time for events to complete. The default interval is 100 milliseconds. The interval can be queried or set with test:run-interval.

A primitive action will not return until the run-interval has expired and the action has finished, raised an error, or yielded. The number of incomplete actions is given by test:number-pending-actions.

Note: Once a primitive action is started, it is not possible to undo it or kill its remaining effect. Thus, it is not possible to write a utility that flushes the incomplete actions and resets number-pending-actions to zero.

However, actions which do not complete right away often provide a way to cancel themselves. For example, many dialog boxes have a ``Cancel'' button which will terminate the action with no further effect. But this is accomplished by sending an additional action (the button push), not by undoing the original action.

3.4.3  Errors

Errors in the primitive actions (which necessarily run in the handler thread) are caught and reraised in the calling thread.

However, the primitive actions can only guarantee that the action has started, and they may return before the action has completed. As a consequence, an action may raise an error long after the function that started it has returned. In this case, the error is saved and reraised at the first opportunity (the next primitive action).

The test engine keeps a buffer for one error, saving only the first error. Any subsequent errors are discarded. Reraising an error empties the buffer, allowing the next error to be saved.

The function test:reraise-error reraises any pending errors.

3.4.4  Technical Issues

Active Frame

The Self Test primitive actions all implicitly apply to the top-most (active) frame.

Thread Issues

The code started by the primitive actions must run in the handler thread of the eventspace where the event takes place. As a result, the test suite that invokes the primitive actions must not run in that handler thread (or else some actions will deadlock). See the eventspace section for more info.

Window Manager (Unix only)

In order for the Self Tester to work correctly, the window manager must set the keyboard focus to follow the active frame. This is the default behavior in Microsoft Windows and MacOS, but not in X windows.

In X windows, you must explicitly tell your window manager to set the keyboard focus to the top-most frame, regardless of the position of the actual mouse. Some window managers may not implement such functionality. You can obtain such an effect in Fvwm and Fvwm95 by using the option:

  Style  "*"  ClickToFocus