Fork me on GitHub
Follow @essenceworks

What is The Eldhelm Platform?

The Eldhelm Platform is an open source, production-ready, application server written in Perl 5 (Why?). It is mainly tailored for real time applications (like games), light web hosting, rapid development and fast deployment.

It is derived from the fantasy card collecting game Battlegrounds of Eldhelm where it has proven its robustness and flexibility.

It is designed with non-blocking IO and multithreaded, task-driven architecture in its core. This makes it suitable for applicatications demanding signifficant load and concurrency.

It's task driven architecture makes it useful also for various non-gaming, realtime, online applications.

Currenlty it is focused on front-end applications developed with Adobe Flash and AIR, so it provides an Action Script 3 framework.

And of course, it hosts this site ... and it features NO syntax sugar, no syntax honey, etc. because, come on, Perl is sweet enough, right! :)

What does it feature?

  • A socket server:
    • sessions
    • authentication
    • latency detection
    • timeouts
    • message delivery detection
    • SSL
    • extendable
  • A basic http server:
    • light http 1.0
    • cookies
    • virtual hosting
    • url rewriting
    • https
  • Task sheduler
  • MVC framework
  • Publish-subscribe framework
  • A.I. framework
  • Testing framework
  • Localization framework
  • Documentation framework

Read more ...

How to get started?

Get the source code from Github:

git clone 'https://github.com/wastedabuser/eldhelm-platform' 'myproject/platform'

Create a start-up script:

myproject/start.pl
Enter the following source:
use strict;
use lib 'platform/lib';
use Eldhelm::Server::Main;

Eldhelm::Server::Main->new(
	configPath => 'platform/quickstart-config.pl'
)->start;

Then just run it:

perl start.pl

Not sure about Perl - read this.
Want more examples? Continue reading bellow!

Tutorials

Basic configuration

To get more out of your server you should configure it!

To do this you should create a pl file with your configuration. You should call it config.pl and place it near your start-up script. This way you can omit the configPath attribute of the server constructor call, like this:

use strict;
use lib 'platform/lib';
use Eldhelm::Server::Main;

Eldhelm::Server::Main->new->start;

Here is an example of a very simple configuration:

{
	server => {
		name   => 'eldhelm',
		host   => '127.0.0.1',
		port   => 80,
		logger => {
			logs => {
				access  => ['stdout'],
				general => ['stdout'],
				debug   => ['stdout'],
				error   => ['stderr'],
			},
		},
		acceptProtocols => ['Http'],
		http            => { directoryIndex => 'controller:index:mymethod' },
	},
}

The server is configured to accept http requests on localhost and all logs are redirected to the standard outputs.

There is an example configuration file distributed with the source code where you can see pretty much all available options.

Contents | Top

Let's do a Hello World!

The line in the configuration

http => { directoryIndex => "controller:index:mymethod" },
makes the server route requests with no url to the handlig controller and method
Eldhelm::Application::Controller::Index->mymethod
So when you call mydomain.com or 127.0.0.1 it will handle the request appropriately.

So let's create this controller. Create the following folder-structure near your start-up script:

Eldhelm/Application/Controller/Index.pm
You end up with a root directory of your project like this:
drwxr-xr-x 4 essence netdev 4096 Oct 22 12:07 .
drwxr-xr-x 6 essence netdev 4096 Oct 22 12:05 ..
-rw-r--r-- 1 essence netdev  708 Oct 22 12:21 config.pl
drwxr-xr-x 3 essence netdev 4096 Oct 22 12:04 Eldhelm
drwxr-xr-x 5 essence netdev 4096 Oct 22 12:04 platform
-rw-r--r-- 1 essence netdev  104 Apr 24  2013 start.pl
The Index.pm file should containt:
package Eldhelm::Application::Controller::Index;

use strict;
use base 'Eldhelm::Basic::Controller';

sub new {
	my ($class, %args) = @_;
	my $self = $class->SUPER::new(%args);
	bless $self, $class;

	my @list = qw(mymethod);
	
	# this will make the method accessible
	$self->export(@list);
	
	# and this will make it accessible without a session 
	$self->public(@list);

	return $self;
}

sub mymethod {
	my ($self) = @_;

	# and of course
	$self->responseWrite('Hello World!');
}

1;

Development tip

Remember, when you do changes to a controller or any pm file you have to restart (or start :)) the server!

Contents | Top

Running the thing as a deamon

When you develop or run the server from your current session restarting is easy.

So you press ctrl^c on the terminal running the server and then:

perl start.pl

Yeah, but usually you want to run your server as a deamon (on a linux machine).

