Expecco API/en

Aus expecco Wiki (Version 2.x)
Zur Navigation springen Zur Suche springen

Inhaltsverzeichnis

How to Program[Bearbeiten]

Before you start programming, please read the "How to Program" document, which describes how program code is handled in expecco. Unless you are familiar with the dynamics of a Smalltalk development environment, some of it may be unknown to you, and you will have more fun and be more productive, if you know the power of the tools. For the best development experience, take a look at the debugger, workspace (notepad) and data inspectors.

The rest of this document describes the syntax and semantics of the elementary action languages; for tool usage, please read the HowTo document.

expecco API[Bearbeiten]

The expecco API provides functions and access to the underlying class library for the use in elementary blocks written in Smalltalk and JavaScript (i.e. for code which is executed inside expecco itself). This API is not available for elementary blocks written in other languages which are executed by external script engines (Shell, Batch, Python etc.) or inside a different Java or CLR Virtual Machine (Groovy, VBScript, IronPython).

For an short introduction to the Smalltalk programming language, please read the Smalltalk tutorial in the Smalltalk/X online manual. For a full book on learning Smalltalk, read "The joy of Smalltalk, An introduction to Smalltalk" by Ivan Tomek.

The API for Groovy elementary blocks is different and described below.


JavaScript and Smalltalk Elementary Blocks[Bearbeiten]

The JavaScript and the Smalltalk API consist of the same functions - both call into the underlying Smalltalk/X system, for which extensive documentation is available as Online Documentation and as Class Reference. The Smalltalk/X class library is ANSI compatible; therefore the official ANSI documents and Smalltalk literature are also valuable sources of information.

JavaScript and Smalltalk only differ slightly in their syntax; semantically they are very similar:

JavaScript Smalltalk
this self the current activity
this.functionName() self functionName a call without arguments
this.functionName(arg) self functionName:arg a call with one argument
this.namePart1_part2(arg1,arg2) self namePart1:arg1 part2:arg2 two arguments
functionName(...) self functionName... implicit this/self receiver
accessor self accessor slot access - implicit this/self send
stat1 ; stat2 stat1 . stat2 statement terminator / separator
in ST: required to separate statements
in JS: required to terminate every statement.
Thus, in Smalltalk, the last period "." inside a method or block can be and often is omitted.
if (cond) {
  ifStats
} else {  
  elseStats
}
cond ifTrue:[
  ifStats
] ifFalse:[
  elseStats
]
conditional execution
while (cond) {
  stats
}
[ cond ] whileTrue:[
  stats
]
while-loop. Notice the extra brackets in Smalltalk
function () { stats } [ stats ] an anonymous (inner) function (called "block" in ST)
function (a1, a2,...) { stats } [:a1 :a2 ...| stats ] in ST: blocks are references to anonymous function
var v1, v2, ... ; | v1 v2 ... | local variables inside a function (or block)
return expr; ^ expr return a value.
In ST: a return from within a block returns the enclosing top-level function
In JS: a return returns from the inner function

Smalltalk beginners may be irritated by the missing binary operator precedence rules. In Smalltalk, all operators have the same precedence and are evaluated left to right. Thus, "a + b * 5" in JavaScript means "a + (b * 5)", whereas in Smalltalk, it is evaluated as "(a + b) * 5". For this, as a guideline (and actually a strict convention), all Smalltalk expressions should be parenthesized to make the intention clear, whenever it conflicts with the intuitive (standard mathematical) precedence.

All functions are actually implemented in Smalltalk and follow the standard Smalltalk naming conventions. The same function names are used for JavaScript. As seen above, this scheme works well for functions without or with a single argument, but requires a name translation for functions with more than one argument. This translation is done by the JavaScript compiler by replacing every colon (:) of the Smalltalk name by an underline (_) character, except for the last colon. Thus for example, the Smalltalk name "at:put:" will be replaced to "at_put" in JavaScript.

In JavaScript, a function-name alone (i.e. without explicit receiver) is translated into a self-send; thus "this.foo()" and "foo()" are equivalent.

Also, for non-argument accessor functions (getters), the empty argument list can be omitted in JavaScript; therefore, "foo" and "this.foo", "foo()" and "this.foo()" are all equivalent.

For example, the JavaScript call:

    this.environmentAt("foo")

is written in Smalltalk as:

    self environmentAt:'foo'

and, to demonstrate the multi-argument translation rule, the Smalltalk code:

    self environmentAt:'foo' put:1234

is written in JavaScript as:

    this.environmentAt_put("foo", 1234)

Syntax[Bearbeiten]

For a formal specification of the JavaScript and Smalltalk languages, see the appendixes below. The following gives a rough overview over the most common syntactic constructs.

JavaScript Smalltalk Notes
this self the current activity
null nil a null reference (UndefinedObject)
; . Statement Terminator/Separator
"..." '...' a String constant (JavaScript allows single quotes too)
#'...' a Symbol constant (not available in JavaScript)
#... also a Symbol constant (not available in JavaScript)
[ el1 , el2 , ... ] #( el1 el2 ... ) an Array constant (elements must be constants)
[ expr1 , expr2 , ... ] { expr1 . expr2 . ... } a computed Array (notice the expression terminators in ST, and that "," (comma) is an operator in ST)
#[ el1 el2 ... ] a ByteArray constant (not available in JavaScript)
( num / den ) a Fraction constant (not available in JavaScript)
rcvr.f () rcvr f function call
rcvr.f (arg) rcvr f: arg with 1 arg
rcvr.a_b (arg1, arg2) rcvr a: arg1 b: arg2 with 2 args
notice the different names of the function
"a_b" vs."a:b:"
return; ^ self without return value (in ST: from outer function; in JS: from current function)
return expr; ^ expr with return value (in ST: from outer function; in JS: from current function)
return from execute; ^ self return outer from inner function
! expr expr not
e1 && e2 (e1 and:[ e2 ]) non evaluating and (e2 is not evaluated if e1 is false)
e1 !! e2 (e1 or:[ e2 ]) non evaluating or (e2 is not evaluated if e1 is true)

Syntactic Sugar[Bearbeiten]

Some syntactic constructs of the JavaScript language are implemented as library functions in Smalltalk. Among others, most noteworthy are conditional execution (if-the-else) and loops.
The JavaScript syntax is mapped to corresponding Smalltalk library functions as follows:

JavaScript Smalltalk Notes
if (expr) { .. } (expr) ifTrue:[ ... ]
if (! expr) { .. } (expr) ifFalse:[ ... ]
if (expr) { .. }
else { ... }
(expr) ifTrue:[ ... ]
ifFalse:[...]
while (expr) { .. } [expr] whileTrue:[ ... ] Notice the square brackets
for (expr1;expr2;expr3) { .. } expr; [expr2] whileTrue:[ ... expr3 ]
for (var i=start; i <= stop; i += incr) { .. } start to: stop by: incr do:[:i | ... ]
try { .. } finally {...} [...] ensure:[...]
try { .. } catch(e) {...} [...] on:Error do:[:e | ...]
try { .. } catch(e) {...} finally {...} [ [...] on:Error do:[:e | ...] ] ensure:[ ... ]
var++ var := var + 1
var-- var := var - 1
arr[ idx ] arr at: idx Array and Dictionary indexed read
Notice that JavaScript uses 0-based indexing, whereas Smalltalk is 1-based.
arr[ idx ] = expr arr at: idx put: expr Array and Dictionary indexed write

Operators[Bearbeiten]

JavaScript Smalltalk Notes
% \ modulu operator
<< bitShift: left-shift; negative shift count is right-shift
>> rightShift: right-shift; negative shift count is left-shift


Array Indexing[Bearbeiten]

A big potential pitfall is the different indexing base used in Smalltalk vs. JavaScript: in Smalltalk, array indices start at 1 and end at the array's size. In JavaScript, indices start at 0 and end at the array's size minus 1.

This also affects some character- and substring search functions, which return 0 (zero) in Smalltalk, whereas some of the standard JavaScript functions return -1 if nothing is found.

The following functions expect or return 0-based indices if used in JavaScript:

  • collection.indexOf(element)
    attention: there exists a corresponding Smalltalk method, which returns a 1-based index
  • collection.lastIndexOf(element)
    attention: there exists a corresponding Smalltalk method, which returns a 1-based index
  • collection.indexOf(element, startIndex)
    the corresponding Smalltalk method "indexOf:element startingAt:startIndex" uses 1-based indices
  • collection.lastIndexOf(element, startIndex)
    ditto


We admit that this is a little confusing at times, but there is no easy solution to this: and we decided to neither change the Smalltalk nor the JavaScript scripting languages inside expecco.

To make your intentions explicit, you can use variants of the "indexOf"/"lastIndexOf" functions which make these implicit differences more explicit: "indexOf0()" / "lastIndexOf0()" and "indexOf1()" / "lastIndexOf1()" are available for both languages.

Shame on us:
the above array indexing behavior was changed for indexOf: between 2.7 and 2.8 (due to a missing translation, the 2.7 versions used 1-based indices in both languages). This leads to a problem, if you run any old 2.7 suite in newer expecco versions without reimporting the StandardLibrary or if you used those functions in your own elementary code. To avoid a need to touch those old suites, we have added a backward-bug-compatibility mode, which is described in a separate note of the release notes.

Inner Functions[Bearbeiten]

Inner Functions in JavaScript[Bearbeiten]

It is possible to create functions within the elementary block's code. This example shows how to do so:

  execute() {
    ...
    function f(arg1, arg2) {
      return(arg1 + arg2);
    }
    ...
}

A inner function f is created with two arguments (arg1, arg2), which returns the sum of both. The function is called like this:

   f(3,5)

Inner functions can be used for example to filter specific elements from collections, as in:

   function isEven(n) { return n.even(); }

   var numbers = [1,2,3,4,5,6,7];
   var evenNumbers = numbers.select( isEven );

Inner functions are first class objects: they can be stored in variables, collections, passed as argument or returned from functions. They can even be passed to other blocks via input- and output pins or via environment variables. Functions can be anonymous; the above example could also be written as:

  execute() {
    var f;
    ...
    f = function (arg1, arg2) {
      return(arg1 + arg2);
    }
    ...
}

(notice the missing function name after the "function" keyword).


Notice, that there is a difference in the behavior of the return statement between JavaScript inner functions and Smalltalk blocks: a JavaScript "return" statement inside an inner function returns from that inner function only, whereas a return in Smalltalk always returns from the outer-most method. To return from the outermost function in JavaScript, use the "return from <fnName>" statement form.

In expecco, where the outermost function is always named "execute", write:

    execute
       ...
       function myFunction() {
           ...
           return from execute;
       }
    ...

(notice that the execute function is not supposed to return a value)

Inner Functions in Smalltalk (Blocks)[Bearbeiten]

In Smalltalk, inner functions are called "block", and are defined as:

   f := [:arg1 :arg2 | arg1 + arg2 ].

This block can be called by sending at a "value:value:" message (one "value:" for each argument):

   f value:3 value:5

Blocks without argument are defined as:

   f := [ Transcript showCR:'blabla: Euler was great' ].

and invoked with a simple "value" message.

In both JavaScript and Smalltalk code, it is an error to invoke an inner function/block with a wrong number of arguments.

Smalltalk blocks and JavaScript inner functions can be used interchangeable - i.e. it is possible to pass a JS inner function to a collection method such as "collect:" or "select:". It is also possible, to pass either via input/output pins to other activities (which is not considered good style, as it could make the program quite hard to understand, if not used with caution).

