Per Bothner
<per@bothner.com>
mailto:srfi minus 64 at srfi dot schemers dot org
.
See instructions
here to subscribe to the list. You can access previous messages via
the
archive of the mailing list.
この SRFI ではテストスイート (test suite) を書くための API を定義し、 Scheme API、ライブラリ、アプリケーション、および実装のテストの移植性を提供し、簡単化する。 テストスイートとは、1つのテストランナー (test-runner) のコンテキストの下で実行されるテストケース (test case) の集合である。 テストスイートの実行結果の報告や処理をカスタマイズできるようにするために、 この SRFI では、新しいテストランナーを書くための支援(★) も行う。
Scheme コミュニティはテストスイートを書くための標準を必要としている。 すべての SRFI や他のライブラリはテストスイートと共に提供されるべきである。 そのようなテストスイートは、 モジュールのような非標準の機能を必要とせず、 移植性がなければならない。 テストスイートの実装、つまり、「ランナー」(runner) は、移植性がある必要はないが、 移植性のある実装を書けるほうが望ましい。
Scheme で書かれた別のテストフレームワークも存在する。
たとえば SchemeUnit がそれである。
しかし、SchemeUnit は移植性がない。
それに少し冗長な側面がある。
SchemeUnit のテストがこの SRFI のフレームワークの下で実行できるように、
あるいは、その逆が実行できるように、
このフレームワークと SchemeUnit との間のブリッジがあると便利かも知れない。
また、
Java 標準の
JUnit API
への Scheme インターフェイスを提供する Scheme ラッパーも存在する。
このフレームワークを使用して書かれたテストを JUnit ランナーの下で
実行させることができるブリッジがあると便利かも知れない。
しかし、これらの機能はいずれもこの SRFI の仕様の一部ではない。
この API は暗黙的な動的状態を使用している。
たとえば暗黙のテストランナー
を使用している。
これにより、この API は簡便で明瞭なものになっている。
しかし、JUnit スタイルのフレームワークのように、
明示的にテスト オブジェクトを使用する方法に比べて、
少しエレガントではなく、構成的
(compositional)
であるかも知れない。
オブジェクト指向あるいは関数型のいずれの設計原則に従うことも主張しないが、
便利で拡張可能な設計にすることを望んでいる。
この SRFI では、 少しのマクロを追加するだけで、 Scheme ソースファイルからテストスイートに変換することができる。 一からファイルを書く必要はなく、したがって、再インデントする必要もない。
この SRFI で定義されるすべての名前は
test-
というプレフィックスで始まる。
関数のようなフォームはすべて構文として定義される。
それらは関数やマクロ、あるいは、組み込みの関数やマクロとして実装してもよい。
それらを構文として定義している理由は、構文を使えば、
部分式を評価せずに特定のテストをスキップすることができたり、
行番号を表示したりや例外をキャッチしたりと言った機能を追加できるからである。
この SRFI はある程度複雑な仕様ではあるが、 以下の最初のいくつかの節を読めば、 簡単なテストスイートを書くことができるようになるだろう。 カスタムのテストランナーを書くというような、 より高度な機能については、最後に記述する。
簡単な例から始めよう。 これは完全に自己完結したテストスイートである。
;; テストスイートを初期化して名前を付ける。 (test-begin "vec-test") (define v (make-vector 5 99)) ;; 式を評価したら真になることを検査する (test-assert (vector? v)) ;; 式が eqv? の意味で別の式に等価であることを検査する (test-eqv 99 (vector-ref v 2)) (vector-set! v 2 7) (test-eqv 7 (vector-ref v 2)) ;; テストスイートを終了し、テスト結果を報告する。 (test-end "vec-test")
このテストスイートは自身のソースファイル内に記述することができる。 他に必要なものはない。 トップレベル フォームを必要としないので、 インデントしなくても、 既存のプログラムやテストをラップすることができる。 個々のテストに名前を付けなくてもよいので (オプションで名前を付けることはできるが)、 新しいテストを追加することも容易である。
テストケースはテストランナーのコンテキストの下で実行される。
テストランナーはテスト結果を蓄積して報告するためのオブジェクトである。
この SRFI では、カスタムのテストランナーを作成して使用する方法を定義している。
しかし、この SRFI の実では、デフォルトのテストランナーも提供すべきである。
この SRFI では次のことを提案する (しかし要求はしない)。
上記のファイルをトップレベル環境にロードすると、
実装固有のデフォルトのテストランナーによってテストが実行され、
test-end
によって実装固有の方法でテスト結果が表示される。
次のフォームは、与えられた条件が真であるかどうかを検査するための、 プリミティブなテストケースである。 テストケースには名前を付けることができる。
(test-assert [test-name] expression)
このフォームは expression を評価する。
その結果が真であれば、検査は合格である。
結果が偽であれば、検査が失敗したことを報告する。
例外が発生した場合も検査は失敗する。
このことは、Scheme 実装が例外をキャッチする手段を備えていることを仮定している。
検査が失敗したときに、それがどのように報告されるかは、
テストランナーの環境に依存する。
test-name 引数にはテストケースの名前の文字列を指定する。
(例では test-name は文字列リテラルを記述しているが、
これは式であり、1度だけ評価される。)
エラーを報告するとき、この名前が使用される。
後で述べるように、テストをスキップするときにも、この名前が使用される。
カレントのテストランナーが存在しないときに
test-assert
を呼び出すことはエラーである。
test-assert
を直接使用するよりも、
以下のフォームを使うほうが便利である。
(test-eqv [test-name] expected test-expr)
これは次のフォームと等価である。
(test-assert [test-name] (eqv? expected test-expr))
同様に test-equal
や test-eq
は test-assert
に
equal?
や eq?
を組み合わせたフォームと等価である。
(test-equal [test-name] expected test-expr) (test-eq [test-name] expected test-expr)
簡単な例を挙げよう。
(define (mean x y) (/ (+ x y) 2.0)) (test-eqv 4 (mean 3 5))
不正確な実数の近似的な等値を検査するには、
test-approximate
を使用します。
(test-approximate [test-name] expected test-expr error)
これは次のフォームと等価である (ただし、上記のフォームでは引数が1度だけ評価される点が異なる)。
(test-assert [test-name] (and (>= test-expr (- expected error)) (<= test-expr (+ expected error))))
We need a way to specify that evaluation should fail. This verifies that errors are detected when required.
(test-error [[test-name] error-type] test-expr)
test-expr を評価するとエラーが発生することを検査する。 エラーの種類は error-type で指定する。
error-type を省略するか #t
を指定した場合は、
「何らかのエラーが発生する」ということを意味する。
たとえば、
(test-error #t (vector-ref '#(1 2) 9))
この SRFI では、
test-error
の形式については規定せず、実装固有の仕様とする
(あるいは将来の SRFI で規定されるものとする)。
しかし、どのような実装でも、
#t
をサポートしなければならない。
実装によっては
「SRFI-35: コンディション」
をサポートしているかも知れないが、これは
「SRFI-36: I/O コンディション」
で標準化されているのみであり、SRFI-36 はテストスイートには役に立たないだろう。
実装固有の例外型
をサポートしてもよい。
たとえば、Java ベースの実装では、
Java 例外クラスの名前を指定できるようにしてもよい。
;; Kawa 固有の例 (test-error <java.lang.IndexOutOfBoundsException> (vector-ref '#(1 2) 9))
例外をキャッチできない Scheme 処理系では、
test-error
フォームは無視すればよい。
テスト構文 (test syntax) はトリッキーである。 不正な構文に対してエラーが発生することを検査したい場合は特にそうである。 The following utility function can help:
(test-read-eval-string string)
この関数は (read
を呼び出すことで) string
を解析し、その結果を評価する。
その評価結果が test-read-eval-string
から返される。
read
を呼び出した後に読み取られていない文字が存在するならば、
エラーを発生させる。
たとえば:
(test-read-eval-string "(+ 3 4)")
を評価すると 7
になる
(test-read-eval-string "(+ 3 4")
はエラーを発生させる。
(test-read-eval-string "(+ 3 4) ")
はエラーを発生させる。
リストを読み取った後に余分なゴミ
(つまりスペース) が残るからである。
test-read-eval-string
を使用したテストを以下に示す。
(test-equal 7 (test-read-eval-string "(+ 3 4)")) (test-error (test-read-eval-string "(+ 3")) (test-equal #\newline (test-read-eval-string "#\\newline")) (test-error (test-read-eval-string "#\\newlin")) ;; srfi-62 が使用可能でなければ次の 2 つのテストをスキップする。 (test-skip (cond-expand (srfi-62 0) (else 2))) (test-equal 5 (test-read-eval-string "(+ 1 #;(* 2 3) 4)")) (test-equal '(x z) (test-read-string "(list 'x #;'y 'z)"))
テストグループ (test group) とは、テストケース、式、定義、を含むフォームの名前付きシーケンスである。 グループに入ると、 テストグループ名 (test group name) が設定される。 グループを抜けると、 直前のグループ名が復元される。 これは動的な (実行時の) 処理であり、 グループはこれ以外に効果を持たない。 テストグループとは非形式的なグループ化である。 Scheme 値でもなければ構文フォームでもない。
テストグループは、ネストされた内部のテストグループを含むことができる。 テストグループ パス (test group path) とは、現在アクティブな (現在入っている) テストグループの 名前のリストであり、最も古い (最も外側の) テストグループを最初に記述する。
(test-begin suite-name [count])
test-begin
は新しいテストグループに入る。
suite-name 引数が現在のテストグループ名になり、
テストグループ パスの最後に追加される。
移植性のあるテストスイートでは、
suite-name に文字列リテラルを指定すべきである。
式や他の種類のリテラルを指定した場合の効果は不定である。
論拠: シンボルを指定するほうが、いくつかの点において好ましいだろう。 しかし、我々は人間が読める名前を付けたいのだが、 標準 Scheme ではリテラル シンボルにスペースや大小文字を区別するテキストを 含める方法を提供していない。
オプションの count 引数には、 このグループによって実行されるテストケースの個数を指定する (ネストされたテストグループは、1つのテストケースとして数える)。 ここで指定された個数より余分なテストは実行されない。 ある種のエラーが発生したためにテストを実行することができない場合に対処するために、 この仕組みを使うことができる。
さらに、現在実行中のテストランナーが存在しない場合は、 実装に固有の方法で、テストランナーがインストールされる。
(test-end [suite-name])
test-end
は現在のテストグループを抜けます。
suite-name が現在のテストグループの名前に一致しない場合は、エラーになる。
さらに、対応する test-begin
が新しいテストランナーをインストールした場合は、
test-end
は実装固有の方法でテスト結果を報告した後、
そのテストランナーをアンインストールする。
(test-group suite-name decl-or-expr ...)
これは次のフォームと等価である。
(if (not (test-to-skip% suite-name)) (dynamic-wind (lambda () (test-begin suite-name)) (lambda () decl-or-expr ...) (lambda () (test-end suite-name))))
通常これは、名前付きテストグループの中で
decl-or-expr を実行することと等価である。
しかし、グループが test-skip
(後述)
に一致した場合は、グループ全体がスキップされる。
また、例外が発生した場合は、
test-end
が実行される。
(test-group-with-cleanup suite-name decl-or-expr ... cleanup-form)
各 decl-or-expr
フォームを
(<body>
のように) 順に実行し、
最後に cleanup-form
を実行する。
decl-or-expr
の1つが例外をレイズした場合でも、
cleanup-form
は必ず実行される
(ここでは Scheme 実装が例外をキャッチする方法を提供していることを仮定している)。
例:
(test-group-with-cleanup "test-file" (define f (open-output-file "log")) (do-a-bunch-of-tests f) (close-output-port f))
以下では、どのテストを実行すべきかを制御するための機能、つまり、 どのテストが失敗すると予想されるかを指定するための機能について述べる。
ある特定のテストだけを実行したいことがあったり、
ある特定のテストは失敗することが分かっていることがあるだろう。
テスト指定子 (test specifier)
とはテストランナーを引数として受け取りブール値を返す関数である。
テスト指定子は、テストを実行する前に呼び出され、
そのテストを実行するかどうかを決定する。
テスト指定子を簡単に指定するために、
手続きでない値をテスト指定子に自動変換する仕組みも提供する。
以下に述べる
count
と name
がそれである。
簡単な例:
(if some-condition (test-skip 2)) ;; 次の2つのテストをスキップする
(test-match-name name)
この構文により作成されるテスト指定子は、
現在のテスト名 (test-runner-test-name
で返される値)
が name 引数に equals?
の意味で等しいときに真を返す。
(test-match-nth n [count])
この構文は状態を記憶する述語を返す。
カウンタが内蔵されており、何回この述語が呼び出されたかを記憶している。
この述語は、n 回目に呼び出したときと、
それに引き続く (- count 1)
回の呼び出し時にのみ真を返す
(最初の呼び出しを 1
回目とする)。
count 引数のデフォルト値は 1
である。
(test-match-any specifier ...)
この構文が返すテスト指定子は specifier 引数のいずれかが真を返すときに真を返す。
各 specifier
は順に適用される。
そのため、前の specifier
が真を返すとしても、後の specifier
は副作用の効果を持つことになる。
(test-match-all specifier ...)
この構文が返す手続きはすべての specifier
引数が真を返すときに真を返す。
各 specifier
は順に適用される。
そのため、前の specifier
が偽を返すとしても、後の specifier
は副作用の効果を持つことになる。
count
(つまり整数)
(test-match-nth 1 count)
に対する省略形である。
name
(つまり文字列)
(test-match-name name)
に対する省略形である。
特定のテストをスキップしたいこともあるだろう。
(test-skip specifier)
test-skip
を評価すると、
テスト指定子 specifier
を現在アクティブなスキップ指定子 (skip-specifier)
の集合に追加する。
各テスト (あるいは test-group
)
を実行する前に、アクティブなスキップ指定子の集合を
アクティブなテストランナーに適用する。
そして、スキップ指定子の集合のいずれかが真を返す場合、
そのテストはスキップされる。
記法を簡便にするために、
specifier が文字列の場合は、
それは (test-match-name specifier)
への構文シュガーとなる。
例:
(test-skip "test-b") (test-assert "test-a") ;; 実行される (test-assert "test-b") ;; スキップされる
(test-begin "group1") (test-skip "test-a") (test-assert "test-a") ;; スキップされる (test-end "group1") ;; 上記の test-skip はアンドゥされる (test-assert "test-a") ;; 実行される
ときとして、テストケースが失敗することが予め予期できることがある。 しかし、それを修正する時間がないとしよう。 多分、ある種の機能がある種のプラットフォームでしか動作しないためだろう。 しかし、このテストケースはそのまま残しておき、 その不具合を覚えておきたいだろう。 そのようなテストケースは、失敗することが予期されることを記録しておきたい。
(test-expect-fail specifier)
Matching tests (where matching is defined as in test-skip
)
are expected to fail. This only affects test reporting,
not test execution. For example:
テスト指定子が真を返す場合、そのテストは失敗することが予期されるものとして扱われる
これは、テスト結果の報告に影響するのであって、
テストの実行に影響するのではないことに注意せよ。
(test-expect-fail 2) (test-eqv ...) ;; 失敗することが予期される (test-eqv ...) ;; 失敗することが予期される (test-eqv ...) ;; 合格することが予期される
テストランナー (test-runner) とはテストスイートを実行し、状態を管理するためのオブジェクトである。 テストグループ パス、スキップ指定子の集合、予期される失敗の指定子の集合は、 テストランナーの一部である。 テストランナーは通常、実行されたテストに関する統計を収集する機能も持つ。
(test-runner? value)
value
がテストランナーであるか判定する。
(test-runner-current)
(test-runner-current runner)
カレントのテストランナーを取得および設定する。
実装が (SRFI-39 のような)
パラメータ オブジェクトをサポートしている場合は、
test-runner-current
はパラメータ オブジェクトでもよい。
別の方法として、
test-runner-current
はマクロや★
Alternatively, test-runner-current
may be implemented
as a macro or function
that uses a fluid or thread-local variable, or a plain global variable.
(test-runner-get)
(test-runner-current)
と同じだが、
カレントのテストランナーが存在しない場合は、例外を投げる。
(test-runner-simple)
単純なテストランナーを作成する。
単純なテストランナーは、標準出力ポートにエラーとテスト結果の要約を表示する。
(test-runner-null)
テスト結果に対して何も行わないテストランナーを作成する。
カスタムのテストランナーを書く際に、
このテストランナーを拡張するために用意されている。
Implementations may provide other test-runners, perhaps
a (test-runner-gui)
.
(test-runner-create)
Create a new test-runner. Equivalent to
((test-runner-factory))
.
(test-runner-factory)
(test-runner-factory factory)
Get or set the current test-runner factory.
A factory is a zero-argument function that creates a new test-runner.
The default value is test-runner-simple
,
but implementations may provide a way to override the default.
As with test-runner-current
, this may be a parameter object,
or use a per-thread, fluid, or global variable.
(test-apply [runner] specifier ... procedure)
Calls procedure with no arguments using the specified
runner as the current test-runner.
If runner is omitted,
then (test-runner-current)
is used.
(If there is no current runner, one is created as in test-begin
.)
If one or more specifiers are listed then only tests matching
the specifiers are executed. A specifier has the same form
as one used for test-skip
. A test is executed
if it matches any of the specifiers in the
test-apply
and does not match any
active test-skip
specifiers.
(test-with-runner runner decl-or-expr ...)
Executes each decl-or-expr in order in a context
where the current test-runner is runner.
Running a test sets various status properties in the current test-runner. This can be examined by a custom test-runner, or (more rarely) in a test-suite.
Running a test may yield one of the following status symbols:
'pass
'fail
'xfail
'xpass
'skip
(test-result-kind [runner])
Return one of the above result codes from the most recent tests.
Returns #f
if no tests have been run yet.
If we've started on a new test, but don't have a result yet,
then the result kind is 'xfail
is the test is expected to fail,
'skip
is the test is supposed to be skipped,
or #f
otherwise.
(test-passed? [runner])
True if the value of (test-result-kind [runner])
is one of 'pass
or 'xpass
.
This is a convenient shorthand that might be useful
in a test suite to only run certain tests if the previous test passed.
A test runner also maintains a set of more detailed result properties
associated with the current or most recent test. (I.e. the properties of the
most recent test are available as long as a new test hasn't started.)
Each property has a name (a symbol) and a value (any value).
Some properties are standard or set by the implementation;
implementations can add more.
(test-result-ref runner 'pname [default])
Returns the property value associated with the pname property name.
If there is no value associated with 'pname
return default,
or #f
if default isn't specified.
(test-result-set! runner 'pname value)
Sets the property value associated with the pname
property name to value.
Usually implementation code should call this function, but it may be
useful for a custom test-runner to add extra properties.
(test-result-remove runner 'pname)
Remove the property with the name 'pname
.
(test-result-clear runner)
Remove all result properties.
The implementation automatically calls test-result-clear
at the start of a test-assert
and similar procedures.
(test-result-alist runner)
Returns an association list of the current result properties.
It is unspecified if the result shares state with the test-runner.
The result should not be modified, on the other hand the result
may be implicitly modified by future test-result-set!
or
test-result-remove
calls.
However, A test-result-clear
does not modify the returned
alist. Thus you can archive
result objects from previous runs.
The set of available result properties is implementation-specific. However, it is suggested that the following might be provided:
'result-kind
(test-result-kind runner)
is equivalent to:(test-result-ref runner 'result-kind)
'source-file
'source-line
test-assert
)
in test suite source code..'source-form
'expected-value
'expected-error
error-type
specified in a test-error
, if it meaningful and known.'actual-value
'actual-error
This section specifies how to write a test-runner. It can be ignored if you just want to write test-cases.
These call-back functions are methods
(in the object-oriented sense)
of a test-runner. A method test-runner-on-event
is called by the implementation when event happens.
To define (set) the callback function for event use the following expression.
(This is normally done when initializing a test-runner.)
(test-runner-on-event! runner event-function)
An event-function takes a test-runner argument, and possibly other arguments, depending on the event.
To extract (get) the callback function for event do this:
(test-runner-on-event runner)
To extract call the callback function for event use the following expression.
(This is normally done by the implementation core.)
((test-runner-on-event runner) runner other-args ...)
The following call-back hooks are available.
(test-runner-on-test-begin runner)
(test-runner-on-test-begin! runner on-test-begin-function)
(on-test-begin-function runner)
The on-test-begin-function is called at the start of an
individual testcase, before the test expression (and expected value) are
evaluated.
(test-runner-on-test-end runner)
(test-runner-on-test-end! runner on-test-end-function)
(on-test-end-function runner)
The on-test-end-function is called at the end of an
individual testcase, when the result of the test is available.
(test-runner-on-group-begin runner)
(test-runner-on-group-begin! runner on-group-begin-function)
(on-group-begin-function runner suite-name count)
The on-group-begin-function is called by a test-begin
,
including at the start of a test-group
.
The suite-name is a Scheme string,
and count is an integer or #f
.
(test-runner-on-group-end runner)
(test-runner-on-group-end! runner on-group-end-function)
(on-group-end-function runner)
The on-group-end-function is called by a test-end
,
including at the end of a test-group
.
(test-runner-on-bad-count runner)
(test-runner-on-bad-count! runner on-bad-count-function)
(on-bad-count-function runner actual-count expected-count)
Called from test-end
(before the on-group-end-function
is called) if an expected-count was specified by the matching
test-begin
and the expected-count does not match
the actual-count of tests actually executed or skipped.
(test-runner-on-base-end-name runner)
(test-runner-on-bad-end-name! runner on-bad-end-name-function)
(on-bad-end-name-function runner begin-name end-name)
Called from test-end
(before the on-group-end-function
is called) if a suite-name was specified, and it did not that the
name in the matching test-begin
.
(test-runner-on-final runner)
(test-runner-on-final! runner on-final-function)
(on-final-function runner)
The on-final-function takes one parameter (a test-runner)
and typically displays a summary (count) of the tests.
The on-final-function is called after called the
on-group-end-function correspondiong to the outermost
test-end
.
The default value is test-on-final-simple
which writes
to the standard output port the number of tests of the various kinds.
The default test-runner returned by test-runner-simple
uses the following call-back functions:
(test-on-test-begin-simple runner)
(test-on-test-end-simple runner)
(test-on-group-begin-simple runner suite-name count)
(test-on-group-end-simple runner)
(test-on-bad-count-simple runner actual-count expected-count)
(test-on-bad-end-name-simple runner begin-name end-name)
You can call those if you want to write a your own test-runner.
The following functions are for accessing the other components of a test-runner. They would normally only be used to write a new test-runner or a match-predicate.
(test-runner-pass-count runner)
Returns the number of tests that passed, and were expected to pass.
(test-runner-fail-count runner)
Returns the number of tests that failed, but were expected to pass.
(test-runner-xpass-count runner)
Returns the number of tests that passed, but were expected to fail.
(test-runner-xfail-count runner)
Returns the number of tests that failed, and were expected to pass.
(test-runner-skip-count runner)
Returns the number of tests or test groups that were skipped.
(test-runner-test-name runner)
Returns the name of the current test or test group, as a string.
During execution of test-begin
this is the name of the
test group; during the execution of an actual test, this is the name
of the test-case.
If no name was specified, the name is the empty string.
(test-runner-group-path runner)
A list of names of groups we're nested in, with the outermost group first.
(test-runner-group-stack runner)
A list of names of groups we're nested in, with the outermost group last.
(This is more efficient than test-runner-group-path
,
since it doesn't require any copying.)
(test-runner-aux-value runner)
(test-runner-aux-value! runner on-test)
Get or set the aux-value
field of a test-runner.
This field is not used by this API or the test-runner-simple
test-runner, but may be used by custom test-runners to store extra state.
(test-runner-reset runner)
Resets the state of the runner to its initial state.
This is an example of a simple custom test-runner. Loading this program before running a test-suite will install it as the default test runner.
(define (my-simple-runner filename) (let ((runner (test-runner-null)) (port (open-output-file filename)) (num-passed 0) (num-failed 0)) (test-runner-on-test! runner (lambda (runner result) (case (cdr (assq 'result-kind result)) ((pass xpass) (set! num-passed (+ num-passed 1))) ((fail xfail) (set! num-failed (+ num-failed 1))) (else #t)))) (test-runner-on-final! runner (lambda (runner) (format port "Passing tests: ~d.~%Failing tests: ~d.~%" num-passed num-failed) (close-output-port port))) runner)) (test-runner-factory (lambda () (my-simple-runner "/tmp/my-test.log")))
The test implementation uses cond-expand
(SRFI-0)
to select different code depending on certain SRFI names (srfi-9
,
srfi-34
, srfi-35
, srfi-39
),
or implementations (kawa
).
It should otherwise be portable to any R5RS implementation.
Here is srfi-25-test.scm
,
based converted from Jussi Piitulainen's
test.scm
for SRFI-25.
Of course we need a test suite for the testing framework itself.
This suite srfi-64-test.scm
was contributed by Donovan Kolbly
<donovan@rscheme.org>
.
Copyright (C) Per Bothner (2005, 2006)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Last modified: Sun Jan 28 13:40:18 MET 2007