First you should create a user. I guess nobody on earth will recommend running a server as root.

sudo useradd -m -g netdev eldhelm
You have to add it to the netdev group (on Ubuntu) so your server can bind sockets.

Run the server as a deamon using:

sudo su eldhelm -c 'perl /home/eldhelm/start.pl 1>/home/eldhelm/logs/output.log 2>/home/eldhelm/logs/crash.log &'
It is always a good idea to capture the stdout and stderr when running as a deamon.

Also you should make your server write to files instead of the standard outputs. Change your configuration to:

logs => {
	access  => ['/home/eldhelm/logs/access.log'],
	error   => ['/home/eldhelm/logs/error.log'],
	# you probably do not want these two logs, so comment them!
	# this one gives verbose info about the server runtime
	# general => ['/home/eldhelm/logs/general.log'],
	# this one is for debugging
	# debug   => ['/home/eldhelm/logs/debug.log'],
},

Please note that usually you won't be able to bind on port 80 (on Ubuntu) using this user configuration. Low number ports need root privileges!
You have to configure your server on another port - lets say 4444 and route anything on 80 to it.
So change the port in your config.pl:

port => 4444,
Then start/restart the server!

Now you need something that redirects your connections on port 80 to 4444.
A neat trick is to use iptables. I guess you will need a firewall on a production server anyway...

sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to 4444

You can surely configure a router or another firewall system to do the same.

Stopping the server is done by sending a signal KILL or INT to the server, but first we need the process id.

ps aux | grep start.pl
If we imagine the process id is 12346 then we type:
sudo kill -INT 12346
The INT signal will make the server shut down gracefully while the KILL (-9) will stop it instantly!

Contents | Top

Basic as3 setup

First you need to get the as3 framework. Do it with a git command:

git clone 'https://github.com/wastedabuser/eldhelm-platform-as3' 'mygame/eldhelm-platform-as3'
You will most likely want do add it as a submodule, so instead do this:
git submodule add 'https://github.com/wastedabuser/eldhelm-platform-as3' 'mygame/eldhelm-platform-as3'
Then point your compiler (Flash Develop or whatever) to reference the src folder inside it!

Now configure your server to be able to communicate with the as3 framework.
Enable the needed protocols and add handling for the crossdomain.xml needed for flash communication. So edit your config.pl as follows:

acceptProtocols => [ 
	'Http', 
	'Json', 
	'Base64', 
	'Xml', 
	'System' 
],
xml  => { 
	'policy-file-request' => 'crossdomain.xml' 
},
The acceptProtocols property lists the message parser/handler classes that the server will use when accepting and sending messages. We are enabling 'Json', 'Base64', 'Xml', 'System' because the as3 framework will use them all to communicate with the server. In fact Json and Base64 are interchangeable, the first is good for developent as it is easy trace-able and the second is good for production as it is more robust and can accept even binary data. They both can not opperate propely without the System though and the as3 framework won't be able to open a sockets to the server without the Xml one.

Ok, so now restart the server.

In any gaming application the most common networking task is send async messages. Let's do it.

Setup a connection with the server first:

var connection:Connection = new Connection( { 
	host: "127.0.0.1",
	ports: [80],
	keepalive: 35
} );
AppManager.connection = connection;
connection.addEventListener(ConnectionEvent.ON_CONNECT, onConnect);
connection.connect();
Create a class which will handle a simple two way communication. We call it a controller class.
package eldhelm.service {
// your controller classes must be
// placed inside the eldhelm.service package
// for the controller routing to work

	import eldhelm.manager.AppManager;
	
	public class MyClientController {
	
		public static function sayHello():void {
			AppManager.connection.say({ 
				// call the controller MyServerController
				action: "myServerController:hello",
				data: {
					hello: "hello"
				}
			});
		}
		
		public static function receiveMessage(data:Object):void {
			trace(data.reply);
		}
		
	}
}
Now let's implement a handler for the connect event:
protected function onConnect(event:ConnectionEvent):void {
	// so right after we connect we send our hello message
	MyClientController.sayHello();
}

We need to handle our request on the server. To do so we implement MyServerController:

package Eldhelm::Application::Controller::MyServerController;

use strict;
use base 'Eldhelm::Basic::Controller';

sub new {
	my ($class, %args) = @_;
	my $self = $class->SUPER::new(%args);
	bless $self, $class;

	my @list = qw(hello);
	
	# this will make the method accessible
	$self->export(@list);
	
	# and this will make it accessible without a session 
	$self->public(@list);

	return $self;
}

