Archive for March, 2009

h1

Lighting up the tunnErl Pt. 9 – The Gotchaz

March 13, 2009

Over the years I’ve become pretty good at picking up on errors, learning a new paradigm as well as a new language helps to introduce a lot more of these than usual so I’ve put some of the more common errors I have come accross to help others pick up on the errrors that had me baphled from one extent to another.

  • function_clause – generally means that the result was not matched for one reason or another, make sure you are retrieving the result you expected.
  • noproc – either the process is not started or the node cannot communicate with it, first try net_adm:ping(hostname). then check to see if the process is alive erlang:is_process_alive(Pid).
  • undef - Happen when a function in module does not exist, the module is not loaded or not exist at all. Maybe the function name has been misspelt.
  • ‘*.app not found’ – this typically means that the application can not be found or it is not included, check that it is part of you dependancies and that has been started properly.
  • error:{badfun,ok} – EUnit based error missing comma, usually because assert is returned in wrong place.
  • syntax error before: ‘end’ – have added an “,/;’ proceeding the end
  • none:error:internal error in beam_asm – Sinan based error, happens when code is out of sync, best solution is to kill sinserv and run sinan again.

As time goes by I’ll continue to add more, what this space. If anyone has come across a gotcha that i’ve missed drop me a line, i’ll be more than happy to add it to this list.

h1

Lighting up the tunnErl Pt.8 – Testing processes

March 13, 2009

Well after months of trying to get my head around running units around a process I think I have it sussed.

Simple unit tests
Well I’m sure you have seen these type of erlang tests before

-module(test_foo).
-include_lib("eunit/include/eunit.hrl").
foo_test_() ->
[?_assertEqual("foo",say("foo")),
?_assertEqual("bar",say("bar"))].

The above code basically asserts that the method say returns what we expect. Okay so now what if we want to setup a process, lets say chatterl:start() and test its functionality, how would we do that? well it took me a while to get my head around this part.

I have noticed that if any processes are left alive during a unit test, the process will still be active whilst the other tests run, so if for some reason you find previous test cases failing where they were initially passing, first check that you have killed all the necessary processes in all units.

Creating simple units was a walk in the park and nothing really different from the standard units I’ve created in various C clones. The doozy was creating units that focus on a specific process rather than a method or function.

Testing processes

chatterl_mid_man_basics_test_() ->
[{setup, fun() ->
chatterl:start() end,
fun(_) ->
chatterl:stop() end,
[{timeout, 5000,
fun() ->
?assertEqual(<<"Illegal content type!">>,
check_json(mochijson2:decode(chatterl_mid_man:user_list("text/json")))),
?assertEqual([],chatterl_serv:list_users()),
?assertEqual({struct,[{<<"clients">>,[]}]},
check_json(mochijson2:decode(chatterl_mid_man:user_list(["text/json"]))))
end}]}].

The above test is from chatterl’s test cases, the test starts chatterl on setup, waits 5000 msecs using a timeout & runs the units within the final fun/0. Once our tests have run we need to stop chatterl (which will drop all its connected processes). I’ve used a timeout because it seems the processes I am working with need some time to be dropped before the next test is started, I’ve noticed that if this wasn’t done I’d get all kinds of unrelated errors or complaining that the process is still alive.

Though this process seems cumbersome, especially as I’m used to a single setup/teardown method which does the same thing each time a unit is run, the above handles our tests pretty well.

For the extra curious, you can find test cases used for chatterl/libs/chatter/src/chatterl_test.erl @ github.

You can also checkout Kevin Smith’s code as he seems pretty up to scratch with his tests membox is a good place to start.

h1

Lighting up the tunnErl part 7 – Chatterl meets Nitrogen

March 13, 2009

Well it’s been a has been a while since I have posted a blog, mainly down to the amount of time I’ve been spending playing around with Chatterl & Nitrogen. I have come a way since first playing around with Erlang I decided a few months ago to make Chatterl my main personal project. I played around with BeepBeep around the same time as Nitrogen, for one reason or another I picked up Nitrogen quite quickly and decided to run with that as Chatterl’s prototype frontend.