Notice again, that the behavior of the Smalltalk return ("^") is different from a JavaScript return ("return-Statement") inside inner functions. The JavaScript return returns a value from the inner function, whereas the Smalltalk return forces a return from the containing method (the block's "execute" method). The Smalltalk return always behaves like the "return from execute" JavaScript special form.

Example uses for Inner Functions[Bearbeiten]

In Smalltalk, blocks are very often used when enumerating collections. For example, code corresponding to a C# 3.0 collection "select (where(x => ...))" is written in Smalltalk as:

    |names namesStartingWithA|

    names := #( 'Alice' 'Ann' 'Bob' 'Mallory' ).
    namesStartingWithA := names select:[:n | n startsWith:'A'].

or in JavaSript as:

    var names, namesStartingWithA;

    names = [ "Alice" , "Ann" , "Bob" , "Mallory" ];
    namesStartingWithA = names.select( function(n) { n.startsWith("A"); } );

To find the first element in a collection, for which some condition is true, use:

    |names firstNameContainingAnO|

    names := #( 'Alice' 'Ann' 'Bob' 'Mallory' ).
    firstNameContainingAnO:= names detect:[:n | n includesString:'o'].

or in JavaSript as:

    var names, firstNameContainingAnO;

    names = [ "Alice" , "Ann" , "Bob" , "Mallory" ];
    firstNameContainingAnO = names.detect( function(n) { n.includesString("o"); } );

For more information on the Smalltalk syntax, see [Smalltalk Basics] in the [Smalltalk Online Tutorial].

Builtin Data Types[Bearbeiten]

The builtin types below and user defined primary types are mapped to corresponding classes of the underlying Smalltalk runtime system. An introductionary overview and links to the detailed documentation of individual classes is found in the Smalltalk Class Documentation of the Smalltalk/X online documentation. The following gives a rough summary of the main concepts. A good entry point for further learning is the Smalltalk/X Basic Classes Overview Document, which provides links to the most heavily used classes and more detailed information.

Numeric Types[Bearbeiten]

The underlying numeric type implementation supports multiple number representations. These can be used transparently in mixed-operations, and values are automatically converted as required. For example, the division of two integers returns another integer iff the division does not produce a remainder. Otherwise, a fraction object is returned. Although very very rarely required in practice, values can be converted explicitly, via one of the "asXXX" messages, if required:

  • asFloat()
  • asInteger()
  • asFraction()
  • asFixedPoint(scale)

Thus, if you want to avoid fractions (why would you?), process the result of an integer division using one of the asFloat(), truncated(), floor(), ceiling() or rounded() functions. But be aware that fractions provide an exact result, whereas float arithmetic is inherently imprecise and rounds on the the last bit.

Integer[Bearbeiten]

This represents arbitrary precision integral numbers. Conversion and memory allocation is completely automatic. Thus, you can write:

x = 100.factorial();

to get the huge number:

933262154439441526816992388562667004907159682643816214685929638952175999932299156089
41463976156518286253697920827223758251185210916864000000000000000000000000

Small integer values (<32bit or <64bit, depending in the underlying hardware's native word length) are stored more efficiently than large integers - however, the conversion and representation as used by the system is completely transparent and automatic. The same is true for mixed mode arithmetic between small and large integers.

Occasionally it is required to perform modulo arithmetic in 32 or 64bit, for example to reproduce or check values as generated by C or Java programs. For this, use methods from the "special modulo" category, such as "add_32:", "add_32u", "mul_32" etc. Use a class browser to find those methods.

Integer constants (of arbitrary size) can be given in hexadecimal (Smalltalk: 16rxxx, JavaScript 0xXXXX), octal (Smalltalk: 8rxxx, JavaScript 0XXXX), and binary (only in Smalltalk: 2rxxx). In fact, Smalltalk supports any base from 2 to 36 written as <b>rxxx, where <b> is the base. Thus 3r100 is the ternary representation of the integer 9.

[More Info] [Class Documentation]

Float[Bearbeiten]

Float represents double precision IEEE floating point numbers (64bit). Although seldom needed, conversion to single precision (32bit) and high precision (80bit/128bit, depending on the underlying hardware) is possible via the asShortFloat() and asLongFloat() conversion functions.
[More Info] [Class Documentation]

Float Rounding Errors[Bearbeiten]

Be again reminded that floating point arithmetic is inherently inaccurate: there will almost always be rounding errors on the last bit. The reason is inherent in that most rational numbers simply cannot be represented as the sum of powers of two. For example, the 0.3 is one of them: no sum of powers of two (1/2, 1/4, 1/8, ...) is able to represent this number exactly.

Typically, print functions (which convert floating point values to a string representation) will "cheat" and round on the last bit. However, if you operate with such numbers (i.e. add, multiply, etc.) these rounding errors will accumulate and may finally add up to more than one bit of error. When this happens, you will get the typical .999999 result strings. As an example, try adding 0.3 a hundred times, as in:

|t|
t := 0.3.
1000 timesRepeat:[t := t + 0.3].
t print

which may give "300.300000000006" as a result.

As a consequence, NEVER compare floating point values for equality. In the above case, the comparison with "300.3" would return false!

Floating point numbers should always be compared by giving a range (i.e. between x-delta and x+delta) or by giving a number of valid digits, by which to compare. expecco provides a number of blocks for such "almost-equal" comparisons. Of course, there are also functions in the Float class, in case you need this in elementary Smalltalk or JavaScript code.

Fraction[Bearbeiten]

Fractions are arbitrary-precision rational numbers. You will get them when dividing two integers and the result is not integral. Fractions represent an exact result, in contrast to floats, which are only approximations.

For example:

1 / 3

returns a fractional result with a numerator of 1 and a denominator of 3 (as opposed to the // operator, which truncates the result of the division to an integer). Fractions have the advantage of avoiding rounding errors; thus, when the above fraction is divided by 3, you will get (1/9), which we can multiply by 9 to get the exact 1 (an integer) without any rounding error. Be reminded that this is typically not the case with Float or Double precision numbers. Due to rounding errors on the last bit, you will often get 0.9999999 as a result there.

Fractions automatically reduce themself - therefore, when adding (1/3) + (2/6), you will get (2/3).
[More Info] [Class Documentation]

FixedPoint[Bearbeiten]

FixedPoint numbers are decimals, with a configurable number of post-decimal digits. They are typically used when dealing with money. Like fractions, they avoid rounding errors. However, when printed, they round in their last digit (however, the exact information is always kept for further processing). FixedPoint numbers are perfect to represent money and other fractional data, which must be represented with a fixed number of post-decimal digits.
[More Info] [Class Documentation]

Number[Bearbeiten]

The Number type is an abstract union type which includes all of the above types. Thus, a variable declared as being of "Number" type can take any of the above values. Due to the polymorphic implementation of the underlying numeric class library, most operations can be passed any of the above values (even mixing arguments is possible). For example, the "Add" elementary block, which is implemented by simply calling the Smalltalk "+" operation, is able to deal with any combination of argument types.
[More Info]

String Types[Bearbeiten]

Strings can come in three flavours, depending on how many bits are required to encode the character's codePoint. In general, UNICODE is used internally as the encoding. However, converters are available to translate into other encodings, such as JIS or ISO 8859-x.

String[Bearbeiten]

This is used to represent character strings, where each individual character has a one-byte encoding. I.e. the values are 0..255. Individual characters can be accessed by an integral index starting with 1 in both Smalltalk and JavaScript.
[More Info] [Class Documentation]

Unicode16String[Bearbeiten]

This is used to represent character strings, where at least one character needs a two-byte encoding. I.e. any character's codepoint is in the range 0x0100 .. 0xFFFF. In fact, there is even a Unicode32String class (see below), which supports the full four-byte unicode character set. But in practice, such strings are very rarely ever encountered or needed.

Unicode32String[Bearbeiten]

This is used to represent character strings, where at least one character needs more than a two-byte encoding. I.e. any character's codepoint is above 0xFFFF. Such strings are very rarely ever encountered or needed.

Character[Bearbeiten]

Individual characters (as extracted from a string or possibly read from a file) are represented as instances of the Character class. Queries are available to ask the character for its type (isLetter, isDigit etc.) or its unicode encoding (codePoint). There is virtually no limit and every character from the full Unicode set (e.g. even above 0xFFFF) can be represented by instances of the Character class.
[More Info] [Class Documentation]

Collection Types[Bearbeiten]

Smalltalk and therefore also expecco provides a very complete set of collection types which are tuned for different access patterns or memory consumption. An introductionary overview on the collection classes is found in the
[Collections Overview] of the [Smalltalk/X online documentation].

Array[Bearbeiten]

Ordered, fixed size, indexed by an integral index starting with 1 in both Smalltalk and JavaScript. Can store any type of object, and storage requirements are 1 machine word per element. As the size is fixed (when created), adding and removing of elements is not possible. Use one of the other collections (OrderedCollection, Set or Dictionary) if the number of elements needs to change later or is not known in advance. When accessing elements by index, access time is constant O(1). Searching for an element in an Array (using includes:, indexOf: or similar methods), time complexity is O(N), because the search is linear through all elements of the array.
[More Info] [Class Documentation]

ByteArray[Bearbeiten]

Ordered, fixed size, indexed by an integral index starting with 1. Can only store very small byte-valued unsigned integers (0..255), and allocates 1 byte per element. Access time and complexity are like with ordinary arrays, but the required memory is much less. ByteArrays are used to represent the contents of binary data, binary files, protocol elements (data packets) or low level memory dumps.
[More Info] [Class Documentation]

BitArray[Bearbeiten]

Ordered, fixed size, indexed by an integral index starting with 1. Can only store tiny bit-valued unsigned integers (0..1), but only needs one byte of storage for every 8 elements.
[Class Documentation]

BooleanArray[Bearbeiten]

Ordered, fixed size, indexed by an integral index starting with 1. Can only store booleans (false..true), but only needs 1 byte for every 8 stored boolean elements.
[Class Documentation]

FloatArray[Bearbeiten]

Ordered, fixed size, indexed by an integral index starting with 1. Can only store short IEEE floats (32bit) and allocates 4 bytes per element. Access times and complexity are like those of regular arrays, but they require are much less memory. FloatArrays are often used for data interchange with C-language functions or in data acquisition scenarios (series of measurement values).
[More Info] [Class Documentation]

DoubleArray[Bearbeiten]

Ordered, fixed size, indexed by an integral index starting with 1. Can only store IEEE doubles (64bit) and allocates 8 bytes per element. These are used in similar situations as FloatArrays.
[More Info] [Class Documentation]

HalfFloatArray[Bearbeiten]

Ordered, fixed size, indexed by an integral index starting with 1. Can only store IEEE half floats (16bit) and allocates 2 bytes per element.
Notice that half floats have a very low precision and are seldom used (exceptions are game programs or 3D graphics accelerators, to preserve memory by using this very compact format for depth information or texture attributes)

IntegerArray[Bearbeiten]

Ordered, fixed size, indexed by an integral index starting with 1. Can only store small 32bit unsigned integer values (0..0xFFFFFFFF) and requires 4 bytes of storage per element.
[Class Documentation]

SignedIntegerArray[Bearbeiten]

Ordered, fixed size, indexed by an integral index starting with 1. Can only store small 32bit signed integer values (-0x800000000..0x7FFFFFFF), requiring 4 bytes of storage per element.
[Class Documentation]

LongIntegerArray[Bearbeiten]

Ordered, fixed size, indexed by an integral index starting with 1. Can only store small 64bit unsigned integer values (0..0xFFFFFFFFFFFFFF), allocating 8 bytes per element.
[Class Documentation]

SignedLongIntegerArray[Bearbeiten]

Ordered, fixed size, indexed by an integral index starting with 1. Can only store small 64bit signed integer values (-0x80000000000000000..0x7FFFFFFFFFFFFFFF), allocating 8 bytes per element.
[Class Documentation]

OrderedCollection[Bearbeiten]

Ordered, variable size, indexed by an integral index starting with 1. Can store any type of object. OrderedCollections are specifically tuned for adding/removing elements at either end. Access time to elements by index has a time complexity of O(1). Insertion and remove at either end approaches O(1), but is O(n) for inner elements, because the elements are moved inside a linear container. When searching for an element in an OrderedCollection (using includes:, indexOf: or similar methods), time complexity is O(n), because a linear search is done.
[More Info] [Class Documentation]

For very big or lightly sparse collections, you can use a SegmentedOrderedCollection. This uses a hierarchy of buckets and does not need storage for big unused regions inside the collection. However, due to the additional computations, the break even in performance compared to regular OrderedCollections is usually at around 100000 to 1000000 elements.

For very very big collections (millions of elements) which are very lightly filled, use a SparseArray. This only keeps the filled slots, and is great, if you have only a few valid elements inside a virtually huge index space.

SortedCollection[Bearbeiten]

Sorts itself, variable size, indexed by an integral index starting with 1. Can store any type of object for which a order relationship is either implicit (i.e. the elements responds to the "<" (less) message) or for which an explicit comparison block can is given (i.e. you provide a sort-block, which takes two elements and returns a boolean depending on the ordering).

By default, sorting is by ascending order based on the "<"-message. However, the sort order can be arbitrarily changed by setting the sortBlock, a two-argument JS-function or a two-argument Smalltalk block which should return true, if its first argument is to be "considered" smaller than the order (i.e. the default sort block is "[:a :b | a < b]").

Access by numeric index is O(1). Insertion and remove is O(1) for the first and last element, O(n) for inner elements, because the elements are moved inside a linear container.
When searching for an element in a SortedCollection (using includes:, indexOf: or similar methods); time complexity is O(log n), because a binary search can be done. When a big sorted collection is created, it is faster to first create it as a non-sorted collection (OrderedCollection) and then converting the whole collection via asSortedCollection(), instead of adding individual elements one-by-one.
[More Info] [Class Documentation]

Set[Bearbeiten]

Unordered, variable size, keeps a single reference only, per equal element. Can store any type of object. When searching for an element in a Set (using the includes: method), time complexity approaches O(1), because the search can be done based on a hashing algorithm in asymptotic constant time.
[More Info] [Class Documentation]

Bag[Bearbeiten]

Unordered, variable size, keeps a count, per equal element. Adding, removing and insertion check are all asymptotical O(1). Can store any type of object.
[Class Documentation]

Dictionary[Bearbeiten]

Unordered, variable size, implements arbitrary key-value mappings. Can store any type of object and use any kind of object as key. When accessing or searching an element in a Dictionary (using at:, includesKey: and similar methods), time complexity asymptotically approaches O(1), because the key search can be done based on a hashing algorithm. Notice, that the same value may be stored multiple times in a dictionary (i.e. stored under multiple keys). Asking for the presence of a particular value in the dictionary (i.e. the reverse query without key) still has O(N) time complexity.

If you need fast access both via the key and the value, either use an additional reverse mapping dictionary, or store an additional value->key mapping in the same dictionary (if the key and value spaces are disjoint).

Java users will know a subset of the dictionary functionality as "Hashtable".
[More Info] [Class Documentation]

OrderedDictionary[Bearbeiten]

Ordered, variable size, implements arbitrary key-value mappings. Can store any type of object and use any kind of object as key. Being similar to dictionaries, these remember the order by which elements were added. It provides both access by hash-key and access by index (which is the order in which elements have been added). Creation of an OrderedDictionary is somewhat slower than for Dictionary or OrderedCollection, but access by either numeric or hashkey is fast O(1).
[Class Documentation]

OrderedSet[Bearbeiten]

This is very similar to a Set, but remembers the order in which elements were added, and provides index read access methods. When enumerated, the elements are generated in this order.

Queue[Bearbeiten]

Ordered, variable size. Can store any type of object. Queues are especially tuned for adding elements at one end, and removing them at the other. The internal representation uses a buffer in round-robin fashion. Both adding and removal are done on O(1) time.
[More Info] [Class Documentation]

If multiple processes are accessing the same queue concurrently, use a SharedQueue, which is implicitly synchronized.
[Class Documentation]

BTree, AATree, BinaryTree[Bearbeiten]

Combining the functionality of Dictionaries and SortedCollection, these keep their elements in a sorted order, indexed by a key, both of which can be arbitrary objects. In contrast to Dictionaries, which use hashing, these use a tree-like representation and guarantee both asymptotic O(log N) worst case and average runtime. (Dictionaries have a much better average runtime of O(C), but also a worst case of O(N), if the hash keys are badly distributed). These trees are self balancing to avoid the linear worst case access time of non balanced trees. [Class Documentation]

There is also a non-balancing BinaryTree, which is slightly faster (by not balancing), but its worst case access times may degenerate to O(N), if elements are added non-randomly. [Class Documentation]

Stream Types[Bearbeiten]

Streams are accessors for either internal collections or external files, sockets and other character oriented I/O devices. They are created by either "opening" a file or device or by creation "on" a numerical indexable collection object (String, Array, OrderedCollection etc.). Once created, read streams deliver the next element via a "next()" operation, write streams append elements via the "nextPut(e)" operation. In addition to the above elementary functions, bulk read/write and linewise read/write and various other functionality is provided.

An introductionary overview on the stream classes is found in the [Streams Overview] of the [Smalltalk/X online documentation].

ExternalStream[Bearbeiten]

A common superclass providing bytewise or linewise access to any stream of the underlying operating system. Concrete subclasses are Socket, Filestream, Console Streams, PTYStream etc.
[More Info] [Class Documentation]

FileStream[Bearbeiten]

Provide bytewise or linewise access to the underlying file system.
[More Info] [Class Documentation]

CharacterWriteStream[Bearbeiten]

A special stream usable for mixed single- and multibyte characters (i.e. Unicode).
[Class Documentation]

Socket[Bearbeiten]

Provides low level bytewise or linewise access to a communication socket. Typically, but not limited to TCP/IP connections.
[More Info] [Class Documentation]

There are a number of useful companion classes which deal with host addresses:
[SocketAddress], [IPSocketAddress] and [IPv6SocketAddress].

PipeStream[Bearbeiten]

These are used to read/write from/to another program's standard input or standard output. For details, consult a Unix programmer's manual.

Other useful Types[Bearbeiten]

Date[Bearbeiten]

Represents a calendar date. Instances can be created with "Date today", by giving day/month/year or a calendar day. Instances offer a large number of queries, such as weekday, dayInWeek, leapYear etc.
[More Info] [Class Documentation]

Time[Bearbeiten]

Represents a time-of-day in second resolution.
[More Info] [Class Documentation]

Timestamp (Date and Time)[Bearbeiten]

Represents a timestamp in millisecond resolution. The Timestamp class has been enhanced to support dates before 1.1.1970 and after 2036, which are typical restrictions of systems which represent times as "seconds since the epoch".
[More Info] [Class Documentation]

TimeDuration[Bearbeiten]

Represents an amount of time, to represent time intervals in millisecond resolution. When reading, a unit-specifier character is allowed to specify milliseconds (ms), seconds (s), minutes (m), hours (h) or days (d). For example, "1h 20s" specifies 1 hour and 20 seconds.
[More Info] [Class Documentation]

Delay[Bearbeiten]

Utility providing delay functions. Most useful are (in Smalltalk) "Delay waitForSeconds: n" and "Delay waitUntil: timestamp", or (in JavaScript) "Delay.waitForSeconds(n)" and "Delay.waitUntil(timestamp)". [Class Documentation]

Filename[Bearbeiten]

Represents names of files in the filesystem. Provides many useful query functions for file size, access- and modification time, parent directory, children, path queries etc.
[Class Documentation]

CTypes[Bearbeiten]

These types describe C-data structures. They are needed when data is exchanged with DLL functions or is to be read/written in binary from/to a file.

CTypes are usually parsed from a string using the C Parser, which is invoked by expecco when a type definition starts with a comment like "/* C: */". But of course, the C-Parser can also be invoked explicitly via a workspace or programmatically via an elementary block. The following example would create a C-type from a structure definition string (in JavaScript code):

    t = CParser.parseDeclaration("
        struct s {
            unsigned long DPR_Address;
            unsigned long DPR_Length;
            unsigned long PortAddress;
            struct p {
                unsigned char ProtocolID;
                union u {
                    uint16 dpInOutAreaSize;
                    uint16 dpInAreaSize;
                } Parameter;
            } Protocol[4];
        };
    ");

the type in variable "t" can then be instantiated by sending it a "new" message:

    datum = t.new();

or with:

    datum = t.malloc();

The difference is that "new" allocates the bytes in the regular garbage collected expecco memory, whereas "malloc" allocates in pinned (not moving) memory on the C heap. Thus references to such malloc'd memory can be passed to DLL code which wants to keep a reference to it, whereas c-data allocate with new can only be used temporarily by C code and will vanish when the call returns.

You can inspect the created instance via the inspector (evaluate: "datum.inspect()").

Individual fields of the c-datum are accessed via the following functions:

  • datum.memberAt(fieldName)
    retrieves a field. If the field is a composite datum (array or structure), a new datum is extracted (i.e. a structure copy is made).
  • datum.at(index0Based)
    retrieves an indexed array element as a copy.
  • datum.memberAtPut(fieldName, value)
    stores a field. If the field is a composite datum (array or structure), the whole value is copied.
  • datum.atPut(index0Based)
    stores an indexed array element.
  • datum.refMemberAt(fieldName)
    creates a pointer to a field. This allows for subfields to be modified.
  • datum.refAt(index0Based)
    creates a pointer to an array element.

notice that in JavaScript, indexed access can be written more convenient as "datum[index0Based]" and that member field access can be written as "datum.fieldName".

For the full protocol of CDatum, please refer to the class code in the Class Documentation.
Additional information is also found in the Datatype Element/en#CTypes documentation.

Additional Standard Objects for JavaScript[Bearbeiten]

In order for a "well known" environment to be provided to those who know JS, but do not know Smalltalk, mimicri classes (Math) and mimicri protocol has been added to the underlying Smalltalk system. These functions simply call corresponding Smalltalk functions and only exist to provide a JavaScript compatible API which is described below. Notice that this represents only a small fraction (less than 1/100th) of the real functionality of the base system. In order to find out more about all existing classes, you should open a class browser and navigate through the system yourself. There is also a more detailed introduction to the basic classes found in the Smalltalk/X Base Classes Documentation.

Math[Bearbeiten]

Constants[Bearbeiten]
  • E
    Euler's constant, the base of the natural logarithm (approximately 2.718).
    Same as "Float e" in Smalltalk.
  • LN10
    Natural logarithm of 10 (approximately 2.302). In Smalltalk, you can use "Math LN10".
  • LN2
    Natural logarithm of 2 (approximately 0.693).
    Same as "Float ln2" in Smalltalk.
  • LOG10E
    Base 10 logarithm of E (approximately 0.434)
  • LOG2E
    Base 2 logarithm of E (approximately 1.442)
  • PI
    Pi (approximately 3.14159).
    Same as "Float pi" in Smalltalk.
  • SQRT1_2
    Square root of 1/2 (approximately 0.707)
  • SQRT2
    Square root of 2 (approximately 1.414).
    Same as "Float sqrt2" in Smalltalk.
Min & Max[Bearbeiten]
  • max (number1 , number2,...)
    Returns the largest of 0 to 5 arguments
  • min (number1, number2,...)
    Returns the smallest of 0 to 5 arguments
Miscellaneous[Bearbeiten]
  • abs (aNumber)
    Returns the absolute value of aNumber
  • binco (n, k)
    Returns the binomial coefficient C(n,k) (n over k, choose k from n)
  • ceil (aNumber)
    Returns the smallest integer greater than or equal to aNumber
  • exp (aNumber)
    Returns EaNumber, where aNumber is the argument, and E is Euler's constant, the base of the natural logarithm
  • fac (aNumber)
    Returns the factorial of aNumber
  • floor (aNumber)
    Returns the greatest integer less than or equal to aNumber
  • gcd (a, b)
    Returns the greatest common divisor of a and b
  • log10 (aNumber)
    Returns the log base 10 of aNumber
  • log (aNumber)
    Returns the log base E of aNumber
  • pow (base, exp)
    Returns base to the exponent power, that is, baseexp
  • random
    Returns a pseudo random number between 0 and 1
  • round (aNumber)
    Returns the value of aNumber rounded to the nearest integer
  • sqrt (aNumber)
    Returns the square root of aNumber
Trigonometric[Bearbeiten]
  • acos (aNumber)
    Returns the arccosine (in radians) of aNumber
  • asin (aNumber)
    Returns the arcsine (in radians) of aNumber
  • atan2 (x, y)
    Returns the arctangent of the quotient of its arguments (in radians)
  • atan (aNumber)
    Returns the arctangent (in radians) of aNumber
  • cos (aNumber)
    Returns the cosine of aNumber (given in radians)
  • sin (aNumber)
    Returns the sine of aNumber (given in radians)
  • tan (aNumber)
    Returns the tangent of aNumber (given in radians)

Number[Bearbeiten]

Properties[Bearbeiten]
  • MAX_VALUE
    The largest representable number. The returned number is 0x7FFFFFFFFFFFFFFF, to mimicri a 64 limit on numbers for JS programs ported from other systems. In fact, expecco does not impose any such limit in integer values.
  • MIN_VALUE
    The smallest representable number. The returned number is -0x8000000000000000, to mimicri a 64 limit on numbers for JS programs ported from other systems. In fact, expecco does not impose any such limit in integer values.
  • NEGATIVE_INFINITY
    Returns the special 'negative infinity' value
  • NaN
    Returns the special 'not a number' value
  • POSITIVE_INFINITY
    Returns the special 'positive infinity' value
Methods[Bearbeiten]
  • asFloat ()
    Returns a floating-point version of the receiver object. For example: "1.asFloat()" yields the floating point number: "1.0".
  • asInteger ()
    Returns an integer version of the receiver object (truncating). For example: "1.0.asInteger()" yields the integer number: "1". Alternatives are ceiling(), floor() and rounded().
  • ceiling ()
    For non-integral values, ceiling returns the smallest integer which is larger than the receiver. For integers, the original value is returned. For example: "1.4.ceiling()" yields the integer number: "2", whereas "1.ceiling()" returns "1". See also: floor() and rounded().
  • floor ()
    For non-integral values, floor returns the largest integer which is smaller than the receiver. For integers, the original value is returned. For example: "1.4.floor()" yields the integer number: "1", whereas "1.floor()" returns "1". See also: ceiling() and rounded().
  • rounded ()
    For non-integral values, rounded returns the nearest integer from rounding. For integers, the original value is returned. For example: "1.4.rounded()" yields the integer number: "1", "1.6.rounded()" yields the integer number: "2" and whereas "1.rounded()" returns "1". See also: ceiling() and floor().
  • roundTo (r)
    Rounds towards the nearest multiple of r. For example, "1.543.roundedTo(0.01)" returns "1.54" and "1.567.roundedTo(0.01)" returns "1.57". See also: ceiling(), floor() and rounded().
  • toExponential (nDigits)
    Returns a string representing the number in exponential notation, where nDigits is the number of digits to appear after the decimal point
  • toExponential (nDigits, nDigitsAfter)
    Returns a string representing the number in exponential notation
  • toFixed (nDigits)
    Returns a string representing the number in fixed notation, where nDigits is the number of digits to appear after the decimal point

Random[Bearbeiten]

A built in random generator, which generates linear congruential pseudo random numbers itself. On Unix/Linux operating systems, an additional class called "RandomGenerator" uses the operating system's random generator (/dev/rand). In addition, alternative generators are available as "RandomParkMiller" and "RandomTT800". For most applications, the default generator named Random should provide a reasonable random sequence.

  • next ()
    Returns a random number between 0 and 1 (float)
  • nextBoolean ()
    Randomly returns either true or false
  • nextBetween_and_ (min, max)
    Returns a random float between min and max
  • nextIntegerBetween_and_ (min, max)
    Returns a random integer between min and max (use 1 and 6, to simulate a dice)

Time / Date[Bearbeiten]

Time and Date instances provide a rich protocol for all kinds of queries. The list below is only a short extract of the most used methods:

  • getDate
    Returns the day of the month (1..31)
  • getDay
    Returns the day of the week (0..6); Sunday is 0
  • getFullYear
    Returns the year
  • getHours
    Returns the hours (0..24)
  • getMinutes
    Returns the minutes (0..60)
  • getMonth
    Returns the day of the month (1..12)

String[Bearbeiten]

Be reminded that the String class inherits from CharacterArray, ByteArray, SequentialCollection, Collection and Object. Thus all of those methods are also available (and useful) with strings. Use the browser or take a look at the online documentation for the full protocol.

  • charAt0 (index)
    Returns the n'th character as a Character instance object, using a 0-based indexing scheme
  • charAt1 (index)
    Returns the n'th character as a Character instance object, using a 1-based indexing scheme
  • charCodeAt0 (index)
    Returns the code of the n'th character, using a 0-based indexing scheme
  • charCodeAt1 (index)
    Returns the code of the n'th character, using a 1-based indexing scheme
  • indexOf0 (aCharacter)
    Returns the index of aCharacter, using a 0-based indexing scheme; -1 if not found
  • indexOf1 (aCharacter)
    Returns the index of aCharacter, using a 1-based indexing scheme; 0 if not found
  • lastIndexOf0 (aCharacter)
    Returns the last index of aCharacter, using a 0-based indexing scheme; -1 if not found
  • lastIndexOf1 (aCharacter)
    Returns the last index of aCharacter, using a 1-based indexing scheme; 0 if not found
  • quote()
    Wraps the receiver into double quotes (")
  • split (separator)
    Splits the string into a collection of substrings using the separator
  • substr0 (index, count)
    Extracts a substring starting at the index with the given length, using a 0-based indexing scheme
  • substr1 (index, count)
    Extracts a substring starting at the index with the given length, using a 1-based indexing scheme
  • substring0 (index1)
    Extracts a substring starting at the index, using a 0-based indexing scheme
  • substring0 (index1, index2)
    Extracts a substring between the given indices, using a 0-based indexing scheme
  • substring1 (index1)
    Extracts a substring starting at the index, using a 1-based indexing scheme
  • substring1 (index1, index2)
    Extracts a substring between the given indices, using a 1-based indexing scheme
  • toLowerCase
    Returns a copy of the receiver with all chars in lower case
  • toUpperCase
    Returns a copy of the receiver with all chars in upper case
  • trim
    Returns a copy of the receiver with all leading and trailing white space removed
  • trimLeft
    Returns a copy of the receiver with all leading white space removed
  • trimRight
    Returns a copy of the receiver with all trailing white space removed

 

Collection (Array)[Bearbeiten]

  • concat (aCollection)
    Returns a new collection consisting of the concatenation of the receiver and the argument
  • every (filterFunction)
    Returns true, if "filterFunction" returns true for all elements
  • filter (filterFunction)
    Selects elements for which "filterFunction" returns true
  • forEach (function)
    Applies "function" for each element
  • join (seperator)
    Joins the strings of the receiver into a single string using the separator
  • map (function)
    Returns a new collection collecting the results of applying "function" to each element in sequence
  • pop
    Removes and returns the last element of the collection
  • push (value)
    Adds an element to the end of the collection
  • reduce0 (filterFunction)
    Applies "function" against two values, reducing from left to right. Function must be declared as: f(previousValue, currentValue, index, arr). Pass 0-based indices to the filter.
  • reduce0 (filterFunction, initialValue)
    Applies "function" against two values, reducing from left to right. Function must be declared as: f(previousValue, currentValue, index, arr). Pass 0-based indices to the filter.
  • reduce1 (filterFunction)
    Applies "function" against two values, reducing from left to right. Function must be declared as: f(previousValue, currentValue, index, arr). Pass 1-based indices to the filter.
  • reduce1 (filterFunction, initialValue)
    Applies "function" against two values, reducing from left to right. Function must be declared as: f(previousValue, currentValue, index, arr). Pass 1-based indices to the filter.
  • shift
    Removes and returns the first element of the collection
  • slice0 (index1, index2)
    Extracts a subcollection, using a 0-based indexing scheme
  • slice1 (index1, index2)
    Extracts a subcollection, using a 1-based indexing scheme
  • some (filterFunction)
    Returns true, if "filterFunction" returns true for any element
  • unshift (arg)
    Adds an element to the beginning of the collection

 

Transcript[Bearbeiten]

The global variable "Transcript" refers to either the Transcript information window (if it is open) or the standard error stream. As such, it implements most of the stream interface functions. Most noteworthy are:

  • cr ()
    Adds a linebreak (i.e. followup text will be shown on the next line)
  • show (arg)
    Adds a textual representation of the argument, which can be a string, number or any other object.
  • showCR (arg)
    A combination of show(), followed by a linebreak.

and from the inherited write stream interface:

  • nextPut (aChar)
    Sends a single character
  • nextPutAll (aString)
    Sends a string
  • nextPutLine (aString)
    Sends a string followed by a linebreak

 

Expecco Objects[Bearbeiten]

Many objects internal to expecco's execution machinery are reachable via elementary code (i.e. from within an elementary-coded action's code). A lot of useful and required information can be acquired by consulting these objects. The anchor to all those objects is the "current activity" object.

Current Activity[Bearbeiten]

Within elementary code the activity instance which is executing this piece of code can be accessed via the variable "this" (Smalltalk: "self"). For every executed action, a new activity object is created. It is usually alive during the execution only (i.e. it is destroyed and its memory reused automatically, after the block's action has finished).

The current activity object supports the following functions:

Reporting[Bearbeiten]
  • error ()
    Report a defect (in the test). Stops execution.
  • error (infoString)
    Report a defect (in the test). Stops execution.
  • fail ()
    Report a failure (in the SUT). Stops execution.
  • fail (infoString)
    Report a failure (in the SUT). Stops execution.
  • inconclusive ()
    Report an inconclusive test. Stops execution.
  • inconclusive (infoString)
    Report an inconclusive test. Stops execution.
  • success () OBSOLETE - see activitySuccess below
    Finishes the current activity with success.
  • success (infoString) OBSOLETE - see activitySuccess below
    Finishes the current activity with success.
  • activitySuccess ()
    Finishes the current activity with success. Actually not needed, because simply returning from an action's execute method is semantically equivalent.
  • activitySuccess (infoString)
    Finishes the current activity with success.
  • pass ()
    Finishes the current testCase with success. Notice: this aborts the current activity and all of the callers up to the test case level.
  • pass (infoString)
    Finishes the current testCase with success.

 

Logging[Bearbeiten]
  • logData (data)
    Adds data to the activity log.
  • logDataFile (fileName)
    Adds a file attachment to the activity log.
  • logError (messageString)
    Adds a error message to the activity log.
  • logError (messageString, detail)
    Adds a error message to the activity log.
  • logWarning (messageString)
    Adds a warning to the activity log.
  • logWarning (messageString, detail)
    Adds a warning to the activity log.
  • logInfo (messageString)
    Adds an info message to the activity log.
  • logInfo (messageString, detail)
    Adds an info message to the activity log.
  • logImageInfo (messageString, image)
    Adds an info message with an image (screendump) to the activity log. There is an option in the report generator to include those in the generated pdf-report.
  • alert (messageString) (1.8.1)
    Adds an warning message to the activity log, and also shows a DialogBox, which has to be confirmed by the operator. The dialog box and confirmation can be disabled by a settings flag in the "Execution - Log -Settings" dialog (by default, it is disabled).
  • warn (messageString) (1.8.1)
    Same as alert() (for Smalltalk compatibility).

Notice the similarity and difference between "error()" and "logError()": both actually create a log-data entry, but "error()" stops the execution, whereas "logError()" proceeds (maybe, the naming is a bit confusing here...)

 

Reflection, Information, Queries and Accessing[Bearbeiten]
  • activityCreator ()
    The triggering activity (i.e. the caller). Except for special tricks, this is only useful to generate nice print messages (such as "activity foo, called from bar".
  • activityProcess ()
    The thread executing the activity
  • blockDescription ()
    The definition of the activity (i.e. the tree definition item of the action's step)
  • environment ()
    The currently valid environment variables of the compound step which contains this action, or (if none) of the test plan in which this action executes (see "Environment Access" below)
  • environmentAt (anEnvironmentVarName)
    The value of an environment variable (in the above environment)
  • environmentAt_put (anEnvironmentVarName, value)
    Changing the value of an environment variable (in the above environment)
  • inputValueForPin (pinName)
    The value of a certain pin
  • inventory ()
    The inventory of the activity
  • nameOfStep ()
    The name of the corresponding step of the activity
  • resources ()
    All resources which have been acquired for the activity (as specified in the resource-definition of the action). Retrieves a collection of Resource objects, as described below.
  • resourcesForId (skillId)
    Resources which have been acquired for one particular skill-requirement. The argument skillId corresponds to the name as specified in the first column of the resource definition of the action). Notice, that a collection of Resource objects is returned - even if only one resource has been acquired.
  • step ()
    The corresponding step of the activity

 

Environment Access[Bearbeiten]

Starting at every compound activity, a nested stack of outer environments is present, ending in a top project environment. The following activity methods provide access to some of them. This can be used to automatically setup environment variables from a configuration or when autodetecting a system-under-test's configuration and leaving the values in a defined outer environment. Depending on the intended lifetime of those values, one of the following environments should be chosen as target of such operations:

  • environment ()
    the environment of the compound in which the current elementary action is executing (as a step)
  • executorEnvironment ()
    an environment which is only present during a single testplan execution
  • projectEnvironment ()
    an environment which is attached to the current test suite (but not persistent when the suite is saved/loaded from an ets file)
  • browserEnvironment ()
    an environment which is present while the expecco application is running (i.e. the current session). (it is not made persistent)

None of the following can be made persistent directly. However, every test suite, test plan and compound block provide a description of initial values and how to compute them in their environment description, which is edited using the Environment Editor . Also, such descriptions can be saved and loaded separately from the test suite via the "Load/Save Parameter Set" menu functions of the "File" menu.

 

Executor Functions[Bearbeiten]

  • executorsWorkingDirectory ()
    Temporary working directory. This is removed after the execution (i.e. useful only for very temporary files, which are generated in an action and read in another)
  • testplan ()
    The currently executed testplan (nil if a block test blocks are executed)
  • project ()
    Project (testsuite or library) in which the test is executed

 

Resource Functions[Bearbeiten]

  • name ()
    The name of the resource
  • skillNamed (nameOrUUID)
    Fetch a concrete skill-value of that resource. The argument can be either a skills name, or the functional-UUID of the skill element (in the project tree).
  • skillAttributeNamed (name)
    Fetch a concrete skill-attribute-value of the resource

 

Test Suite (Project) Functions[Bearbeiten]

  • documentation ()
    The documentation or nil
  • functionalId ()
    A unique ID which defines this testSuite independent of its name (remains constant after change)
  • projectsWorkingDirectory ()
    Project directory - i.a. all attachments. This directory is created when a project file is loaded and is deleted when expecco is closed. Do not use it for permanently needed files.
  • modelLanguageStringFor (aString)
    translates a string according to the model-language translation table
  • versionId ()
    A unique ID which defines this testSuite in exactly this version. (changed with any edit operation)
  • elementWithName (aString)
    Retrieve an element by name
  • elementWithId (aUUID)
    Retrieve an element by its id
  • elementWithFunctionalId (aUUID)
    Retrieve an element by its version id
  • typeNamed (aTypeName)
    Retrieve a datatype. To instantiate a type, write "(self project typeNamed:'Foo') new".

 

Testplan Functions[Bearbeiten]

  • documentation ()
    The documentation or nil
  • name ()
    The name of the Testplan
  • tags ()
    A collection of tags of the testplan
  • functionalId ()
    A unique ID which defines this testplan independent of its name (remains constant after change)
  • versionId ()
    A unique ID which defines this testplan in exactly this version. (changed with any edit operation)

 

Step Functions[Bearbeiten]

  • name ()
    The name of the Step
  • tags ()
    A collection of tags of the Step

 

Pin Functions[Bearbeiten]

The input and output pins can be referenced in elementary code by their name. The name refers to the pin - NOT the passed value (this allows for the code to check, if a value is present). For indexed pins (i.e. in steps with a variable number of pins), which is a new feature in v2.1, the collection of pins is referred to by the name, and pins are accessed via an index (1..). See more about how to handle variable pins in the section below.

Common to all Pins:[Bearbeiten]
  • datatype ()
    Returns the data type of the pin (Reflection-API)
Input Pins:[Bearbeiten]
  • hasValue ()
    Returns true if the pin has received a value (useful to determine if there is a value if the trigger condition is "AndConnected" or "Or")
  • hasEnvironmentFreezeValue ()
    Returns true if the pin has a freezeValue from an environment
  • hasFreezeValue ()
    Returns true if the pin has a freezeValue
  • isConnected ()
    Returns true if the pin is connected (but not frozen)
  • value ()
    Returns the value of the pin. This is either the value received via a connection, or a constant freeze value, or an environment variable freeze value. Raises an error if the pin did not receive any value.
  • valueIfAbsent (alternativeValue)
    Returns the value of a pin or the alternativeValue, if the pin did not receive any value.
  • valueIfPresent ()
    Returns the value of a pin or nil if the pin did not receive any value. Similar to "value()", but avoids the exception.
  • waitForValue ()
    The pin has to be a mailbox pin. The execution of this action will be suspended until the pin receives a value.
  • waitForValueWithTimeout (seconds)
    Like above, but the execution will only wait for the specified time limit, given in seconds. The parameter is typically an integer, but can also be a fraction (e.g. 1/3) or a floating-point number (e.g. 0.3).
Output Pins:[Bearbeiten]
  • isBuffered ()
    True if the pin is buffered
  • isConnected ()
    Returns true if the pin is connected
  • value (data )
    Writes the value. If the output pin is connected to another step's input pin, the other step's action may be triggered (if the other step's trigger condition is fulfilled).
Variable Input Pins[Bearbeiten]

Starting with expecco v2.0, steps can have a variable number of input pins, if the pin's schema has the "variable pin" attribute set. To change the number of pins of a step in the diagram editor, pull the pin-handle of a placed step, to add or remove pins. In elementary code, the following API call entries (to the variable pin) are provided to deal with this situation:

  • pinName
    now refers to a collection of pins, representing the set of pins actually present at the step. In contrast regular pins, where pinName refers to a single pin.
  • pinName.numberOfVariablePins ()
    returns the number of actual pins present in the step for the (variable) schema pin named "pinName"
  • pinName.size ()
    same as the above "numberOfVariablePins"
  • pinName.at (idx)
    returns the i'th actual pin of the schema pin named "pinName". Notice this retrieves the pin for reflection queries, NOT the pin value. To get value or query if it has a value, call valueAt:() / hasValueAt().
  • pinName.hasValueAt (idx)
    true if the i'th variable pin is connected and has received a value
  • pinName.values ()
    Retrieves the values passed to the pins as an array. The array contains nil entries for disconnected pins and those without a value
  • pinName.valuesPresent ()
    Retrieves the values as present; skips unconnected pins or those without a value.
  • pinName.valueAt (idx)
    to get the i'th pin's value; raises an error if no value was given or pin is not connected
  • pinName.valueIfPresentAt (idx)
    to get the i'th pin's value, or nil if no value was given or pin is not connected
Variable Output Pins[Bearbeiten]

Starting with expecco v2.1, steps can also have a variable number of output pins, if the schema has the "variable output pin" attribute set. To change the number of pins, pull the pin-handle of a placed step, to add or remove pins. In elementary code, the following API call entries (to the variable pin) are provided to deal with this situation:

  • pinName.numberOfVariablePins ()
    returns the number of actual pins present in the step for the (variable) schema pin named "pinName".
  • pinName.valueAt_put (idx, value)
    write a value to the output pin
  • pinName.at (idx)
    returns the i'th actual pin of the schema pin named "pinName". Notice that this retrieves the pin; to write an output value to it, call value(v) on the returned pin.
  • pinName.outputPins ()
    returns a collection of all actual pins present in the step for the (variable) schema pin named "pinName".

 

Datatype Functions[Bearbeiten]

A datatype can be retrieved by asking a pin for its datatype using the datatype () function. This is sometimes useful in elementary code, in order to instantiate a new value (given the pin) or to ask for details of the type.

  • name ()
    The name of the Datatype
  • isAnyType ()
    true if the datatype is the builtin "Any" type
  • isArrayType ()
    true if the datatype is an "Array" type (with any element type)
  • isCompoundType ()
    true if the datatype is a compound (structure) type
  • isEnumType ()
    true if the datatype is an enumeration type
  • isPrimaryType ()
    true if the datatype is any builtin primary type
  • isRangeType ()
    true if the datatype is a subrange of another type
  • isTupleType ()
    true if the datatype is a tuple type
  • isUnionType ()
    true if the datatype is a union type
  • isStringPrimaryType ()
    true if the datatype is the builtin "String" primary type

Enum Type Functions[Bearbeiten]

  • values ()
    returns an array of enum-value names (Symbolic)
  • integerValueOf (elementName)
    returns the associated integer value. (new in expecco 2.8)

Compound Type Functions[Bearbeiten]

  • namedFieldDescriptions ()
    returns an array of descriptions of the named fields (if any)
  • indexedFieldDescriptins ()
    returns a description of indexed fields (if any)

Each field description can be asked for the "fieldName()", "fieldType()" and "defaultValue()".

Primary Type Functions[Bearbeiten]

  • typeClass ()
    returns the underlying Smalltalk class, of which values are instances of.

Range Type Functions[Bearbeiten]

  • baseType ()
    returns the underlying base type. Typically this will be the Integer primary type.
  • minValue ()
    returns the minimum value of the range
  • maxValue ()
    returns the maximum value of the range

 

Functions for Any Tree- or Diagram Element[Bearbeiten]

These include: BlockDescriptions, Types, TestPlans, TestCases, Steps, Connections, Pins, Annotations etc.

  • versionId
    A globally unique identifier (GUID or UUID) which is changed with every modification of the item
  • functionalId
    A globally unique identifier (GUID or UUID) which is assigned once-and-for-all to the item
  • id
    alias for versionId().
  • tags
    A collection of tags.
  • isAnnotation
    true, if it is an annotation (in a diagram)
  • isStep
    true, if it is a step (in a diagram)
  • isConnection
    true, if it is a connection (in a diagram)
  • isBlockDescription
    true, if it is a block description (in the tree)
  • isAttachment
    true, if it is any attachment (in the tree)
  • isFileAttachment
    true, if it is a file attachment (in the tree)
  • taggedValueAt (aKey)
    To retrieve a tagged value. These are usually preserved when objects are transported to/from other systems.
  • taggedValueAt_put (aKey, anObject)
    to change a tagged value. Only a limited set of datatypes are supported: anObject must be a String or a Number.
  • propertyAt (aKey)
    to retrieve an expecco property. These are expecco-internal properties. These are usually not recognized by other systems.
  • propertyAt_put (aKey, anObject)
    to set an expecco property. These are usually not recognized by other systems. Only a limited set of datatypes are supported: anObject must be a String or a Number.

 

Groovy Elementary Blocks[Bearbeiten]

Groovy is an open source interpreter and compiler for a Java like scripting language which runs on top of the Java Virtual machine. For details, visit "Groovy: A dynamic language for the Java platform" and especially the documentation portal..

Code written as a Groovy elementary block is not executed directly by expecco. Instead, the code is forwarded to a Groovy shell which runs inside a Java Virtual Machine (JVM). This may be a local JVM, whose sole purpose is to provide additional utility functions or which provides an interface to the actual system under test (SUT), or it may be the JVM which executes the tested application and is running physically on the SUT. By using Groovy blocks, expecco's basic black-box test functionality can be easily extended by many powerful gray- and white-box tests. You can access the internals of objects inside the SUT, call its functions and even define new classes and functions and call them. Groovy is especially useful, if you have to install callback- or hooks inside the SUT, which interact back to expecco (e.g. to collect SUT-internal events and allow expecco to read those from the SUT).

The machinery to interact with a JVM (and therefore to execute Groovy code blocks) requires the Java Bridge plugin, which is not part of the basic expecco package. Groovy itself is available for free as a jar class library, which is loaded with or into the target application. The Java Bridge plugin already includes a prepackaged Groovy interpreter which can be loaded transparently into the SUT application whenever a Groovy block is executed. So except for the setting of the JVM path, no further setup or installation is required.

Why use Groovy Elementary Blocks?[Bearbeiten]

The Java Bridge and especially Groovy code is very useful when testing GUI frameworks, communication protocols or machine control soft- and hardware which is written in Java. Especially, if you have to call functions internal to the SUT in order to trigger actions or if you need to be informed about state changes via listeners or callbacks, Groovy is the perfect tool for the task. Note that calls into the JVM are relatively straight forward, using the method forwarding mechanism, even from Smalltalk code. However, callbacks and listeners, which usually have to be implemented as instances of special derived classes of a an abstract framework class, are hard to realize without Groovy.

Without Groovy, you would have to add such classes to the software inside the SUT - e.g. by adding them to the tested software's jar packages, or by adding extra jar packages. In addition, you'd have to provide a communication mechanism (socket, pipe or file interchange), by which your test action is able to retrieve this information.

What is the Difference between a JavaScript Block and a Groovy Block?[Bearbeiten]

In one sentence: JavaScript actions are executed inside expecco, Groovy actions inside the SUT (System Under Test).

More detailed:

JavaScript actions are actually alternative syntactic forms of Smalltalk actions. When executed, they run inside the expecco executable and have access to any class or object inside expecco itself, or those which are passed in via input pins or environment variables.

On the other hand, Groovy action code is "injected" into a Java VM and executes in that object space. It cannot directly access expecco classes or objects, but instead can access all public classes and objects within the system under test directly.

Groovy Compilation Mechanism and Performance Issues[Bearbeiten]

When a groovy action is executed for the very first time, the block's code is transmitted via the bridge to the Groovy shell on the target JVM as a string. There, the code is parsed and an anonymous jar is created, containing Java byte code. Thus, there is some initial overhead involved in both sending the code and more so in compiling the code in the JVM. However, for every followup call, the JVM will directly call the generated code and run at full Java speed (i.e. the execution speed is almost as if native Java code was executed).

Using a Single Local JVM Connection / Executing Groovy Actions on the Local Machine[Bearbeiten]

By default, a Groovy action is executed on a JVM which runs on the local machine; i.e. the machine on which expecco itself is running. With the execution of the very first groovy action, a JVM is started as defined in the expecco settings dialog, then a bridge connection is established to that JVM, and the Groovy interpreter is loaded and instantiated on the JVM. Then a reference to that GroovyShell object is remembered and used for all followup Groovy actions which are to execute on that local JVM. This default behavior is convenient if you want to use Java support libraries for reporting, statistics or other computations, or if your SUT is running inside a local JVM, or if you want to call interface functions (protocols) which actually talk to your SUT.

Using a Particular JVM Connection / Executing Groovy on a Possibly Remote Machine[Bearbeiten]

If you either need to customize the JVM startup and/or your test scenario has to talk to one or multiple remote systems (or multiple java programs within one scenario), you have to setup multiple java connections, and pass the connection to use to your Groovy actions. This can be done either via an input pin, or via an expecco environment variable. The algorithm to provide this is as follows:

  • if the action has a pin named "groovy", and it is connected, that pin's value is used as Groovy shell handle
  • otherwise, if the environment contains a variable named "GROOVY", that variable's value is used
  • otherwise, a Groovy shell is instantiated and used, on the JVM which is given by:
    • if the action has a pin named "java", and it is connected, that pin's value is used as Java Bridge handle
    • otherwise, if the environment contains a variable named "JAVA", that variable's value is used
    • finally, otherwise a local JVM connection is used (a shared singleton connection).

To use input pins, you have to create corresponding pins manually, and the pin names MUST be "java" / "groovy". You cannot use these two reserved names as regular input pin names.

Calling Existing Code in the System Under Test via a Groovy Action[Bearbeiten]

A Groovy block's code looks very similar to a regular JavaScript block's code. However, it is executed on the SUT, and can therefore instantiate Java objects and call static and member functions.

For example, too call a static function named "foo" in a class named "MyTestClass", use the following groovy block:

def execute() {
    MyTestClass.foo("hello world");
}

if the class is located in another package (or especially: in one of your own), use explicit package prefixes:

def execute() {
    javax.swing.JFrame frame = new javax.swing.JFrame();
    javax.swing.JButton button = new javax.swing.JButton( "Press Me" );

    frame.add(button);
    frame.setSize(300,100);
    frame.setVisible(true);
}

or an import declaration:

import javax.swing.*;

// end of import definitions

def execute() {
    JFrame frame = new JFrame();
    JButton button = new JButton( "Press Me" );

    frame.add(button);
    frame.setSize(300,100);
    frame.setVisible(true);
}

Notice that you may not remove the comment line starting with "// end of....".
This separator pattern has been hardcoded into the Groovy machinery to separate any class and import definitions from the execution method.

Defining a New Class in the System Under Test using Groovy[Bearbeiten]

You can define your own classes in a Groovy block:

class MyClass extends Object {
    Object someState;

    def set_someState(Object newValue) {
        someState = newValue;
    }
    def get_someState(Object newValue) {
        return someState;
    }
    def String toString() {
        return "a very\nlong\nmultiline\na\nb\nstring\n";
    }
}

// end of local definitions

def execute() {
    outputPin.value( new MyClass() );
}

Instances of new classes may be required especially to install listeners and callbacks when testing UI or server applications. A typical scenario is when you want expecco to be informed about mouse or keyboard events (MouseListener).

Defining a Listener Class in the System Under Test using Groovy[Bearbeiten]

A common pattern in Java communication frameworks is to define an abstract classes or interfaces which has to be subclassed by users of the framework. This pattern is common for callbacks or event listeners. The programmer has to define a listener class, create an instance of it, and register it towards the communication framework. To interact with such a framework in expecco, you will need a listener class, which responds to callbacks from the framework and either remembers them for later queries from a testcase's activity, or by directly calling back into expecco. The later is much harder to deal with, as those callbacks may come at any time, even when the test has already finished or the test is being debugged (remember that the JVM may run independent from expecco). So synchronization issues may be hard to solve. It is much easier and recommended to implement the first approach: the listener collects incoming events, until expecco is ready to process them.

You will need an elementary Groovy block to instantiate and register the listener towards the framework similar to the conceptual code below:

// a Groovy Elementary Block

import <interface or abstract class from which to inherit>;

class MyListener extends AbstractListener {
    ArrayList eventQueue = new ArrayList();
    Object eventNotifier = new Object();

    /**
      * Called from a framework when an event occurs.
      */
    public void onExampleEvent(Object event) {
        synchronized (eventQueue) {
            eventQueue.add(event);            
        }
        synchronized (eventNotifier) {
            eventNotifier.notifyAll();
        }
    }

    /**
      * Called from expecco to fetch next event. 
      * 
      * Of there is no event in the queue, blocks
      * until an event arrives (by means of #onExampleEvent())
      */
    public Object getNextEvent() {
        while (true) {
            synchronized (eventQueue) {
                if (! eventQueue.isEmpty()) {
                    return eventQueue.remove(0);
                }
            }
            synchronized (eventNotifier) {
                try {
                    eventNotifier.wait();
                } catch (InterruptedException ie) {
                    // Pass, try again
                }
            }
        }
    }
}

// end of local definitions

// called from expecco, to instantiate a listener
def execute() {
    var theListener = new MyListener();

    framework.registerListener ( theListener );
    outputPin.value( theListener ); // return it to expecco
}

Make sure that the above elementary code is called from your activity diagram in the initialization/setup phase, and that the returned listener reference is either remembered in an environment variable of your suite, or passed down to the actual test case via input/output pins.

Define an additional elementary block (here in JavaScript, but could also be Smalltalk or Groovy), which takes the listener as input pin and fetches the next event via getNextEvent():

// a JavaScript Elementary Block

execute() {
    var theListener;
    var event;


    theListener = inPin.value();
    event = theListener.getNextEvent();
    outpin.value ( event );
}

The above code blocks the test suite until an event arrives. It may be useful to add another method to query the number of received events, or to flush any previously received and already enqueued events.

In your test case, perform any stimulation required to make the framework generate an event, so that the listener's framework entry is called. Then, invoke the #getNextEvent() action to suspend execution until an incoming event arrives (you will probably set a time limit on that step's execution). Once this action finishes, the event is available in expecco as a reference to the real event object (remember: the event object is an object inside the remote Java machine, so what you have in expecco is a handle to that remote object).

All getters, setters and other methods of the event's implementation can be invoked from elementary code (any of the programming languages can do that, due to the message forwarding of the bridge).

For example, assuming that the event provides getEventID() and getMessageString() getters, which can be called from a Smalltalk action with:

id := theEvent getEventID. msg := theEvent getMessageString.

and from a JavaScript action or from another Groovy action witht:

id = theEvent.getEventID(); msg = theEvent.getMessageString();.

Also, the expecco object inspector has been enhanced to recognize those proxy bridge objects, and presents the internal state, inheritance and class protocol in a multitab fashion. This makes looking into those remote objects almost as comfortable as looking into local ones.

Groovy Datatype Limitations[Bearbeiten]

Integer types[Bearbeiten]

As Groovy dynamically compiles to plain Java bytecode and executes on top of a regular JVM, the integer range is limited to 32bit (64bit for "long int" types). No automatic conversion between small and large integers is performed. If required, you will have to use a BigNum package and/or cast int to long int. There is currently no automatic conversion or transparent support to pass large numbers from expecco to Groovy and vice versa. Integers passed from Java to expecco will be represented as instances of the Integer type there.

Limited Object Conversion[Bearbeiten]

Some limited form of object conversion is automatically performed when passing expecco's Smalltalk objects to Groovy, and back when returning values from Groovy. This conversion especially affects values passed to/from pins of a Groovy action.

The following table summarizes the conversion process:

from Smalltalk to Java and from Java to Smalltalk
String String String
Integer (up to 32/64 bit) int, long int, boxed Integer Integer
Float, Double float, double; boxed or unboxed Float
(Notice that Smalltalk Floats have double precision)
Fraction float, double; boxed or unboxed (danger alert: precision may be lost) Float
(Notice that Smalltalk Floats have double precision)
Boolean Boolean Boolean
Array of any above ArrayList of any above Array of any above
arbitrary Smalltalk Object (!) opaque reference to alien object (!) original Smalltalk Object (identity preserving) (!)
arbitrary Java Object -> opaque reference to alien Java Object original Java Object (identity preserving)

(!) does not work, yet: at the time of writing, Smalltalk objects other than the basic types listed above cannot be passed to Java. However, the reverse direction is possible, and Smalltalk includes a mechanisms to handle (and even inspect) remote Java objects.

No Smallalk Classes, No Smalltalk Objects[Bearbeiten]

Of course, no Smalltalk class can be used directly in Groovy code. And Smalltalk objects can only be passed as opaque handles to/from Groovy as described above. However, all Java classes are at your hands now! Some limited interaction with expecco and underlying Smalltalk objects is possible via the "eval()" and "perform()" API described below.

However, in general: if more complex objects need to be interchanged, this must be either done by converting them to an array (of objects), possibly an array of arrays, which is passed to a Groovy "interface function" first, which instantiates the required Java object(s). Or, alternatively to some ascii representation (XML or JSON, for a more lightweight approach) and convert this back and forth.

Handling of Java Objects in expecco/Smalltalk[Bearbeiten]

Java Object References (Handles)[Bearbeiten]

Groovy elementary function can return references to Java objects via output pins. Because the actual object is located inside a JVM (i.e. outside of expecco), these references are treated like opaque handles by the underlying Smalltalk runtime system. However, a mechanism is provided to allow for almost transparent access to the referenced object's private slots, its classes' static slots, its class hierarchy and method information. The object inspector is able to present that information in a convenient way, similar to how Smalltalk objects are presented. Also methods of the Java object can be called and values returned to expecco.

Message Forwarding[Bearbeiten]

Smalltalk and JavaScript code can send regular messages to Java objects. This is possible via the Smalltalk "doesNotUnderstand:" mechanism, which intercepts any call, sends a message to the real object (via the Java Bridge), awaits an answer and returns the result, which is usually a reference to another Java object. All of this is transparent to the programmer of the elementary code, except for the fact that Java method naming is different from Smalltalk. And of course the performance penalty, due to the interprocess communication, marshalling overhead and dynamic lookup via reflection on the Java side.

Method Name Conversions[Bearbeiten]

When a message is sent from Smalltalk code to a Java object, a message translation mechanism similar to the Smalltalk <-> JavaScript mechanism is used:

foo                  -> foo()
foo:arg              -> foo(arg)
foo:a1 _:a2 ... _:aN -> foo(a1, a2, ... aN) where the "_" are arbitrary strings.

thus, to call a Java object's "installHandler(arg1, arg2, arg3)" function, you should write in Smalltalk:

javaObject installHandler:arg1 _:arg2 _:arg3

and in JavaScript:

javaObject.installhandler(arg1, arg2, arg3)

It is obvious, that this kind of code is better written in JavaScript, being the same as a native Java call would look like.

Conversion of Dynamic Smalltalk Objects to Static Java Types[Bearbeiten]

Inside expecco (which is running under a Smalltalk VM), all object references are dynamically typed. This means, that in Smalltalk, every object holds a reference to its class and in principle, any object can be passed as argument to a called function. In contrast, Java requires the type of a function argument to be declared (either as a class or interface), and the argument type(s) are part of a method's signature.

Therefore, when calling a Java function from Smalltalk, the correct function must be found by reflection at execution time.

For example, if the Java object provides two methods as in:

public void foo(Integer arg) { ... }
public void foo(Float arg) { ... }

and it is called from Smalltalk with:

aJavaObjectHandle foo: 1

then the "Integer"-argument version of foo must be called.

Whereas if called as:

aJavaObjectHandle foo: 1.0

then the "Float"-argment version is to be called.

For this, the Java Bridge side uses reflection to scan the available methods for a best fit, and transforms the passed Smalltalk argument to the best matching type before calling the actual function.

There are some rare situations, where this automatic lookup fails or finds a wrong method. For example, if same-named functions exist with both a "float" (unboxed) and a "Float" (boxed) argument. In such cases, you should use a Groovy block as mediator, which calls the desired function with an explicitly casted argument, and call the Groovy block from Smalltalk (or JavaScript).

Current IDE Limitations[Bearbeiten]

The Smalltalk IDE has currently only limited support to extract and present Java's static type information. Currently, there is no class- and function name completion in the editor, making elementary code development for Java/Groovy less pleasant. Therefore, for complicated setups, it is a good idea to open a specialized Java development environment (e.g. eclipse) in parallel, and test the setup code there before copy-pasting it into a Groovy block.

Eclipse is also useful to find out class- and method names during development.

Groovy Code API[Bearbeiten]

Groovy code supports a subset of the above activity functions, which is intended provide an interface similar to the JavaScript and Smalltalk elementary code API. Of course, technically for every API function below, the elementary code executing in the remote Java VM has to make a remote procedure call back to expecco. On the JVM side, this is done very similar to the above described remote message mechanism, when messages are sent from Smalltalk to Java/Groovy. However, due to the static type system, it is not possible to call previously unknown interfaces. Therefore, only a fixed set of API entry points is supported.

Attention / Warning[Bearbeiten]

Unless explicitly prevented by the "mayStillDoExpeccoCalls()" function (described below), all called expecco functions listed below will ONLY work as expected WHILE the groovy action is still ACTIVE (i.e. while inside the Groovy "execute()" function).

The reason is that expecco ensures that handlers, which are responsible for transferring information from the groovy code back to expecco, will always be released, when the Groovy step has finished its execution. This is mostly required to prevent memory leaks in the Java VM, because the observer instance object is registered inside the JVM and would remain there forever, if not unregistered. This observer object is a proxy for Java code, which implements all the functions below and forwards them (via an IPC mechanism) to expecco. One new such object is required for every Groovy action execution and if not released, they sooner or later consume huge amounts of JVM memory. Therefore by default, the observer is released after the Groovy action execution.

In most Groovy blocks this is not a problem, except for those which install callback methods, AND those callbacks get called AFTER the Groovy block has finished (for example by another Java thread), AND the callback code calls one of those expecco interface functions. This includes the Transcript output functions, notifications and pin value writers.

Unless the observer is still around, callbacks are ignored and behave like a no-operation, if called after the step has finished. Of course, writing to a pin from within a callback AFTER the execute() function has finished does not work in any case (with or without a kept observer). The behavior in this case is undefined; currently, it is a no-operation, but it may be changed to raise an error in future versions.

The Current Activity (Groovy)[Bearbeiten]

The current activity instance is accessed as "this" inside the Groovy action code (like in an elementary JavaScript block). For every executed action, a new activity object is instantiated. It is usually alive during the execution only (i.e. it is destroyed and its memory reused automatically, after the block's action has finished). In Groovy (as in JavaScript or Java), if no receiver is given for a function call, "this" is the implicit receiver. Thus the statements "this.logInfo("hello")" and "logInfo("hello")" are equivalent.

Reporting (Groovy)[Bearbeiten]

  • error ()
    Report a defect (in the test). Stops execution.
  • error (infoString)
    Report a defect (in the test). Stops execution.
  • fail ()
    Report a failure (in the SUT). Stops execution.
  • fail (infoString)
    Report a failure (in the SUT). Stops execution.
  • inconclusive ()
    Report an inconclusive test. Stops execution.
  • inconclusive (infoString)
    Report an inconclusive test. Stops execution.
  • activitySuccess ()
    Finishes the current activity with success.
  • activitySuccess (infoString)
    Finishes the current activity with success.
  • pass ()
    Finishes the current testCase with success.
  • pass (infoString)
    Finishes the current testCase with success.

 

Logging (Groovy)[Bearbeiten]

  • logError (messageString)
    Adds a error message to the activity log.
  • logError (messageString, detail)
    Adds a error message to the activity log.
  • logWarning (messageString)
    Adds a warning to the activity log.
  • logWarning (messageString, detail)
    Adds a warning to the activity log.
  • logInfo (messageString)
    Adds an info message to the activity log.
  • logInfo (messageString, detail)
    Adds an info message to the activity log.
  • alert (messageString)
    Adds an warning message to the activity log, and also shows a DialogBox, which has to be confirmed by the operator. The dialog box and confirmation can be disabled by a settings flag in the "Execution - Log -Settings" dialog (by default it is disabled).
  • warn (messageString)
    Same as alert() (for Smalltalk compatibility).

 

Reflection, Information, Queries and Accessing (Groovy)[Bearbeiten]

  • environmentAt (anEnvironmentVarName)
    The value of an environment variable
  • environmentAt_put (anEnvironmentVarName, value)
    Changing the value of an environment variable.
  • nameOfActiveTestPlan ()
    The name of the currently executing text plan
  • nameOfActiveTestPlanItem ()
    The name of the currently executing text case
  • nameOfStep ()
    The name of the corresponding step of the activity

 

Interaction with Expecco (Groovy)[Bearbeiten]

  • eval (smalltalkCodeString)
    Evaluate a piece of Smalltalk code inside expecco.
  • evalJS (javascriptCodeString)
    Evaluate a piece of JavaScript code inside expecco.

 

Special Functions[Bearbeiten]

  • mayStillDoExpeccoCalls (boolean)
    Make sure that the functions described here will still be callable (by Java callback functions from other threads) even after the step has finished execution. In other words, do NOT release the observer object which is responsible for the transfer of information between the two systems. Be aware that this object remains and will never be released, until the bridge connection is closed. I.e. there is a potential for a memory leak inside the Java VM here.

 

Pin Functions (Groovy)[Bearbeiten]

Currently, groovy blocks do not support a variable number of input or output pins.

Attention:
Groovy uses many more reserved keywords for syntax than JavaScript or Smalltalk. These keywords cannot be used as pin names, and you will get a syntax error ("<foo> token not expected") if you try. Be careful to not name your pins as any of: "in", "return", "class", "private", "public", etc. As a proven best practice, add a "Pin" suffix to your pin names (i.e. name it "inPin", instead of "in").

Input Pins (Groovy)[Bearbeiten]
  • hasValue ()
    Returns true if the pin has received a value
  • value ()
    Returns the value of the pin. Raises an error if the pin did not receive any value.
  • valueIfAbsent (alternativeValue)
    Returns the value of a pin or the alternativeValue if the pin did not receive any value.

 

Output Pins (Groovy)[Bearbeiten]
  • isBuffered ()
    True if the pin is buffered
  • value (data)
    Writes the value.

 

Transcript, Stderr and Stdout[Bearbeiten]

The expecco "Transcript", "Stderr" and "Stdout" are also accessible from Groovy code. However, only a limited subset of messages is supported:

  • cr ()
    Adds a linebreak (i.e. followup text will be shown on the next line)
  • show (arg)
    Adds a textual representation of the argument, which can be a string, number or any other object.
  • showCR (arg)
    A combination of show(), followed by a linebreak.
  • writeLine (string)
    For compatibility with Java's PrintStream protocol.
  • print (string)
    For compatibility with Java's PrintStream protocol (expecco 2.8).
  • println (string)
    For compatibility with Java's PrintStream protocol (expecco 2.8).
  • println ()
    For compatibility with Java's PrintStream protocol (expecco 2.8).

 

DotNET Elementary Blocks[Bearbeiten]

VisualBasic Elementary Blocks[Bearbeiten]

Support for VisualBasic Script elementary block execution is provided as an extension plugin, and requires a separate plugin license. It is only available for expecco running on the MS Windows operating system.

Code written as a VisualBasic elementary block is not executed directly by expecco. Instead, the code is forwarded to a VisualBasic scripting host which runs as another process either on the local or on a remote host. The scripting host must be a Microsoft Windows host (but expecco itself may run on any type of operating system). By using VisualBasic blocks, expecco's basic black box test functionality can be easily extended by many powerful gray- and white-box tests. Many libraries for device and equipment control, COM/DCOM and other technologies and UI interaction are possible with the VisualBasic plugin.

Please consult the separate VisualBasic plugin documentation for more detail.

Tutorial: Common Tasks[Bearbeiten]

This chapter gives code fragments and examples for common tasks.

The underlying Smalltalk class library contains (among others) very powerful and robust collection, stream and number representation systems, of which many functions are useful for elementary block developers. The set of usable functions is much larger than for example in Java or C standard libraries. Thus for a newcomer to Smalltalk, it is highly recommended and fruitful to read the introduction texts and/or use the class browser for a deeper understanding. You can benefit from existing code and save a lot of development time in knowing a little about your class libraries.

Also, it is a good idea to try the sample code in a "workspace" window or within the code editor, by selecting the piece of code to be tried, and performing the "printIt" menu function. Thus you can quickly test&try code fragments without a need for an elementary block and its setup.


String Handling[Bearbeiten]

Notice, that in Smalltalk all collection indices are 1-based. I.e. the index of the first element is 1, and the last index is the collection's size. This is different from C, JavaScript and Java. Thus Smalltalk loops over collection elements should run from 1 to the collection's size, not from zero to the size minus 1. You will get an index exception, if you try. Also notice, that index-based enumeration is actually seldom needed, due to the powerful "do:", "collect:", "select:" etc. family of enumeration methods. These do all the index computations for you, and are often heavily tuned for best performance.

Also notice, that in Smalltalk, String is a subclass of Collection, and most of the functions described below are actually not implemented in the String class, but inherited from a superclass. Take this into consideration, when searching for String utility functions in the class browser (i.e. look there first or click on the SeeInheritedInBrowser.png-"see inherited methods" button in the class browser). On the other hand, the fact that most of those functions are implemented in the Collection superclass also means, that they work on many other collections (Arrays, ByteArray, OrderedCollection).

If you are in doubt, open a workspace window, enter a piece of code and evaluate it the (using the "printIt" menu function).

Copying Parts of a String[Bearbeiten]

To extract a substring, and the index and/or count is known, use one of:

   aString copyFrom: startIndex to: stopIndex
   
   aString copyFrom: startIndex
 
   aString copyTo: stopIndex
 
   aString copyFrom: startIndex count: n
 
   aString copyFirst: count   "/ same as copyTo:
 
   aString copyLast: count    "/ gives the last count characters

All of them copy including a given start- or stop index. I.e.

   'hello world' copyFrom:2 to:5

will return 'ello'. This is different from some C/Java functions, which expect the stopIndex to be one-after the last copied element.

Finding Elements and Substrings[Bearbeiten]

To search for elements of a string (which are characters), use:

   aString indexOf: aCharacter
 
   aString indexOf: aCharacter startingAt: startIndex

to search from the beginning for a given character, and:

   aString lastIndexOf: aCharacter
   aString lastIndexOf: aCharacter startingAt: startIndex

to search backward from the end. These methods return the 1-based index, or 0 (zero) if not found.


Notice that a character constant is written in Smalltalk as $<x>, where <x> is a printable character or a space. Non-printable characters must be specified by their name ("Character return", "Character lf", "Character tab") or their unicode-point (decimal: "Character value: 127" or hex: "Character value: 16r1F00").

If there are multiple characters to be searched for, pass a collection of searched characters to one of:

   aString indexOfAny: aBunchOfCharacters
   aString indexOfAny: aBunchOfCharacters startingAt: startIndex

for example,

   'hello world' indexOfAny: 'eiou' startingAt:3

looks for the next vocal at or after position 3.

Finally, there is a very generic search function, where you can pass a test-procedure as argument:

   aString findFirst:[:ch | ...aboolean test on ch ...]

will return the index of the next character for which your boolean check computes a true value. For example, to search for the next digit, use:

   aString findFirst:[:ch | ch isDigit]

to search for a digit OR period OR dollar character, you can use one of:

   aString findFirst:[:ch | ch isDigit or:[ ch == $. or:[ ch == $$]]]
   aString indexOfAny:'0123456789$.'

use whichever you find more readable. (there is also a "findFirst:startingAt:', and corresponding "findLast:" and "findLast:startingAt:" for backward searches)


Because it is a common task to search for separators and other non-printable characters, special methods exist for those:

   aString indexOfSeparatorStartingAt: startIndex
   aString indexOfNonSeparatorStartingAt: startIndex
   aString indexOfControlCharacterStartingAt: startIndex

where separators are the whitespace characters (space, tab, vt, cr and lf), and control characters are all characters below ascii 32.

All of the index-search methods return a 0 (zero) if the character is not found.

Substrings can be searched with:

   aString indexOfSubCollection: anotherString
 
   aString indexOfSubCollection: anotherString startingAt: startIndex

There are also corresponding backward search variants "lastIndexOfSubCollection:".

Notice the somewhat generic names "indexOfSubCollection:" instead of a more intuitive "indexOfString:". The reason is that these methods are inherited from collection, which does not care if the elements are characters or other objects. This means, that you can use the same method to search for a slice in an array:

   #(1 2 3 4 5 6 7 8 9 8 7 6 5 1 2 3 2 1) indexOfSubCollection:#(9 8 7)

using the same code.

To find all such methods in the class browser, search for implementors of "*indexOf*", and look at the matches in Collection, SequenceableCollection, CharacterArray and String.

If you don't need the Index[Bearbeiten]

If you only need to check if a character or substring is present, but not in the exact position, you can use "includes:", "includesSubCollection:", "includesAny:" or "includesAll:" in a similar spirit. These may be slightly faster and make the code more readable. They return a boolean.

Caseless Searches[Bearbeiten]

For strings, special substring searches are available, which ignore case (upper/lower) differences:

   aString includesString: anotherString caseSensitive: false
 

Prefix and Suffix Checks[Bearbeiten]

   aString startsWith: anotherString
 
   aString endsWith: anotherString

are obvious. The powerful:

   aString startsWithAnyOf: aCollectionOfStrings

searches for multiple string prefixes. For example,

   'hello' startsWithAnyOf: #( 'ab' 'ce' 'de' )

would look for all of them and return false in this concrete example. Of course, there is a corresponding "endsWithAnyOf:".

Splitting Parts of a String (or Collection)[Bearbeiten]

The simplest are:

   aString asCollectionOfWords
   aString asCollectionOfLines

which split at separators and line-ends respectively and return a collection as they say in their name.

More generic is:

   aString split: charOrString

which returns a collection of substrings split at "charOrString" elements. as the name suggests, you can pass either a single character or a substring as argument. Thus:

   'hello world isn't this nice' split:(Character space)

will give you the strings ('hello' 'world' 'isnt' 'this' 'nice'). Whereas:

   'some : string : separated : by : space : colon space' split:' : '

will return a collection containing: ('some' 'string' 'separated' 'by' 'space' 'colon' 'space').


To split into same-sized pieces, use:

   aString splitForSize: charCount

this may be less of interest for strings, but is handy to split fixed records of binary data (remember: these functions are implemented in a superclass of string, from which ByteArray also inherits)

Joining and Concatenating Strings[Bearbeiten]

The simplest of them is the "," (comma) operator. This takes two collections (and as such also Strings) and creates a new collection containing the concatenation of them. Thus:

   'hello' , 'world' 

will create a new string containing 'helloworld', and:

   'hello' , ' ' , 'world'

will give you 'hello world'.

Notice that concatenation may become somewhat inefficient, if many strings are concatenated, because many temporary string objects are created. Its time complexity is O(n^2), where n is the number of strings concatenated.

If you have to construct a string from many individually, either use one of the join functions below (with O(n) complexity), or a writeStream with O(n log n) complexity. Both handles the reallocations much more efficiently (actually: avoiding most of them).


To join a set of strings, use:

   asCollectionOfStrings asStringWith: separator

where separator may be a character, a separator string or nil. For example:

   #( 'a' 'b' 'c') asStringWith:nil

gives 'abc', and:

   #( 'a' 'b' 'c') asStringWith:' : '

returns: 'a : b : c'

Using a writeStream, write:

   |w|

   w := WriteStream on:(String new:10).
   w nextPutAll: 'a'.
   w nextPutAll: 'b'.
   ...
   w contents

but please read more on streams in the stream chapter below.

Formatting Strings (Method A)[Bearbeiten]

For text messages, you may want to construct a pretty user readable string which contains printed representations of other objects. For this, use:

   formatString bindWith: argument
 
   formatString bindWith: argument1 with: argument2
 
   formatString bindWith: argument1 with: ... with: argumentN (up to 5 arguments)
 
   formatString bindWithArguments: argumentCollection (any number of arguments)

The formatString itself may contain "%X" placeholders, which are replaced by corresponding printed representations of the argumentX. The arguments will be converted to strings if they are not, thus arbitrary objects can be given as argument (although, in practice, these are usually numbers).

If many arguments are to be passed in, the placeholder names X may consist of a single digit (1-9) only for the first 9 arguments. For other arguments, you must write "%(X)". I.e. '%(10)' to get the tenth argument sliced at that position.

The last of the above methods is a powerful tool, in that it does not only handle argumentCollections indexed by numbers (i.e. Array of arguments), but also Dictionaries, which are indexed by name. For example, if you have a Dictionary collection of a person record (in expecco: a compound datatype instance), with element keys "firstName", "lastName" and "city", you can generate a nice printed string with:

   |record|

   record := Dictionary new.  "/ example using a dictionary
   record at:'firstName' put: 'Fritz'.
   record at:'lastName' put: 'Müller'.
   record at:'city' put: 'Stuttgart'.

   '%(lastName), %(firstName) lives in %(city)' bindWith: record.

gives you the string: 'Müller, Fritz lives in Stuttgart'. In combination with a expecco compound datatype instance, this would be:

   |person|

   person := myType new.
   person firstName:'Fritz'.
   person lastName:'Müller'.
   person city:'Stuttgart'.
 
   '%(lastName), %(firstName) lives in %(city)' bindWith: person.

Formatting Strings (Method B)[Bearbeiten]

There is also a formatting method similar to "printf". It takes a format string and a number of arguments and constructs a string representation. As Smalltalk does not support variable numbers of arguments, these must be passed with different keyword messages:

   formatString printfWith: arg
   formatString printfWith: arg1 with: arg2
   formatString printfWith: arg1 with: arg2 with: arg3
   ... up to 5 arguments
   formatString printf: argVector

For the argVector, use the {..} construct.

For example,

   '%05d 0x%-7x %f' printf:{ 123 . 517 . 1.234 }

generates the string:

   '00123 0x205     1.234'

The above provides the functionality of C's "sprintf" - i.e. it generates a string as result. However, streams also understand printf-messages, so you can also print directly to a stream:

   Transcript printf:'%05x\n' with:12345

or:

   Transcript printf:'%05x %d %f %o\n' withAll:{ 123. 234*5. 1.234. 254 }

Notice, that printf translates the standard C-character escapes "\n", ”\t" etc.

Regex and Glob Matching[Bearbeiten]

Both match a string against a match pattern, but the syntax and functionality is different.

  • Glob is much easier to use but less powerful. Glob is the match algorithm used in the Unix and MS-DOS filesystem (e.g. when saying "ls *.foo" or "dir *.foo" on the command line).
  • Regex is the algorithm used in tools like "grep" and many text processing systems and languages.

Notice that regex patterns are different and not just a superset of glob patterns. The meanings of * and . are different.

For a Glob match, use:

   aGlobPattern match: aString

   aGlobPattern match: aString caseSesitive: false

for a regex match, use:

   aString matchesRegex: aRegexPattern

   aString matchesRegex: aRegexPattern ignoringCase: true

Sorry for the inconsistent method naming which is due to the fact that the regex matcher originated in a public domain package, which was developed independently from the original ST/X system. In order to remain compatible with other Smalltalk dialects, the names were kept.

Please consult Wikipedia for more information on Regex [1] and GLOB [2].

Code Converting[Bearbeiten]

There are code converters for various character encodings in and below the CharacterEncoder class. Most of them are obsolete or seldom used these days, as now most systems support Unicode (which was not the case, the ST/X was written 30 years ago and switched to Unicode in the 90's!).

In general, you can get an instance of an encoder via a convenient CharacterEncoder interface:

   CharacterEncoder encoderFor:<encoding>

or:

   CharacterEncoder encoderToEncodeFrom:<encoding1> to:<encoding2>

where encoding is a name, such as #iso8859-1', 'unicode', 'utf8', 'utf16', 'ascii', 'jis7' etc.

Thus, to encode a string from unicode (which is the internal encoding anyway) to Japanese jis7, use:

   (CharacterEncoder encode:#'jis') encode:'hello')

or to encode from Microsoft cp1253 to koi8-r, use:

   (CharacterEncoder encoderToEncodeFrom:#'cp1253' to:#'koi8-r') encode:<someStringInCP1253>

You will probably not need that general interface, but use UTF8 these days. For those, the String class provides easier to use interface:

   aString utf8Encoded

   aString utf8Decoded

Collection Slices[Bearbeiten]

A slice is a reference to a subcollection, which looks and behaves like any other collection, but shares the underlying data with the original collection. Thus modifications in either the original or slice are visible in the other and vice versa.

To get a slice, use one of the "from:", "from:to:", "to:" and "from:count:" methods (the names are similar to the above mentioned "copy*" methods).

For example:

   data := ... some big ByteArray read from a file ...

   record1 := data from: 1 to: recordSize.
   record2 := data from: recordSize+1 to: recordSize*2.
   etc.

gives you the records from the big collection as individual objects. If any modification is made to any of the slices, that modification is actually made in the underlying data collection (i.e. C-Programmers may think of this as a "pointer into the data object".

Slices are especially useful with bulk data processing, to avoid the creation of individual record copies. They are a bit dangerous, as the programmer has to always be aware if the side effects.

Stream Handling[Bearbeiten]

to be written...

for now a summary:

Creating[Bearbeiten]

streams for reading:

   rs := ReadStream on: aCollection.
   rs := aCollection readStream

streams for writing:

   ws := WriteStream on: (String new: initialSize)
   ws :=  writeStream

Checking[Bearbeiten]

   s atEnd

   s size

Positioning[Bearbeiten]

   s position
   s position: pos
   s rewind
   s setToEnd
   s backStep
   s skip: n

Reading[Bearbeiten]

   rs next

   rs next: count

   rs nextAvailable: count
   rs upTo: element
   rs skipTo: element
   rs through: element

Writing[Bearbeiten]

   ws nextPut: element
   ws next: count put: element
   ws nextPutAll: aCollectionOfElements
   ws nextPutLine: aCollectionOfElements
   ws print: anObject
   ws println
   ws println: anObject
   ws contents

File Streams[Bearbeiten]

   aString asFilename readStream
   aString asFilename writeStream

See more below in the chapter on file operations.

Numbers[Bearbeiten]

Reading Numbers from a String[Bearbeiten]

To read a number from a string, use the "readFrom:" method provided by the number classes, where the argument is either a string, or a readstream. A readstream is obtained with either "(ReadStream on:aString)" or the more convenient "(aString readStream)".

Thus, in general, code to read a number looks like:

   <class> readFrom: aString

or:

   <class> readFrom: (ReadStream on:aString)

where "<class>" is one of "Integer", "Float", "FixedPoint" or "Number".

If the string contains nothing but the number string, these two behave the same. However, there is an often overlooked difference in case of extra characters after the number:

  • the string-reader expects that the given string contains a representation of a corresponding number object, and NO extra characters before or after it. It will report an error otherwise.
  • in contrast, the stream reader will first skip any spaces, then read as many characters as required from the stream, return the number and leave the stream positioned after the number. It will report an error if no number can be read.

Thus both:

   Integer readFrom:'1234'

and:

   Integer readFrom:(ReadStream on:'1234').

return the Integer object 1234.

Whereas:

   Integer readFrom:'1234bla'

will raise an error, but:

   Integer readFrom:(ReadStream on:'1234bla')

returns the integer 1234 and leave the stream positioned on the 'bla'.

Notice that "Integer readFrom:" will read an integer, but not a float. Thus:

   Integer readFrom:'1234.5'

will raise an error, but:

   Integer readFrom:(ReadStream on:'1234.5').

will return the Integer object 1234 and leave the stream positioned on the decimal point character.

When expecting an arbitrary number (e.g. with or without decimal point), use:

   Number readFrom: aStringOrStream

it will return either an integer object or a float object depending on what it gets. If you want to enforce getting a float, use

   Float readFrom: aStringOrStream

which returns the float "1234.0" when given the string "1234".

Error handling[Bearbeiten]

By default, the readers raise an exception, if any conversion error occurs. This can be caught in an error handler as described elsewhere (on:do:).

However, the conversion routines can also be given a correction function as argument, which is invoked in case of an error, and which provides a replacement value.

For this, all of the methods described are also present in a variant with an extra "onError:" argument, which provides that replacement.

For example:

   Integer readFrom: aString onError:[ 0 ]

will return the number as usual if OK, but zero if not. Of course, arbitrary code can be provided inside this handler - especially it may show an error dialog and ask the user for a replacement, or return from the executed elementary block:

   val := Integer
           readFrom:aString
           onError:[
               |ersatz|
               ersatz := Dialog request:'Please enter a correct number'.
               Integer readFrom:ersatz
           ]
Reading with a Different Decimal Point Character[Bearbeiten]

If the number was originally for a European human, it may contain a decimal point different from ".".

For example, in Germany you may have to convert the monetary amount "1245,99".

For this, use:

   Float readFrom: aStringOrStream decimalPointCharacters:','

or (better):

   FixedPoint readFrom: aStringOrStream decimalPointCharacters:','

If your code has to deal with both US and German numbers, provide all possible decimal points in the string, as in:

   FixedPoint readFrom: aStringOrStream decimalPointCharacters:',.'
Reading Multiple Numbers from a Stream[Bearbeiten]

A common task is to read multiple numbers from a single long string. Of course, you could first split the string into pieces and use the above "readFrom:" on each. This may also be a quick&dirty solution, if the individual number strings are contained in fixed-length fields and there are no separators in between the fields (but read below for a better way to do this). Take a look at the string handling examples on how to split strings.

To read multiple numbers, the best solution is to first create a read stream on the string, then read the individual numbers:

   myString := '1234 456.7 0.5 2345.3456 3234234234234234234234234234234234234'.
   myStream := myString readStream.
   n1 := Number readFrom: myStream.      "/ gives the integer 1234 in n1
   n2 := Number readFrom: myStream.      "/ gives the float 456.7 in n2
   n3 := Number readFrom: myStream.      "/ gives the float 0.5 in n3
   n4 := FixedPoint readFrom: myStream.  "/ gives the fixedPoint 2345.3456 in n4
   n5 := Number readFrom: myStream.      "/ gives the large integer 3234...34 in n5

Notice that "readFrom:" first skips any separators (spaces) - therefore no extra code is needed to deal with those.

If extra stuff needs to be skipped, use any of the existing stream functions (skipFor:/skipUntil: etc.). If none does the job, you can look at individual characters and skip until a match is found. For example, to read 3 numbers from the following string '123 bla 456.8|0.5', you could use:

   s := '123 bla 456.8|0.5' readStream.
   n1 := Integer readFrom:s.
   s skipSeparators.
   s nextAlphanumericWord.  "/ skips over the 'bla'
   n2 := Number readFrom:s.
   s next.                  "/ skips over one character
   n3 := Number readFrom:s.

of course, the above code would not handle strings like '123 bla bla 456.8|0.5' or '123 bla 456.8 |0.5'.
Using "peek", which looks at the next character in the stream without consuming it, we could to change the code to:

   n1 := Integer readFrom:s.
   [ s peek isDigit ] whileFalse:[ s next ]. "/ skips over the 'bla bla'
   n2 := Number readFrom:s.
   s next.                                   "/ skips over one character
   n3 := Number readFrom:s.

Notice that "peek" returns nil, if the end of the stream is reached. And that "nil isDigit" will report an error (because only characters can be asked for being a digit). Thus, the code like the above is not prepared to read a variable number of numbers.

Reading a Particular Number of Numbers from a Stream[Bearbeiten]

Assume that you are given a number which defines how many numbers to extract from a given string.

Of course, you can use a "start to: stop do:[:idx |..." loop, but experienced Smalltalk programmers make use of the collection protocol, as in:

   (1 to: count) collect:[:idx | Number readFrom: aStream ]

Such a construct is needed e.g. when you have to parse a string, where the first entry defines how many numbers follow:

   coll := (1 to: (Integer readFrom: myString)) collect:[:idx | Number readFrom: aStream ].

which would parse the string "5 1.0 3.0 2.0 -1.0 .05" into a 5-element collection containing the converted numbers.

Reading a Variable Number of Numbers from a Stream[Bearbeiten]

To do so, we should collect numbers as being read from the stream in a collection:

   collectedNumbers := OrderedCollection new.
   [ aStream atEnd ] whileFalse:[
       collectedNumbers add: ( Number readFrom: aStream ).
   ]

or, if any non-digits and non-spaces between numbers should be skipped, use:

   collectedNumbers := OrderedCollection new.
   [ aStream atEnd ] whileFalse:[
       aStream skipUntil:[:char | char isDigit].
       collectedNumbers add: ( Number readFrom: aStream ).
   ]

Using the Pattern Matcher to Extract Parts[Bearbeiten]

In rare situations, complex patterns need to be matched and numeric values retrieved from the matched parts. For this, use a regex pattern matcher to first extract parts from the string, and then convert the substrings. For example, to fetch numbers after certain keywords in a string, you could use:

   "/ assuming that the string is of the form:
   "/    <..arbitrary-text...>key1<number1><...arbitrary text...>key2<number><...arbitrary text...>
   "/ where arbitrary text may even contain key1 and key2, but not followed by a number,
   "/ we first use a regex to match, then convert the matched regex parts:
   myString := 'bla bla key1 bla key11234more bla key2 bla key29876bla bla'.
   parts := myString allRegexMatches:'((key[12]))([0-9]+)'.
   numbers := parts collect:[:eachPart | Number readFrom: (eachPart copyFrom:5) ].

notice that parts delivers the matched strings i.e. the collection ('key11234' 'key29876'), so that we have to skip over the 'keyX' prefix before extracting the numbers.

File Operations[Bearbeiten]

to be written -

for now, a short summary:

   aString asFilename
   aFilename readStream
   aFilename writeStream


Checking[Bearbeiten]

   aFilename exists
   aFilename isReadable
   aFilename isWritable
   aFilename fileSize
   aFilename isDirectory
   aFilename isNonEmptyDirectory
   aFilename isRegularFile
   aFilename isSymbolicLink
   aFilename info

gets all info; follows any symbolic link. Includes ownerID, groupID, access and modification times etc.

   aFilename linkInfo

gets all info of a symbolic link itself. Includes ownerID, groupID, access and modification times etc.

   aFilename accessTime
   aFilename modificationTime
   aFilename creationTime (same as modificationTime on Unix systems)
   aFilename fileType
   aFilename mimeType

File names[Bearbeiten]

   aFilename pathName
   aFilename directory
   aFilename baseName
   aFilename suffix
   aFilename hasSuffix: aString
   aFilename isAbsolute
   aFilename isRelative
   aFilename / subPath
alternative:
   aFilename construct: subPath

Operations[Bearbeiten]

   aFilename makeDirectory
   aFilename recursiveMakeDirectory

ensures that all needed intermediate folders along the path are also created.

   aFilename removeDirectory
   aFilename recursiveRemoveDirectory
   aFilename moveTo: newName
   aFilename copyTo: newName
   aFilename recursiveCopyTo: destinationName


Directory Contents[Bearbeiten]

   aFilename directoryContents
   aFilename directoryContentsDo:[:eachFile | ...]
   aFilename filesMatching: aGLOBPatternString

File Contents[Bearbeiten]

   aFilename contents

retrieves the contents as a collection of line strings. Be careful - this should not be used for huge files.

   aFilename contentsAsString

retrieves the contents as one (possibly big) string. Be careful - this should not be used for huge files.

   aFilename binaryContentsOfEntireFile

retrieves the contents as one (possibly big) byte array. Be careful - this should not be used for huge files.

   aFilename readingLinesDo:[:eachLineString | ... ]

better use this for large files

   aFilename readingFileDo:[:stream | ... ]

or that to read yourself inside the code block.

Shared Data and Synchronization[Bearbeiten]

Examples[Bearbeiten]

The following code examples contain versions for Smalltalk, JavaScript and Groovy. Notice that Groovy actions are invoked via a remote procedure call (RPC) mechanism, which means that they are much slower due to the communication, encoding, decoding and general round trip delays. Therefore, Groovy blocks should actually only be used to access data inside the system under test or call functions which are not available in the base system (such as additional protocols or hardware access, for which only a JAR is available). Thus, most of the examples below are for demonstration purposes, not for real world scenarios (i.e. it will certainly slow down your test runs, if you do math via Groovy, as shown below). Also bear in mind, that the underlying numeric representations are less flexible and more error-prone if you do math in Groovy, because there will be less signalling of overflow/underflow situations. Especially with integer arithmetic, which may be outside the 32bit/64bit range. Also, Java has no exact fraction representations, which means that you may loose precision in floating point math operations.

Reading/Writing Pin Values[Bearbeiten]

Assuming that the block has two pins, named "in1" and "in2" and two output pins, named "out1" and "out2", the following blocks write concatenated strings to both outputs:

JavaScript[Bearbeiten]

(runs inside expecco when executed)

execute() {
    out1.value( in1.value() + in2.value() );
    out2.value( in2.value() + in1.value() );
}
Smalltalk[Bearbeiten]

(runs inside expecco when executed)

execute
    out1 value:(in1 value , in2 value).
    out2 value:(in2 value , in1 value).

Groovy[Bearbeiten]

(runs inside the JVM, which may be the system under test or another machine running Java)

def execute() {
    out1.value( in1.value() + in2.value() );
    out2.value( in2.value() + in1.value() );
}

Reading/Writing Environment Variables[Bearbeiten]

Assuming that a String-typed environment variable named "IncDec" and an Integer-typed variable named "Counter" exist in the project's environment, and are writable, the following blocks read and write to either variable:

JavaScript[Bearbeiten]

(runs inside expecco, when executed)

execute() {
    if ( environmentAt("IncDec") == "inc" ) {
        environmentAt_put("Counter", environmentAt("Counter") + 1);
    } else {
        environmentAt_put("Counter", environmentAt("Counter") - 1);
    }
}
Smalltalk[Bearbeiten]

(runs inside expecco, when executed)

execute
    (self environmentAt:'IncDec') = 'inc' ifTrue:[
        self environmentAt:'Counter' put:(self environmentAt:'Counter')+1.
    ] ifFalse:[
        self environmentAt:'Counter' put:(self environmentAt:'Counter')-1.
    ]
Groovy[Bearbeiten]

(runs inside the JVM, which may be the system under test or another machine running Java). Notice that the environment-access from inside Groovy involves even more overhead, as those require another RPC call from Groovy back to expecco. When executed, the code below will make 4 full remote-procedure call roundtrips (1 for the call and return value, one for "environmentAt(IncDec)", one for "environmentAt(Counter)" and a fourth one for "environmentAt_put()").

def execute() {
    if ( environmentAt("IncDec") == "inc" ) {
        environmentAt_put("Counter", environmentAt("Counter") + 1);
    } else {
        environmentAt_put("Counter", environmentAt("Counter") - 1);
    }
}

Sending Messages to the Transcript[Bearbeiten]

JavaScript[Bearbeiten]

(runs inside expecco, when executed)

execute() {
    Transcript.showCR("---------------------");
    for (var y=1; y<=10; y++) {
        for (var x=1; x<=10; x++) {
            Transcript.show( x * y );
            Transcript.show( " " );
        }
        Transcript.cr();
    }
}
Smalltalk[Bearbeiten]

(runs inside expecco, when executed)

execute
    Transcript showCR:'---------------------'.
    1 to:10 do:[:y |
        1 to:10 do:[:x |
            Transcript show:(x * y); show:' '.
        ].
        Transcript cr.
    ].
Groovy[Bearbeiten]

A few of the common messages and objects for logging and printing are also made available to Groovy code. Of course, a hidden inter-process mechanism is used, which forwards those calls back to expecco (remember: the Groovy code runs inside the system under test). The Transcript object seen by Groovy code is such a proxy object which implements a subset of the Smalltalk Transcript object but passes the argument strings via IPC back to expecco. So although the code looks similar to the above, the internal implementation (and timing) is completely different, because these calls are implemented as RPC calls from Groovy back to expecco.

def execute() {
    Transcript.showCR("---------------------");
    for (int y=1; y<=10; y++) {
        for (int x=1; x<=10; x++) {
            Transcript.show( x * y );
            Transcript.show( " " );
        }
        Transcript.cr();
    }
}

For compatibility with existing Java/Groovy code, the functions print() and println() can also be used (in addition to the above used show(), showCR() and cr() functions)

Square Root Block[Bearbeiten]

A function, which computes the square root of its input value, could be implemented like that: (the names of the pins are: in and out, their data type is Number):

JavaScript[Bearbeiten]

(runs inside expecco, when executed)

execute() {
    var inValue;

    inValue = in.value;          // Reading input pin value
    out.value( inValue.sqrt() ); // Writing to output pin
}

alternative:

execute() {
    var inValue;

    inValue = in.value;              // Reading input pin value
    out.value( Math.sqrt(inValue) ); // Writing to output pin
}
Smalltalk[Bearbeiten]

(runs inside expecco, when executed)

execute
    |inValue|

    inValue := in value.       "/ Reading input pin value
    out value: inValue sqrt.   "/ Writing to output pin
Groovy[Bearbeiten]

(runs inside a JVM/the SUT, when executed).
As mentioned above, this kind of operation should definitely be executed in Smalltalk/JavaScript inside expecco, and not in the system under test, due to the RPC overheads.

def execute {
    Object inValue;

    inValue = inPin.value();           // Reading input pin value
    outPin.value(Math.sqrt(inValue));   // Writing to output pin
}

(Notice: "in" is a reserved keyword in Groovy and cannot be used as pin name)

Random-Fail Block[Bearbeiten]

A block, which randomly fails (to demonstrate exception handling), could be implemented like:

JavaScript[Bearbeiten]

(runs inside expecco, when executed)

execute() {
    var dice;

    dice = Random.nextIntegerBetween_and_(1,6);
    if (dice <= 2) {
        fail();
    }
}
Smalltalk[Bearbeiten]

(runs inside expecco, when executed)

execute
    |dice|

    dice := Random nextIntegerBetween:1 and:6.
    (dice <= 2) ifTrue:[
        self fail.
    ].
Groovy[Bearbeiten]

(runs inside a JVM/the SUT, when executed)

execute() {
    Random rand = new Random();
    int dice = rand.nextInt(5)+1;

    if (dice <= 2) {
        fail();
    }
}

Using the Smalltalk/X Class Library (Smalltalk and JavaScript Blocks Only)[Bearbeiten]

Please note, that besides these direct interfaces with the expecco system described here, you can also use the whole class library of the runtime system. Please check the corresponding Smalltalk/X Documentation as well as the Documentation of the Class APIs.

expecco includes a fully featured class browser, to explore the underlying system's code.

You will see the source code, if you have checked the "Install Source Code" checkbox during the installation procedure (if you did not, redo the installation, and only check this box). You can also access the source code via the public eXept CVS repository.

The class browser is opened via the main-menu's "Extra"-"Tools"-"Class Browser" item. It is documented in full detail in the Smalltalk/X online documentation.


Here are a few more examples, using that class library:

Bulk-Data Reading from a File[Bearbeiten]

The following code reads a measurement data block of 100000 floating point values from file. The file was created beforehand by a recorder device, which was triggered by a dll-callout. For the demonstration, the code below does not read its values from input pins. In a real world application, the size of the file and its fileName would probably be passed in via input pins, of course.

JavaScript[Bearbeiten]
execute() {
    var N;  // should be read from an input-pin
    var fileName; // should be read from an input-pin
    var dataArray;
    var fileStream;

    N = 100000;
    fileName = 'dataFile.dat';
    fileStream = fileName.asFilename().readStream();
    dataArray = Array.new(N);
    for (i=1; i<=N; i++) {
        dataArray[i] = fileStream.nextIEEESingle();
    }
    out.value(dataArray);
}
Smalltalk[Bearbeiten]
execute
    |N fileName dataArray fileStream|

    N := 100000.
    fileName := 'dataFile.dat'.
    fileStream := fileName asFilename readStream.
    dataArray := (1 to:N) collect:[:i | fileStream nextIEEESingle].
    out value:dataArray.

Opening/Using Smalltalk/X Applications, Dialogs and Components[Bearbeiten]

Any existing Smalltalk/X utility (both the one's which are provided with the base system, and even those coming from additional loaded Smalltalk code) can be called and used. For example, the following uses the builtin file dialog to ask for a directory.

JavaScript[Bearbeiten]
execute() {
    var answer;

    answer = Dialog.requestDirectory("Please select an output folder:");
    if ( answer.notEmptyOrNil() ) {
        out.value( answer );
    }
}
Smalltalk[Bearbeiten]
execute
    |answer|

    answer := Dialog requestDirectory: 'Please select an output folder:'.
    answer notEmptyOrNil ifTrue:[
        out value: answer
    ]

Loading Additional Smalltalk/X Code[Bearbeiten]

You can even create whole library or application packages, compile it as separate package, and load it dynamically to be used from within an elementary block. Assuming that a package named "myPackage.dll" (or "myPackage.so" under Unix/Linux) containing an application class named "MyApplication" has been created and is located in the expecco bin folder, the code can be loaded and executed with:

Smalltalk[Bearbeiten]
execute
    |answer|

    Smalltalk loadPackage:'myPackage'. "/ checks if already loaded, to only load the first time called
    MyApplication open.

To create your own applications or addons, download the free Smalltalk/X development IDE for development and package building.

Expecco and Smalltalk API Stability[Bearbeiten]

The Smalltalk/X class library has been around for more than 30 years now, and of course grew over time. We have always tried hard to keep the API backward compatible, in not removing existing methods, even when new methods appeared which provided a similar or superset functionality.

Thus, code written 30 years ago can still depend on the interface and it is still working unchanged.

We will continue to keep this backward compatibility in the future, to avoid breaking customer code, whenever possible (there were very few exceptions in the past, where obvious bugs had to be fixed, and customer code already depended on the wrong behavior).

That said, we must emphasize on that being only true for the ST/X class libraries themself, and any officially published expecco APIs from this document.

If you use internal expecco functionality (which you may find when browsing in the class browser), we cannot guarantee backward compatibility in future versions. If you feel a need for using such an interface, please consult eXept to either get help on a cleaner solution, and/or to get your used interface be marked as "stable EXPECCO_API".

Appendix[Bearbeiten]

Smalltalk Language Syntax (BNF)[Bearbeiten]

See also: Smalltalk Short Description , Smalltalk Basics

 ;; notice: an elementary Smalltalk Block's execute method is a unary method (no argument).

 method ::= selectorSpec body

 selectorSpec> ::= unarySelectorSpec
                   | binarySelectorSpec
                   | keywordSelectorSpec

 unarySelectorSpec ::= symbol

 binarySelectorSpec ::= binopSymbol identifier

 keywordSelectorSpec ::= ( keywordSymbol identifier )+

 body ::= [ "|" localVarList "|" ]  [statements]

 localVarList ::= ( identifier )+

 statements ::= statement
                | statement "."
                | statement "." statements

 statement ::= expression
               | returnStatement

 returnStatement ::= "^" expression

 expression ::= identifier ":=" expression
                | keywordExpression

 keywordExpression ::= binaryExpression
                       | binaryExpression ( keywordSymbolPart binaryExpression )+

 binaryExpression ::= unaryExpression
                      | unaryExpression ( binopSymbol unaryExpression )+

 unaryExpression ::= primary
                     | primary ( unarySymbol )+

 primary ::= identifier
             | literalConstant
             | braceArray
             | "(" expression ")"
             | block
             | "self"
             | "super"

 identifier ::= ( "_" | "a"-"z" | "A"-"Z")   ( "_" | "a"-"z" | "A"-"Z" | "0"-"9" )*

 literalConstant ::= integerConstant
                     | radixIntegerConstant
                     | floatConstant
                     | arrayConstant
                     | byteArrayConstant
                     | stringConstant
                     | characterConstant
                     | "true"
                     | "false"
                     | "nil"

 integerConstant ::= ( "0"-"9" )+

 radixIntegerConstant ::= base "r" baseDigits+

 base ::= integerConstant (valid are: 2-31)

 baseDigits := "0"-"9" | "A"-"Z" | "a"-"z" (valid are "0".."0"+base-1 and "a".."a"+base-10-1 and "A".."A"+base-10-1)

 floatConstant ::= [ "-" ] [ digits+ ] "." [ digits+ ] [ ("e" | "E" | "d" | "D") [ "-"] digits+ ]

 arrayConstant ::= "#(" literalConstant* ")"

 byteArrayConstant ::= "#[" integerConstant(0..255)* "]"

 stringConstant ::= " ' " character* " ' "

 characterConstant ::= "$" character

 braceArray ::= "{" [ expression | expression ( "." expression )+ ] "}"

 block ::= "[" [ blockArgList ] body "]”

 blockArgList ::= (identifier":" )*

 comment ::= " " " any-character* " " " | eolComment

 eolComment ::= " "/ " any-character-up-to-end-of-line

 unarySymbol ::= identifier

 binopSymbol ::= ( "+" | "-" | "*" | "/" | "\" | "," | "@" | "%" | "&" | "=" | "<" | ">" | "~" )+

 keywordSymbolPart ::= identifier":"

 keywordSymbol ::= keywordSymbolPart+

 symbolConstant ::= "#"unarySymbol
                    | "#"binarySymbol
                    | "#"keywordSymbol
                    | "#'"any-character*"'"

JavaScript Language Syntax (BNF)[Bearbeiten]

-- incomplete w.r.t. expression syntax -- to be added

 ;; notice: an elementary JavaScript Block's execute function must be a unary function (no argument).

 method ::= functionSpec functionBody

 functionSpec ::= identifier "("  [argList] ")"

 functionBody ::= "{" (statement)+ "}"

 statement ::= varDeclaration
                      | ifStatement
                      | whileStatement
                      | doStatement
                      | forStatement
                      | tryCatchStatement
                      | switchStatement
                      | returnStatement
                      | "{" (statement)+ "}"
                      | expression

 varDeclaration ::= "var" identifier ( ","  identifier)* ";"

 ifStatement ::= "if" "(" expression ")" statement
                         [ "else" statement ]

 whileStatement ::= "while" "(" expression ")" statement
 
 doStatement ::= "do" statement "while" "(" expression ")"  ";"

 forStatement ::= "for" "(" [expression] ";" [expression] ";" [expression] ")" statement

 tryCatchStatement ::= "try" statement (catchClause | finallyClause | catchFinallyClause)

 catchClause ::= "catch"  "(" identifier [identifier] ")" statement

 finallyClause ::= "finally"  statement

 catchFinallyClause ::= catchClause finallyClause

 switchStatement ::= "switch" "(" expression ")" "{" ("case constantExpression ":" statement)+ [ "default:" statement+ "}"

 returnStatement ::= "return" expression [ "from" identifier ]

 expression ::= term ( ("+" | "-") term)*

 term ::= factor ( ("*" | "/" | "%" ) factor)*

 factor ::= "typeof" "(" expression ")"
                | "!" factor
                | "-" factor
                | "++" factor
                | "--" factor
                | primary "--"
                | primary "++"


 primary ::= identifier
                   | literalConstant
                   | "(" expression ")"
                   | block
                   | "this"
                   | "super"
                   | "new" classIdentifier [ "(" constant ")" ]
                   | functionDefinition

 functionDefinition ::= "function" [functionName] "("  [argList] ")" functionBody

 identifier ::= ( "_" | "a"-"z" | "A"-"Z")   ( "_" | "a"-"z" | "A"-"Z" | "0"-"9" )* 

 literalConstant ::= integerConstant
                              | radixIntegerConstant
                              | floatConstant
                              | arrayConstant
                              | byteArrayConstant
                              | stringConstant
                              | characterConstant
                              | "true"
                              | "false"
                              | "null"

 integerConstant ::= ( "0"-"9" )+

 radixIntegerConstant ::= hexIntegerConstant | octalIntegerConstant | binaryIntegerConstant

 hexIntegerConstant ::= "0x"hexDigit+

 hexDigit := "0"-"9" | "A"-"F" | "a"-"f"

 octalIntegerConstant ::= "0"octalDigit+

 octalDigit := "0"-"7"

 binaryIntegerConstant ::= "0b"binaryDigit+

 binaryDigit := "0" | "1"

 floatConstant ::= [ "-" ] [ digits+ ] "." [ digits+ ] [ ("e" | "E" | "d" | "D") [ "-"] digits+ ]

 arrayConstant ::= "[" [literalConstant ("," literalConstant)*] "]"

 stringConstant ::= " ' " character* " ' " | " " " character* " " "

 characterConstant ::= "$" character

 comment ::= "/*" any-character* " */ " | eolComment

 eolComment ::= " // " any-character-up-to-end-of-line

Back to Online_Documentation#Code_API_Overview Online Documentation



Copyright © 2014-2024 eXept Software AG