sub hello {
	my ($self) = @_;
	my ($conn, $data) = ($self->connection, $self->data);
	
	# write our hello message 
	# into the debug log
	$self->worker->debug($data->{hello});
	
	# let's talk with the server
	$conn->say({
		action => 'myServerController:receiveMessage',
		data => {
			reply => 'hello there!'
		}
	});
	
	# and again to prove it is async :)
	$conn->say({
		action => 'myServerController:receiveMessage',
		data => {
			reply => 'listen to me client!'
		}
	});
}

1;
We restart the server!

And we should get some traces in the output panel of our Flash IDE, Flash Develop or whatever:

hello there!
listen to me client!

Contents | Top

Synced as3 calls

Async calls are great, but we also need synced calls to load data, populate the UI, etc.

In eldhelm platform such calls are called rpc, cute right :). So we do:

Rpc.execute({
	action: 'myRpcController:endpoint',
	params: {
		something: 'a message'
	},
	complete: onComplete
});
The complete callback is usually a class method:
protected function onComplete(event:RpcEvent):void {
	if (event.success) {
		trace('success');
	} else {
		trace('fail');
	}
	trace(event.data.databack);
}

Let's handle our call on the server:

package Eldhelm::Application::Controller::MyRpcController;

use strict;
use base 'Eldhelm::Basic::Controller';

sub new {
	my ($class, %args) = @_;
	my $self = $class->SUPER::new(%args);
	bless $self, $class;

	my @list = qw(endpoint);
	
	# this will make 
	# the method accessible
	$self->export(@list);
	
	# and this will make it 
	# accessible without a session 
	$self->public(@list);

	return $self;
}

sub endpoint {
	my ($self) = @_;
	my ($conn, $data) = (
		$self->connection, 
		$self->data
	);
	
	# write our parameter 
	# into the debug log
	$self->worker->debug(
		$data->{something}
	);
	
	$self->rpcRespond({ 
		databack => 'this will pass' 
	});
	# $self->rpcRespond({ 
	#	databack => 'this shall not!' 
	# }, 0);
	
	# be aware that 
	# execution continues!
	$self->worker->debug(
		'i will execute also'
	);
	
	# need to return when done!
	return;
	
	$self->worker->debug(
		'i will never execute'
	);
}

1;
Restart the server.

The calls are done via plain text socket and are very cheap. Many messages could be sent at once and they all can be synced.

var rpcPool:RpcPool = new RpcPool( {
	complete: onAllComplete
});
rpcPool.getRpc( {
	action: 'myRpcController:endpoint',
	params: { a: 1 },
	complete: function(event:RpcEvent):void {
		// do smething
	}
});
rpcPool.getRpc( {
	action: 'myRpcController:endpoint',
	params: { b: 2 },
	complete: function(event:RpcEvent):void {
		// do smething
	}
});
rpcPool.getRpc( {
	action: 'myRpcController:endpoint',
	params: { c: 3 },
	complete: function(event:RpcEvent):void {
		// do smething
	}
});
rpcPool.request();

// or this way
RpcPool.execute({
	rpcs: [
		{ action: 'myRpcController:endpoint' },
		{ action: 'myRpcController:endpoint' }
	],
	complete: onAllComplete
});

Contents | Top

Delayed actions

Usually when writing a game service you need something to happen in after a delay. You can easily do this using the delay method:

package Eldhelm::Application::Controller::DelayTest;

use strict;
use base 'Eldhelm::Basic::Controller';

sub new {
	my ($class, %args) = @_;
	my $self = $class->SUPER::new(%args);
	bless $self, $class;

	my @list = qw(mymethod);
	$self->export(@list);
	$self->public(@list);

	return $self;
}

sub mymethod {
	my ($self) = @_;

	# this method can be 
	# called by the client
	# or programmatically 
	# by other means
	
	# create a delayed call 
	# in 10 seconds
	my $id = $self->worker->delay(
		10, 
		'delayTest.delayHandler', 
		{
			parameter => 'value'
		}
	);
	
	# to cancel a delay
	$self->worker->cancelDelay(
		$id
	);
}

sub delayHandler {
	my ($self, $object, $params) = @_;
	
	# $object is the delay call context
	
	# do something useful here
	
	# for example write into the log
	$self->worker->debug(
		$params->{parameter}
	);
}

1;

Contents | Top

Scheduled tasks

Almost all game servers work with some sort of ticks, turns or at least some sort of time based execution. A common solution is to set up a timer - schedule a task. From the configuration do it this way:

{
	server => {
	
		# probably some other 
		# configuration here
		
		shedule => {
		
			action => [
			
				# execute on 5th of October 
				# at 11 o'clock
				[ "2015-10-05 11:00", 
					"myController:myHandler" ],
					
				# executed every day at 12 o'clock
				[ "12:00", "myController:myHandler" ],
				
				# executed every 6 hours
				[ "6h", "myController:myHandler" ],
				
				# executed every 5 minutes
				[ "5m", "myController:myHandler" ],
				
				# executed every 13 seconds
				[ "13s", "myController:myHandler" ],
				
			],
			
			# named scheduled tasks
			namedAction => {
			
				# the same as above
				# you can modify it 
				# at runtime though!
				myName => [ 
					"5m", 
					"myController:myHandler" 
				],
				
			}
			
		},
		
		# probably some more 
		# configuration here
		
	}
}
The myController:myHandler is a standard controller method.

You can programmatically create a new scheduled task or access a named one:

sub myMethod {
	my ($self) = @_;
	
	# Start ne scheduled task
	# to be executed every 10 minutes
	# the first call will be 
	# after 10 minutes
	$self->worker->setShedule(
		'myUniqueName', 
		'10m', 
		'myController:myHandler', 
		{ parameter => 'value' }
	);
	
	# and also find
	# existing one
	my $s = $self->worker->getShedule(
		'getShedule'
	);
	# $s is Eldhelm::Server::Shedule
	
	# to remove it
	$self->worker->removeShedule(
		'getShedule'
	);
	
}

Contents | Top

Session objects

When a client app is talking with the server it will most likely need some persistance. Some data has be stored for later use. Sessions are a special persistant objects which act as a bridge directly to a specific client.

sub myMethod {
	my ($self) = @_;
	
	my $sess = Eldhelm::Server::Session->new(
		customAttribute => 'value'
	);
	
	# You will probably want 
	# to handle these at some point
	# $sess->bind('connect',
	#	'myController:onSessionConnect');
	# $sess->bind('disconnect',
	#	'myController:onSessionDisconnect');
	# $sess->bind('dispose',
	#	'myController:onSessionDisconnect');
	# $sess->bind('reset',
	#	'myController:onSessionReset');
	
	# set up a connection
	$sess->setConnection(
		$self->connection
	);
	
	# and talk with the client
	$sess->say({
		action => 'myClass:myHandlerMethod',
		data => {
			parameter => 'value'
		}
	});
	
	# when you don't 
	# need it any more
	$sess->dispose;
	
}

The session object will persist until it is connected. You can check wheather it is connected by calling $sess->connected.

When the connection drops, the object will be auto-disposed after a specified amount time. This time is configured by an option in the configuration:

{ 
	server => { 
		session => { 
		
			# value in seconds
			timeout => 5 * 60,
			# set to 5 minutes
			
		} 
	} 
}

Usually you want to find a specific session and do something with it. Sessions can be located by their id or by custom attribute. To get the session id you do $sess->id. To get a session by id:

my $sess = 
	$self->worker->getPersist(
		$id
	);

To get sessions by property you do:

my ($sess) = 
	$self->worker->filterPersist({
		id => $id,
		connected => 1,
		# or any property
	});

This method unfortunately iterates through all persistant objects (not only sessions) which might be thousands. For a frequesntly searched attributes a good idea is to register them as lookup properties:

my $sess = 
	Eldhelm::Server::Session->new(
		
		playerId => 'player17'
		
		# and regsiter it as 
		# a lookup property
		lookupProperties => [
			'playerId'
		], 
		
	);

# some time later
# you find it this way
my ($s1, $s2) = 
	$self->worker->findAndFilterPersist(
		'playerId', 
		[ 
			'player17', 
			'player18',
			# and more ...
		], 
		
		# you can also add 
		# an optional filter
		{ connected => 1 }
	);
This will reduce the set and then apply filterPersist method on the result to further reduced the set.

Contents | Top

Persisting objects

We talked about Sessions. Sessions are just a special type of persistant objects. Sessions have the abbility to hold connection and trigger some connection related events. To create a general purpose persist object we do:

my $pObject = 
	Eldhelm::Basic::Persist->new;
To manipulate the object, for example get and set data we do:
# set a property
$pObject->set('myProp', 'value');

# get it back
my $value = 
	$pObject->get('myProp');

# set multiple
$pObject->setHash(
	a => 1,
	b => 2
);

# get multiple
my ($a, $b) = 
	$pObject->getList('a', 'b');

Please note that you should NEVER do this:

 # DON'T!!!
$pObject->{a} = 1;

# Instead do
$pObject->doFn(sub {
	$_[0]->{a} = 1;
	$_[0]->{b} = 1;
});
The reason is that the persistant objects are shared objects between threads. Using $pObject->{mtProp} is not thread safe!