I have to admit though I have never really been a fan of event driven frameworks, especially those that mask mark up language but after a day or two of messing around with Nitrogen I found it quite a pleasant experience.

The learning curve can be somewhat steep as the documentation is not as good as it could be, I also have issues with having the root directory starting with /web/, so the admin directory would be http://blah.com/web/admin, hopefully this will be changed in the near future. Besides those minor gripes Nitrogen is starting off as a nice framework.

Well I wish I had some time to run through a little tutorial, for the moment the best I can do is show you how I implemented some of the functionality need for Chatterl’s front-end. Below is a list of features I’ll be concentration on:

  • Main body
  • Validate Message
  • Client Panel
  • Message Panel
  • Groups Body
  • Users Panel

We’re working with Chatterl’s Web Interface API (CWIGA) which works on 127.0.0.1:9000, I prefer JSON so I’ve only implemented functionality to handle this. The implementation code is very similar to that I’ve written about previous so I wont waste bytes (see Twitterl), I’ll just say that we need to connect to CWIGA via HTTP.

For the curious, the code can be found the functionality needed to interact with Chatterl can be found in the file chatterl_cwiga_handler.erl. The actual calls are defined within Chatterl interface.

Now we have the basics for interacting with Chatterl over HTTP, lets have a look at how this all ties in with working with Nitrogen. I won’t go over how Nitrogen works as that part is pretty well documented in a few placed (Nitrogen, Jón Grétar) but I will explain the functionality I’ve implemented for chatterl_nitrogen.

