In the next days I’ll reorganize existing tests to make it easier to merge them in Mojo’s master. My (broken) Mojo fork remains online.
There you can find my test verbosifications and tests
Many many thanks to my great mentor marcus! :)
You know the cool new oneliner module ojo? It’s cool, especially with Mojo::DOM:
$ perl -Mojo -le 'print g("heise.de")->dom->at("h1 a")->text'
Oracle kehrt OpenSolaris den Rücken
It’s also a server, if you want:
$ perl -Mojo -e 'a("/" => {text => "AWESOME!"})->start' daemon
Unfortunately, ojo isn’t tested very well, so I wrote tests for its server and client part.
But there’s one issue - the error handling of the client part. The return value of a g() or f() call is a Mojo::Message::Response object, which works great for most cases and makes your oneliners short. The big Mojo::Client returns Mojo::Transaction objects, which contain request and response objects. That’s important if there’s an error on the request side, for example an invalid host. If you call the error method of transaction objects, you’ll get the request error; but with ojo we only have the response object, which is empty in case of request errors.
Two possible solutions:
ojo return transaction objects, so user code can check its error method, which gets all request and response errors. Disadvantage: you’ll have to write ->res after almost all request function calls. In addition, its uncommon for oneliners to have an explicit error handling.ojo die on request errors, which is fine for oneliners. The Klingon sayIt’s better to die than to come back in failure
They may be right.
I spend some time with the client. Although I wrote some tests for it, his test suite needs more love in the future. But don’t worry, I’m still there. ;)
In some situations it could happen that a server creates an infinite redirect loop. User agents are supposed to detect them. Firefox for example displays a message after about 30 redirects, the curl of my mentor allows 50 redirects of this:
get '/redirect_endless' => sub {
my $self = shift;
$self->redirect_to('redirect_endless');
} => 'redirect_endless';
The Mojo::Client ATM doesn’t detect this at all, so this test fails:
my $max_endless_redirects = 500;
$client->max_redirects($max_endless_redirects);
$tx = $client->get('/redirect_endless');
my $counter = 0;
$counter++ while $tx = $tx->previous;
isnt($counter, $max_endless_redirects, 'endless redirect detected');
I’m not sure if this is a problem, since one needs to increase the client’s max_redirects value explicitely. But maybe it’s not too hard to detect if a redirect comes in the 30st time. In addition, it’s only a SHOULD.
Sometimes there’s a MUST in RFC 2616, like here:
All responses to the HEAD request method MUST NOT include a message-body […]. All 1xx (informational), 204 (no content), and 304 (not modified) responses MUST NOT include a message-body.
In Mojolicious it’s easy to write actions which respond with 204 or 304 statuus and a body. We think the framework user should not be allowed to break its RFC 2616 compliance, so I wrote tests for this situation.
As my mentor suggested, I looked around in Mojo’s cookies code. I found out that the cookie parsing is pretty good tested and seems stable, well done. The cookie stringification, however, is not, so no one noticed that the to_string method doesn’t quote. The following code
$cookie = Mojo::Cookie::Response->new;
$cookie->name('foo');
$cookie->value('"bar"; Path=/baz');
leads to this cookie:
Cookie: foo="bar"; Path=/baz; Version=1
which is not what we wanted. the parser works correctly and assigns bar to the value and baz to the path value. I think, the problem sits here and here, including the following lines. It’s not only important to find failing things but test the good ones too. You can find new cookie tests on my bulletproofing branch
I know that my commit history is broken, but ATM it’s first priority for me to write tests. To make it easy to pull them into Mojo later, I’ll clean up that mess with a new fork and more branches with small patches.
Maybe you remember my test idea for bad requests. The problem was the server script, which sat on my virtual server. If this machine shuts down, the test won’t work, which is bad. With a fork, this problem can be solved, in one process lives the server, the IO::Socket client in the other and they can communicate as if the server were elsewhere:
my $port = 42666;
my $pid = fork;
die "couldn't fork: $!" unless defined $pid;
# Server
if ($pid == 0) {
# Silence!
app->log->level('warn');
# Simple response
get '/' => {text => 'OK'};
# Start on defined port
app->start(daemon => "--listen=http://*:$port");
}
# Client
else {
# IO::Socket connect here
# tests here
# We're done: kill the server
kill 9 => $pid;
}
The only problem seems to be the port choice, which isn’t very variable. I think I should at least provide the possibility to choose the port via environment.
…
Dear people,
I somehow lost the right way. In the second (the creative) part, I had some serious trouble to get the right start. I wrote some tests, but they were either trivial or there were no good way to handle the situation right. One of my first tests:
my $c = Mojo::Client->singleton->app(app);
# GET a 404
my $tx = $c->get('/doesnotexist');
ok(!$tx->success, 'not successful');
is($tx->res->code, 404, 'right status');
like($tx->res->body, qr/not found/i, 'right content');
Cool, eh? My first try to get smarter tests was handling wrong Content-Length headers. There are two possibilities, too less content or too much:
get '/too_short' => sub {
my $self = shift;
$self->res->headers->content_length(42);
$self->render(text => '1234567');
};
get '/too_much' => sub {
my $self = shift;
$self->res->headers->content_length(2);
$self->render(text => '1234567');
};
my $c = Mojo::Client->singleton->app(app)->keep_alive_timeout(1);
# Wrong Content-Length header (too short)
my $tx = $c->get('/too_short');
my $cl = $tx->res->headers->content_length;
is($cl, 42, 'right Content-Length header');
is($tx->res->body, '1234567', 'right content');
# Wrong Content-Length header (too long)
$tx = $c->get('/too_much');
$cl = $tx->res->headers->content_length;
is($cl, 2, 'right Content-Length header');
is($tx->res->body, '12', 'shortened content');
OK. I couldn’t figure out why the first test gets the right results (after the keep alive timeout), but the second yields to a non-terminating test script. I tried a few days to debug this thing, but failed. It seems that there’s no good way to handle this things. Test no. 1, test no. 2.
My problem then was that I felt shame about not having any good tests and stopped communicating to my mentor and other Mojolicious guys for a long time. OK, I never really stopped working on better tests, but I widely missed the chance to get help from them. That’s completely my fault and I’m taking the full responsibility for the things I did. The GSoC program deadline is in two days and I might fail.
However, the time of not communicating is over, I talked to Marcus and Sebastian, which are both sad about how things evolved. I will present some of my better tests here, hoping they will help Mojolicious, whatever my GSoC project results will be:
GET http://mojolicio.us/ HTTP/1.1
Host: apache.org
When receiving a request which has an absolute URI in the start line, the host header must be ignored, as stated in RFC 2616, Section 5.2. Mojolicious behaves almost right, but the apache.org still sits in $req->url->base. The test on github
A typical redirect has the 30* status code and a Location header, which is the location the client should look for the requested ressource. This patch tests the behaviour of the client when it gets a relative URL or no Location header.
The chunked transfer encoding is a way to transfer data between server and client without knowing the message size in advance. The body is splitted in small chunks which have a number which informs about the chunk size:
HTTP/1.1 500 Internal Server Error
Content-Type: text/plain
Transfer-Encoding: chunked
4
abcd
9
abcdefghi
If that number is wrong, the message parser stops, but it doesn’t give any error messages: chunked request, chunked response
For HTTP requests, the request method (like GET, POST or TRACE, I didn’t even knew that one before!) is essential. There exist status codes for server responses on wrong request methods, which aren’t used by Mojo: 405 Method not allowed for a wrong method on that resource and 501 Method not implemented for unknown methods (like FOO). Their use is marked as SHOULD in the HTTP 1.1 RFC, so these tests may be useless: wrong method handling tests
This is some kind of a follow-up on the last topic and was a bit tricky for me. Most things can be tested with a Lite app and the built in tester client, but to check the handling of bad requests, the client is completely useless. It just doesn’t want to send bad requests (which is a feature, but not for me), so I needed to write my own … um … client with IO::Socket.
If the host (provided by start line or Host header) is not a valid host on that server, it needs to respond with a 400 Bad request status (RFC 2616, Section 5.2 again). Mojo responds with a 200 OK: test script draft
The Range header allows a client to request parts of a ressource by telling which range it wants to receive:
Range: bytes=42-99
In case of a wrong range (like with a ressource of byte length 17 in the example above), the server SHOULD respond with a 416 Requested Range Not Satisfiable status, which it doesn’t.
I also added tests to check the Content-Range response header in this situations, which works pretty good in Mojolicious. Only in the 416 case this tests fail.
There are more minor test cases in my bulletproofing branch, but they are mostly not that important.
To be continued.
I submitted my verbosifications of the Mojolicious test suite on time, so yesterday I got the message that I passed the midtern evaluation. :)
You can see all my verbosifications on github. Sebastion won’t use everything but at first I need to fulfil my proposal.
Now comes the second part, which is much more flexible and needs creativity. I also need more communication with Sebastion to find cases in which Mojo fails. Looking forward for a great second part of GSoC! :)
Hi there,
the second week of my gsoc project is over. I started with verbosifying some Mojo tests (I think the Client test messages are much more readable now)… You’ll find verbose Mojo tests in the verbose_tests branch of my mojo fork. Damn, that client class is very flexible and awesome! Last night, Sebastian implemented a CSS3 selector type DOM parser, look at the hack of the day:
print Mojo::Client->new
->get('http://digg.com')->success
->dom->at('h3 a')->text;
I also tried to understand the Mojolicious dispatch process in detail. While playfully writing a simple but flexible content management plugin for Mojolicious, I saw one issue not for the first time: if you have a bridge with a callback (which is the standard method to do things in Mojolicious::Lite) and want to add some standard Mojolicious controller/action routes to that bridge, you’re in trouble, because the callback is merged together with the controller and action fields in the captures hash (see MojoX::Routes::Match) and when dispatching (see MojoX::Dispatcher::Routes), the callback wins, so the dispatcher gets the bridge callback twice.
I found absolutely no way to patch this without breaking anything else. I wrote a test for this situation, but it’s also possible that no one will ever need this, except me. For now, the workaround is to undef the cp thing:
# "Remote" action without callback
get '/' => {
namespace => 'Some',
controller => 'controller',
action => 'action',
cb => undef,
};
Oh, and I learned two things:
Last week I spent my time reading Mojolicious code. The perl debugger definitely helps understanding how everything works and gives you hours/days of joy:
$ mojolicious generate lite_app mojolicious-lite.pl
$ perl -d mojolicious-lite.pl get '/foo'
While my knowledge of Mojolicious grows, it’s a bit hard for me to get a good start for writing better tests. It’s a bit fettering to not wanting to make mistakes. Oh, and by the way: my english still sucks.
Like all Mojolicious code (and the whole Mojolicious universe) my patches will live in a github repository: github.com/memowe/mojo. At the bottom of each page in this blog, you’ll find a link to it.
The Google Summer of Code (GSoC) has started! Yay! In the last minute I submitted my proposal which was accepted by the majority in The Perl Foundation. Thank you very much. Here’s the abstract:
Bulletproofing the Mojolicious unit and integration test suite
Mojolicious test suite is at the moment a nice mixture of unit and integration tests and does what it is expected to do. But its error messages aren’t very user friendly (if they exist) and there are many things that can go wrong in the web communication world. My goal is to close the leaks and to improve the whole test suite to be bulletproof and user friendly.
In the next few months I’ll document my journey in this blog. Sorry for my bad english, I learned some at school, but that was years ago. If you can’t understand it, it’s my fault. If you find a sentence that makes sense, it’s the excellent work of Ms. Mackenroth.
I really like bootylicious (one file markdown file-based blog engine on mojo steroids by vti), but my shared hoster doesn’t. Actually, I think he doesn’t love Mojolicious. And not perl. And not git. And not whoami(1). No problem, I have a virtual server, but … no good domain names, or what do you think about gsoc.netzverwaltung.info? The best domain for this blog (gsoc.memowe.de) is not available for my server because it sits on my shared hoster because of … mail … and … no time to change that. So for now, I hope tumblr will work.
I really like Mojolicious. I like the simplicity and elegance of sri’s code. I like the Simpsons quotes. I like the sinatraness of Mojolicious::Lite. And I like the community, which seems to be 95% russian for some reasons, if you look at the ML. I need to rewrite all of my webdesign shit to work with Mojolicious, now. But first, there’s something about Google.