To create a custom object you have to extend the base one Eldhelm::Basic::Persist this way:

package Eldhelm::Application::Persist::MyCustomObject1;

use strict;
use base 'Eldhelm::Basic::Persist';

sub new {
	my ($class, %args) = @_;
	
	# don't forget 
	# persistType property
	my $self = $class->SUPER::new(
		persistType => __PACKAGE__, 
		%args
	);
	bless $self, $class;

	# my custom construction

	return $self;
}

sub myCustomMethod {
	my ($self, $arg1, $arg2) = @_;
	
	# do something here
}

1;

You can also extend the Eldhelm::Basic::Persist::Process class. It will provide you the sessionContext method.

package Eldhelm::Application::Persist::MyCustomObject2;

use strict;
use base 'Eldhelm::Basic::Persist::Process';

sub new {
	my ($class, %args) = @_;
	my $self = $class->SUPER::new(
		persistType => __PACKAGE__, 
		%args
	);
	bless $self, $class;
	return $self;
}

sub myCustomMethod {
	my ($self, $arg1, $arg2) = @_;
	
	my $s = $self->sessionContext;
	# $s is Eldhelm::Server::Session
	# this is the session 
	# that invoked the current
	# function call remotely
	
	# do something here
	# with the $s here
}

1;

Contents | Top

Checking, testing and static analysis

The server restarts quiet fast - only a few seconds. Still it is really annoying when your code fails due to a syntax error.

There is a useful utility which does both testing and static analysis of your code. It is a perl script - test/check.pl provided with the server. You can run it without arguments to see the help:

perl check.pl
This will list all the options:
Usage:
        perl check.pl [source file|source directory|dotted notation] [Options]

Options:
        -h -help - see this help
        -all - checks all code
        -platform - check platform code only
        -util - check platform utils code only
        -product - check product code only
        -dump - show verbose output
        -syntax - check syntax
        -static - run static anlysis using Perl::Critic
        -unittest - run unit tests referenced in the source

Examples:
        perl check.pl -all -syntax -static -unittest
        perl check.pl /home/me/myproject/Eldhelm/Application/Controller/Test.pm -syntax
        perl check.pl myNamespace.myController -syntax

The options all, platform, util, product will test parts or your entire project, file by file.

The options syntax, static, unittest will do the testing an the analysis of your code.

For example:

perl check.pl -platform -syntax