Main body
body() ->
ChatBody = [ #panel { id=messageBodyPanel} ],
Left = [ #panel
{ id=leftPanel,body=[client_panel(),message_panel(),
[#panel { id=groupsPanel, body=[groups_body()]}],users_panel()] } ],
Right = [ #panel { id=rightPanel, body=[ChatBody] } ],
Container = #panel { id=containerPanel, body=[Left,Right] },
validate_message(),
wf:render(Container).

Ok now what the hell is all that??? Well it doesn’t look like it but this is how Nitrogen renders HTML, we structure our HTML using Nitrogen’s elements & actions which in turns renders our pages for us.

Firstly we define the containers needed to organise the chat window, Allowing me to have the chat box on the right, whilst having all the other features (message box, groups list, etc) on the left. As you may of already guessed the element #panel represents a div with XHTML, id’s act as you would expect & can be manipulated with CSS as usual.

Validate Message
validate_message() ->
wf:wire(messageTextBox, messageTextBox, #validate
{ validators=[
#is_required { text="You forgot to submit a message." },
#min_length { length=2, text="Title must be at least 2 characters long." }]}).

Here we use the method wf:wire which basically connects an element to another, so a input box could be ‘wired’ to a submit button. In this case we want to make sure that we validation only when the messageTextBox has data, validate it. The next two lines do exactly that, firstly #is_required makes sure that we actually have a value, whilst #min_length gives a validation error if it has not been adhered to. The response are triggered by AJAX using JQuery, so as soon as an event is caught it is handled asynchronously.

Client Panel
client_panel() ->
[#panel { id=clientPanel, body=[get_client_panel()]}].

This panel is pretty straight forward, it simply defines a DIV elements which calls get_client_panel/0 which in turn determine what actions are needed. This method will be explained better shortly.

Message Panel
message_panel() ->
[#panel { id=messagePanel, body=[
#label { id=messageLabel, text="Message:" },
#textbox { id=messageTextBox, text="", style="width: 100px;", postback=group_message}
]}].

This is the the DIV element that is used by the user to send messages to chatterl. We have a couple of things we have encountered yet, our postback, which will be explained shortly & two elements (#label & #textbox). Pretty straight forward right. What about this postback thingie? Well postbacks are in fact our AJAX calls, allowing the user to interact with the systems backend seemlessly. We’ll see how these are handled in the Events section.

Groups Body
groups_body() ->
SelectedGroup = wf:q(groupsDropDown),
#panel { class=groupsPanel, body=[
#h1 { text="Groups" },
#label { text="Select a group to join:" },
#dropdown { id=groupsDropDown, value=SelectedGroup,
options = [ #option { value=Group, text=Group }
|| Group <- chatterl_interface:get_groups()],
postback=join_group} ] }.

Here we have an panel which allows users to select a group to join, we again use a postback to handle our AJAX call & use the chatterl_interface module to retrieve a list of groups from Chatterl.

Users Panel
users_panel() ->
SelectedUser = wf:q(usersDropDown),
[#panel { id=usersPanel, body=[
#label { id=usersLabel, text="Select a user:" },
#dropdown { id=usersDropDown, value=SelectedUser,
options = [ #option { value=User, text=User }
|| User <- chatterl_interface:get_users()] } ]}].

This should be pretty self explanatory as it is more aless the same functionality as groups_body/0.

Get client Panel
get_client_panel() ->
case wf:session(client) of
undefined ->
wf:wire(groupsPanel, #hide {}),
wf:wire(messagePanel, #hide {}),
client_login_panel();
Client ->
wf:comet(fun() -> update_status("messages",1000) end),
wf:wire(groupsPanel, #show {}),
wf:wire(messagePanel, #show {}),
client_logout_panel(Client)
end.

Well this method basically checks to see if a user is connected if they are then we display the messagePanel, groupsPanel along with groupsJoinedPanel. We allow the messages panel to update, giving the user the most up to date messages from Chatterl. We lastly set the client_panel to client_logout_panel/1, which we’ll have a look at shortly, this method basically allows the user to disconnect once they have connected successfully to Chatterl.

Client login Panel
client_login_panel() ->
#panel { id=loginPanel,
body=[
#label { text="Client name:" },
#textbox { id=userNameTextBox, text="", style="width: 100px;", html_encode=true, postback=connect }]}.

Client logout panel
client_logout_panel(Client) ->
#panel { id=logoutPanel,
body=[
#inplace_textbox { id=userNameTextBox, text=Client, html_encode=true, start_mode=view },
#button { id=theButton, text="Disconnect", postback=disconnect }]}.

This method is pretty much the same the the client_login_panel/0, with 1 small change, instead of connecting to Chatterl we want to disconnect. The principle is still the same, create an event, passing the postback (disconnect).
which in turn disconnects the user from Chatterl.

Update status
update_status(Type,Timeout) ->
process_flag(trap_exit, true),
case Type of
undefined -> ok;
"messages" -> loop_messages(Timeout);
{"general",StatusType} -> loop_status(Timeout,StatusType);
_ -> io:format("Unknown loop type: ~s",[Type])
end,
wf:comet_flush(),
update_status(Type,Timeout).

Events

Connect Event
event(connect) ->
[Client] = wf:q(userNameTextBox),
case chatterl_interface:connect(Client) of
{ok,Message} ->
wf:flash(Message),
wf:session(client,Client),
wf:update(clientPanel, #panel { body=[get_client_panel()] });
{error,Error} -> wf:wire(#alert { text=Error })
end.

Above is the actually postback method call event(connect) which utilises chatterl_interface:connect/1 allowing us to make a connection to Chatterl directly. If the request is successful we create a new session wf:session(client,Client) & update our clientPanel so that they user can logout as you would expect.We do this by using the wf:update/2 method that dynamically changes XHTML elements on the fly.

Disconnect Event
event(disconnect) ->
case chatterl_interface:disconnect(wf:session(client)) of
{ok,Message} ->
wf:flash(Message),
wf:session(client,undefined),
wf:session(selectedGroup,undefined),
wf:update(messageBodyPanel, #panel { body=[] }),
wf:update(clientPanel, #panel { body=[get_client_panel()] }); % Should redirect here.
{error,Error} ->
wf:wire(#alert { text=Error })
end.

Well there’s the disconnect event, nothing to dramatic here, simply unregister the client & selectedGroup cookies& update the page so that the client_panel is set back to its default.