|
Function literals are evaluated lazily and capture their context. This makes implementation of deferred functions feasible. For example:
type function CancelFunc();
testcase Example() runs on MTC {
// start fixtures
var cancel := SetupDatabase();
// do some testing
// handle graceful shutdown
cancel();
}
function SetupDatabase() return CancelFunc
{
var db := DatabaseComponent.create;
db.start(listen());
return function() {
db.stop; // Captures db and allows to call methods, locally
}
}
Another use-case is parametrizing callbacks:
module Service
{
type component Component { /* ... */ }
function Start(in ExitFunc ef := withSuccess) return Component
{
var c := Component.create;
c.start(mainLoop(ef));
return c;
}
private function mainLoop(ExitFunc ef) runs on Component {
p.receive(ProcessState:{started := ?});
log("Service is up and running");
var ExitCode ec;
p.receive(ProcessState:{exited := ?}) -> value ec;
f(ec);
}
type function ExitFunc(ExitCode ec) runs on Component;
type enumerated ExitCode {
SUCCESS(0),
ERROR(1..127),
SIGTERM(-15),
SIGKILL(-9),
}
group helpers
{
function withSuccess(in ExitCode ec) runs on Component {
if (ec != SUCCESS) {
setverdict(fail, "want=0, got=" & ec);
stop;
}
}
function withError(in ExitCode ec) runs on Component {
if (ec != ERROR) {
setverdict(fail, "want!=0, got=" & ec);
stop;
}
}
function withSignal(in ExitCode sig) return ExitFunc
{
return function(in ExitCode ec) runs on Component {
if (ec != sig) {
setverdict(fail, "want=" & sig, got=" & ec);
stop;
}
}
}
function withTimeout(in float duration) return ExitFunc
{
return function(in ExitCode ec) runs on Component {
// ...
}
}
}
module Example
{
testcase SunnyDay()
{
var service1 := Service.Start();
var service2 := Service.Start(Service.withError);
// do some API testing...
all components.done;
}
testcase ResilianceTest_ServiceCrashes()
{
var service1 := Service.Start(Service.withSignal(Service.SIGTERM));
var service2 := Service.Start(Service.withTimeout(60.0));
// do some API testing...
all components.done;
}
} |
|