Syntax check 1 [../lib/Eldhelm/AI/BehaviourTree.pm] ... OK
Syntax check 2 [../lib/Eldhelm/AI/BehaviourTree/Action.pm] ... OK
Syntax check 3 [../lib/Eldhelm/AI/BehaviourTree/Composite.pm] ... OK
Syntax check 4 [../lib/Eldhelm/AI/BehaviourTree/Condition.pm] ... OK
Syntax check 5 [../lib/Eldhelm/AI/BehaviourTree/Decorator.pm] ... OK
Syntax check 6 [../lib/Eldhelm/AI/BehaviourTree/End.pm] ... OK
Syntax check 7 [../lib/Eldhelm/AI/BehaviourTree/Inverter.pm] ... OK
Syntax check 8 [../lib/Eldhelm/AI/BehaviourTree/Lookup.pm] ... OK
Syntax check 9 [../lib/Eldhelm/AI/BehaviourTree/Loop.pm] ... OK
Syntax check 10 [../lib/Eldhelm/AI/BehaviourTree/Node.pm] ... OK
Syntax check 11 [../lib/Eldhelm/AI/BehaviourTree/Parallel.pm] ... OK
Syntax check 12 [../lib/Eldhelm/AI/BehaviourTree/Selector.pm] ... OK
Syntax check 13 [../lib/Eldhelm/AI/BehaviourTree/Sequence.pm] ... OK
Syntax check 14 [../lib/Eldhelm/AI/BehaviourTree/Task.pm] ... OK
Syntax check 15 [../lib/Eldhelm/Application/Controller/QuickStart.pm] ... OK
Syntax check 16 [../lib/Eldhelm/Application/Controller/Helper/Form/Autocomplete.pm] ... OK
Syntax check 17 [../lib/Eldhelm/Application/Controller/Monitoring/Heartbeat.pm] ... OK
Syntax check 18 [../lib/Eldhelm/Basic/Controller.pm] ... OK
Syntax check 19 [../lib/Eldhelm/Basic/Model.pm] ... OK
Syntax check 20 [../lib/Eldhelm/Basic/Persist.pm] ... OK
Syntax check 21 [../lib/Eldhelm/Basic/Script.pm] ... OK
Syntax check 22 [../lib/Eldhelm/Basic/View.pm] ... OK
Syntax check 23 [../lib/Eldhelm/Basic/Model/AdvancedDb.pm] ... OK
Syntax check 24 [../lib/Eldhelm/Basic/Model/BasicDb.pm] ... OK
Syntax check 25 [../lib/Eldhelm/Basic/Model/BasicIteratable.pm] ... OK
Syntax check 26 [../lib/Eldhelm/Basic/Model/BasicRecord.pm] ... OK
Syntax check 27 [../lib/Eldhelm/Basic/Persist/Process.pm] ... OK
Syntax check 28 [../lib/Eldhelm/Basic/View/BasicPage.pm] ... OK
Syntax check 29 [../lib/Eldhelm/Database/Filter.pm] ... OK
Syntax check 30 [../lib/Eldhelm/Database/MySql.pm] ... OK
Syntax check 31 [../lib/Eldhelm/Database/Pool.pm] ... OK
Syntax check 32 [../lib/Eldhelm/Database/Template.pm] ... OK
Syntax check 33 [../lib/Eldhelm/Helper/Html/Form.pm] ... OK
Syntax check 34 [../lib/Eldhelm/Helper/Html/Node.pm] ... OK
Syntax check 35 [../lib/Eldhelm/Helper/Html/Table.pm] ... OK
Syntax check 36 [../lib/Eldhelm/Helper/Html/Form/Scaffold.pm] ... OK
Syntax check 37 [../lib/Eldhelm/Mail/Bulk.pm] ... OK
Syntax check 38 [../lib/Eldhelm/Mail/Reader.pm] ... FAILED
Syntax check 39 [../lib/Eldhelm/Mail/Sender.pm] ... OK
Syntax check 40 [../lib/Eldhelm/Mail/TLS.pm] ... OK
Syntax check 41 [../lib/Eldhelm/Pod/DocCompiler.pm] ... OK
Syntax check 42 [../lib/Eldhelm/Pod/Parser.pm] ... OK
Syntax check 43 [../lib/Eldhelm/Server/AbstractChild.pm] ... OK
Syntax check 44 [../lib/Eldhelm/Server/BaseObject.pm] ... OK
Syntax check 45 [../lib/Eldhelm/Server/Child.pm] ... OK
Syntax check 46 [../lib/Eldhelm/Server/Connection.pm] ... OK
Syntax check 47 [../lib/Eldhelm/Server/Executor.pm] ... OK
Syntax check 48 [../lib/Eldhelm/Server/Handler.pm] ... OK
Syntax check 49 [../lib/Eldhelm/Server/Logger.pm] ... OK
Syntax check 50 [../lib/Eldhelm/Server/Main.pm] ... OK
Syntax check 51 [../lib/Eldhelm/Server/Router.pm] ... OK
Syntax check 52 [../lib/Eldhelm/Server/Session.pm] ... OK
Syntax check 53 [../lib/Eldhelm/Server/Shedule.pm] ... OK
Syntax check 54 [../lib/Eldhelm/Server/Worker.pm] ... OK
Syntax check 55 [../lib/Eldhelm/Server/Handler/Base64.pm] ... OK
Syntax check 56 [../lib/Eldhelm/Server/Handler/Factory.pm] ... OK
Syntax check 57 [../lib/Eldhelm/Server/Handler/Http.pm] ... OK
Syntax check 58 [../lib/Eldhelm/Server/Handler/Json.pm] ... OK
Syntax check 59 [../lib/Eldhelm/Server/Handler/RoutingHandler.pm] ... OK
Syntax check 60 [../lib/Eldhelm/Server/Handler/System.pm] ... OK
Syntax check 61 [../lib/Eldhelm/Server/Handler/Xml.pm] ... OK
Syntax check 62 [../lib/Eldhelm/Server/Parser/Base64.pm] ... OK
Syntax check 63 [../lib/Eldhelm/Server/Parser/Json.pm] ... OK
Syntax check 64 [../lib/Eldhelm/Test/Fixture/ControllerTester.pm] ... OK
Syntax check 65 [../lib/Eldhelm/Test/Fixture/DbData.pm] ... OK
Syntax check 66 [../lib/Eldhelm/Test/Fixture/MainTester.pm] ... OK
Syntax check 67 [../lib/Eldhelm/Test/Fixture/TestBench.pm] ... OK
Syntax check 68 [../lib/Eldhelm/Test/Mock/Connection.pm] ... OK
Syntax check 69 [../lib/Eldhelm/Test/Mock/Model.pm] ... OK
Syntax check 70 [../lib/Eldhelm/Test/Mock/Router.pm] ... OK
Syntax check 71 [../lib/Eldhelm/Test/Mock/Session.pm] ... OK
Syntax check 72 [../lib/Eldhelm/Test/Mock/Socket.pm] ... OK
Syntax check 73 [../lib/Eldhelm/Test/Mock/Worker.pm] ... OK
Syntax check 74 [../lib/Eldhelm/Util/CommandLine.pm] ... OK
Syntax check 75 [../lib/Eldhelm/Util/Communication.pm] ... OK
Syntax check 76 [../lib/Eldhelm/Util/ExternalScript.pm] ... OK
Syntax check 77 [../lib/Eldhelm/Util/Factory.pm] ... OK
Syntax check 78 [../lib/Eldhelm/Util/FileCache.pm] ... OK
Syntax check 79 [../lib/Eldhelm/Util/FileSystem.pm] ... OK
Syntax check 80 [../lib/Eldhelm/Util/HttpStatus.pm] ... OK
Syntax check 81 [../lib/Eldhelm/Util/LangParser.pm] ... OK
Syntax check 82 [../lib/Eldhelm/Util/MachineInfo.pm] ... OK
Syntax check 83 [../lib/Eldhelm/Util/Math.pm] ... OK
Syntax check 84 [../lib/Eldhelm/Util/Mime.pm] ... OK
Syntax check 85 [../lib/Eldhelm/Util/PlainCommunication.pm] ... OK
Syntax check 86 [../lib/Eldhelm/Util/StringUtil.pm] ... OK
Syntax check 87 [../lib/Eldhelm/Util/Template.pm] ... OK
Syntax check 88 [../lib/Eldhelm/Util/Tool.pm] ... OK
Syntax check 89 [../lib/Eldhelm/Util/Url.pm] ... OK
Syntax check 90 [../lib/Eldhelm/Util/Version.pm] ... OK
Syntax check 91 [../lib/Perl/Critic/Policy/Eldhelm/ProhibitCallingBaseConstructorByClassName.pm] ... OK
Syntax check 92 [../lib/Perl/Critic/Policy/Eldhelm/ProhibitUsingBaseInsteadOfParent.pm] ... OK
=======================================================================================================================================
ERRORS=1;
=======================================================================================================================================
Failed 38 [../lib/Eldhelm/Mail/Reader.pm]
        Can't locate Email/MIME.pm in @INC (you may need to install the Email::MIME module) (@INC contains: ../lib ../../platform-utils
/lib ../../ D:/programs/strawberry-perl-5.18.4.1-64bit-portable/perl/site/lib D:/programs/strawberry-perl-5.18.4.1-64bit-portable/perl/
vendor/lib D:/programs/strawberry-perl-5.18.4.1-64bit-portable/perl/lib) at ../lib/Eldhelm/Mail/Reader.pm line 5.
        BEGIN failed--compilation aborted at ../lib/Eldhelm/Mail/Reader.pm line 5.

=======================================================================================================================================
OK=91/92; ERRORS=1; CHECKED=92;
=======================================================================================================================================

There are helper scripts (check.bat and check.sh) for convinience both on Linux and Windows.
You can setup your text editor or IDE to run them with a hotkey. They receive two arguments:

  1. The full path to the directory containing the check.pl file;
  2. The full path to your file (usually provided by the editor);
On Windows you do:
check.bat [path to the testdir] [a file to test]
Here is an example output on Windows:
perl check.pl "D:\projects\eldhelm-platform\server\platform\lib\Eldhelm\Util\Version.pm" -dump -syntax -static -unittest
.
Syntax check 1 [D:\projects\eldhelm-platform\server\platform\lib\Eldhelm\Util\Version.pm] ... OK
        D:\projects\eldhelm-platform\server\platform\lib\Eldhelm\Util\Version.pm syntax OK

Static analysis 1 [D:\projects\eldhelm-platform\server\platform\lib\Eldhelm\Util\Version.pm] ... VIOLATED
        9 violations found

Unit tests 1 [D:\projects\eldhelm-platform\server\platform\lib\Eldhelm\Util\Version.pm] ...
        Running the following tests:
        - 301_version_parsing.pl
                                                                                            OK
        # test 1 - one number
        # test 2 - multiple numbers
        # test 3 - version compare
        t/301_version_parsing.pl .. ok
        All tests successful.
        Files=1, Tests=16,  0 wallclock secs ( 0.03 usr +  0.05 sys =  0.08 CPU)
        Result: PASS

========================================================================================================================
ERRORS=1;
========================================================================================================================
Failed 1 [D:\projects\eldhelm-platform\server\platform\lib\Eldhelm\Util\Version.pm]
        Always unpack @_ first at line 5, column 1. See page 178 of PBP.
        Always unpack @_ first at line 12, column 1. See page 178 of PBP.
        Always unpack @_ first at line 19, column 1. See page 178 of PBP.
        Always unpack @_ first at line 26, column 1. See page 178 of PBP.
        Always unpack @_ first at line 33, column 1. See page 178 of PBP.
        Always unpack @_ first at line 42, column 1. See page 178 of PBP.
        Always unpack @_ first at line 59, column 1. See page 178 of PBP.
        Useless interpolation of literal string at line 65, column 12. See page 51 of PBP.
        Useless interpolation of literal string at line 66, column 18. See page 51 of PBP.

========================================================================================================================
OK=1/1; ERRORS=1; CHECKED=1;
========================================================================================================================
Press any key to continue . . .

The static anlysis is done via Perl::Critic - pre-configured to match the Eldhelm Platform conventions.

To use the unittest option properly you should hint check.pl which unit tests to run for the specified source code. The easiest way to do this is by embedding the hint into the source like this:

package Eldhelm::Application::Controller::Index;

use strict;
use base 'Eldhelm::Basic::Controller';

sub new {
	my ($class, %args) = @_;
	my $self = $class->SUPER::new(%args);
	bless $self, $class;

	my @list = qw(mymethod);
	$self->export(@list)
	$self->public(@list);

	return $self;
}

### UNIT TEST: mymethod_tester.pl ###

sub mymethod {
	my ($self) = @_;

	$self->responseWrite('Hello World!');
}

1;
The mymethod_tester.pl is a standard unit test script written with Test::More.

Contents | Top

Unit testing

Testing is a huge part of the software developent process. The unit testing in the Eldhelm Platform is done with the help of Test::More. The tests are written the standard way. Take the following example:

use strict;
use lib '../lib';
use lib '../../lib';
use Test::More 'no_plan';
use Eldhelm::Util::Version;

diag('test 1 - one number');
is(Eldhelm::Util::Version->parseVersion(1),    '0001');
is(Eldhelm::Util::Version->parseVersion('2'),  '0002');
is(Eldhelm::Util::Version->parseVersion('99'), '0099');

diag('test 2 - multiple numbers');
is(Eldhelm::Util::Version->parseVersion('1.2'),     '00010002');
is(Eldhelm::Util::Version->parseVersion('1.2.3'),   '000100020003');
is(Eldhelm::Util::Version->parseVersion('1.2.3.4'), '0001000200030004');
is(Eldhelm::Util::Version->parseVersion('1.2.3.4', 3), '000100020003');
is(Eldhelm::Util::Version->parseVersion('1',       3), '000100000000');

diag('test 3 - version compare');
is(Eldhelm::Util::Version->compare(1, 2), -1);
is(Eldhelm::Util::Version->compare(2, 1), 1);
is(Eldhelm::Util::Version->compare(1, 1), 0);

is(Eldhelm::Util::Version->compare('1.1', 1),     1);
is(Eldhelm::Util::Version->compare('1.1', '1.2'), -1);
is(Eldhelm::Util::Version->compare('2.1', '1.2'), 1);
is(Eldhelm::Util::Version->compare('1.2', '2.1'), -1);
is(Eldhelm::Util::Version->compare('1.1', '1.1'), 0);

A usefull utility to run multiple or all tests is the script test/runner.pl provided with the server. You can run it with no arguments to read the help:

Usage:
        perl runner.pl [unit test file|directory] [Options]

Options:
        -h -help - see this help
        -all - runs all avaialbale tests
        -platform - runs platform test only
        -product - runs product test only
        -dump - dumps the test results

The options all, platform, product will test parts or your entire project, file by file.

For example running the following command:

perl runner.pl -platform
Will get you this output:
	
	[ ... Hundreds of lines of dumped test data here ... ]
	
All tests successful.
Files=10, Tests=164,  9 wallclock secs ( 0.14 usr +  0.02 sys =  0.16 CPU)
Result: PASS

The platform itself is stable and tested, then more usefull command will be:

perl runner.pl -product
or
perl runner.pl -all
just to be sure.

Development tip

A useful trick is hook this script to your source control software pre-commit(in git terms) hook. This way you gurantee a working commit at the end of the day.

Deployment tip

You can tie this script to a deployment script and run it after your code is deployed on the production server (prior restarting it).
This way you gurantee that you never forget to install a perl module on the production machine for example or other similar mistake causing down time of your application while you catch the problem!

Contents | Top