diff options
-rw-r--r-- | Makefile.PL | 21 | ||||
-rw-r--r-- | config.yml | 17 | ||||
-rw-r--r-- | eg/post_hook.t | 18 | ||||
-rw-r--r-- | environments/development.yml | 4 | ||||
-rw-r--r-- | environments/production.yml | 6 | ||||
-rwxr-xr-x | jitterbug.pl | 5 | ||||
-rw-r--r-- | lib/jitterbug.pm | 24 | ||||
-rw-r--r-- | lib/jitterbug/Hook.pm | 42 | ||||
-rw-r--r-- | lib/jitterbug/Plugin/Redis.pm | 21 | ||||
-rw-r--r-- | lib/jitterbug/Project.pm | 57 | ||||
-rw-r--r-- | lib/jitterbug/WebService.pm | 34 | ||||
-rw-r--r-- | public/404.html | 17 | ||||
-rw-r--r-- | public/500.html | 17 | ||||
-rw-r--r-- | public/css/error.css | 70 | ||||
-rw-r--r-- | public/css/style.css | 34 | ||||
-rwxr-xr-x | public/dispatch.cgi | 3 | ||||
-rwxr-xr-x | public/dispatch.fcgi | 6 | ||||
-rw-r--r-- | public/favicon.ico | bin | 0 -> 1406 bytes | |||
-rw-r--r-- | scripts/builder.pl | 74 | ||||
-rwxr-xr-x | scripts/builder.sh | 21 | ||||
-rwxr-xr-x | scripts/capsule.sh | 26 | ||||
-rw-r--r-- | t/001_base.t | 5 | ||||
-rw-r--r-- | t/002_index_route.t | 11 | ||||
-rw-r--r-- | t/data/hook.json | 44 | ||||
-rw-r--r-- | t/data/test.yaml | 42 | ||||
-rw-r--r-- | views/index.tt | 8 | ||||
-rw-r--r-- | views/layouts/main.tt | 32 | ||||
-rw-r--r-- | views/project/index.tt | 24 |
28 files changed, 683 insertions, 0 deletions
diff --git a/Makefile.PL b/Makefile.PL new file mode 100644 index 0000000..ecc2f9f --- /dev/null +++ b/Makefile.PL @@ -0,0 +1,21 @@ +use strict; +use warnings; +use ExtUtils::MakeMaker; + +WriteMakefile( + NAME => 'jitterbug', + AUTHOR => q{YOUR NAME <youremail@example.com>}, + VERSION_FROM => 'lib/jitterbug.pm', + ABSTRACT => 'YOUR APPLICATION ABSTRACT', + ($ExtUtils::MakeMaker::VERSION >= 6.3002 + ? ('LICENSE'=> 'perl') + : ()), + PL_FILES => {}, + PREREQ_PM => { + 'Test::More' => 0, + 'YAML' => 0, + 'Dancer' => 1.1810, + }, + dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', }, + clean => { FILES => 'jitterbug-*' }, +); diff --git a/config.yml b/config.yml new file mode 100644 index 0000000..3cf2c20 --- /dev/null +++ b/config.yml @@ -0,0 +1,17 @@ +layout: "main" +logger: "file" +appname: "jitterbug" +serializer: "JSON" +redis: "127.0.0.1:6379" +template: "xslate" +engines: + xslate: + path: / + type: text + cache: 0 + +jitterbug: + reports: + dir: /tmp/jitterbug + build: + dir: /tmp/build diff --git a/eg/post_hook.t b/eg/post_hook.t new file mode 100644 index 0000000..f0d766e --- /dev/null +++ b/eg/post_hook.t @@ -0,0 +1,18 @@ +use strict; +use warnings; +use 5.010; +use LWP::UserAgent; +use HTTP::Request::Common; +use YAML::Syck; +use JSON; + +my $content = LoadFile('t/data/test.yaml'); +my $payload = JSON::encode_json($content); + +my $url = "http://localhost:5000/hook/"; + +my $req = POST $url, [payload => $payload]; + +my $ua = LWP::UserAgent->new(); +my $res = $ua->request($req); +$res->is_success ? say "ok" : say "not ok"; diff --git a/environments/development.yml b/environments/development.yml new file mode 100644 index 0000000..5bc5d3b --- /dev/null +++ b/environments/development.yml @@ -0,0 +1,4 @@ +log: "debug" +warnings: 1 +show_errors: 1 +auto_reload: 0 diff --git a/environments/production.yml b/environments/production.yml new file mode 100644 index 0000000..1a69035 --- /dev/null +++ b/environments/production.yml @@ -0,0 +1,6 @@ +log: "warning" +warnings: 0 +show_errors: 0 +route_cache: 1 +auto_reload: 0 + diff --git a/jitterbug.pl b/jitterbug.pl new file mode 100755 index 0000000..28b677d --- /dev/null +++ b/jitterbug.pl @@ -0,0 +1,5 @@ +#!/usr/bin/env perl +use Dancer; +use lib ('lib'); +load_app 'jitterbug'; +dance; diff --git a/lib/jitterbug.pm b/lib/jitterbug.pm new file mode 100644 index 0000000..47c186e --- /dev/null +++ b/lib/jitterbug.pm @@ -0,0 +1,24 @@ +package jitterbug; + +BEGIN { + use Dancer ':syntax'; + load_plugin 'jitterbug::Plugin::Redis'; +}; + +our $VERSION = '0.1'; + +load_app 'jitterbug::Hook', prefix => '/hook'; +load_app 'jitterbug::Project', prefix => '/project'; +load_app 'jitterbug::WebService', prefix => '/api'; + +before_template sub { + my $tokens = shift; + $tokens->{uri_base} = request->base; +}; + +get '/' => sub { + my @projects = redis->smembers(key_projects); + template 'index', {projects => \@projects}; +}; + +true; diff --git a/lib/jitterbug/Hook.pm b/lib/jitterbug/Hook.pm new file mode 100644 index 0000000..ae17b4b --- /dev/null +++ b/lib/jitterbug/Hook.pm @@ -0,0 +1,42 @@ +package jitterbug::Hook; + +BEGIN { + use Dancer ':syntax'; + load_plugin 'jitterbug::Plugin::Redis'; +}; + +setting serializer => 'JSON'; + +post '/' => sub { + my $hook = from_json(params->{payload}); + + my $repo = $hook->{repository}->{name}; + + my $repo_key = key_project($repo); + + if ( !redis->exists($repo_key) ) { + my $project = { + name => $repo, + url => $hook->{repository}->{url}, + description => $hook->{repository}->{description}, + owner => $hook->{repository}->{owner}, + }; + redis->set( $repo_key, to_json($project) ); + redis->sadd(key_projects, $repo); + } + + my $last_commit = pop @{ $hook->{commits} }; + + $last_commit->{repo} = $hook->{repository}->{url}; + $last_commit->{project} = $repo; + $last_commit->{compare} = $hook->{compare}; + + my $task_key = key_task_repo($repo); + redis->set($task_key, to_json($last_commit)); + + redis->sadd(key_tasks, $task_key); + + { updated => $repo }; +}; + +1; diff --git a/lib/jitterbug/Plugin/Redis.pm b/lib/jitterbug/Plugin/Redis.pm new file mode 100644 index 0000000..d2be756 --- /dev/null +++ b/lib/jitterbug/Plugin/Redis.pm @@ -0,0 +1,21 @@ +package jitterbug::Plugin::Redis; + +use Dancer::Config qw/setting/; +use Dancer::Plugin; +use Redis; + +register redis => sub { + Redis->new( server => setting('redis') ); +}; + +sub _key { join( ':', 'jitterbug', @_ ); } + +register key_projects => sub { _key('projects'); }; +register key_project => sub { _key('project', @_); }; +register key_builds_project => sub { _key('builds', @_); }; +register key_task_repo => sub { _key('tasks', @_); }; +register key_tasks => sub { _key('tasks'); }; + +register_plugin; + +1; diff --git a/lib/jitterbug/Project.pm b/lib/jitterbug/Project.pm new file mode 100644 index 0000000..904ebf9 --- /dev/null +++ b/lib/jitterbug/Project.pm @@ -0,0 +1,57 @@ +package jitterbug::Project; + +BEGIN { + use Dancer ':syntax'; + load_plugin 'jitterbug::Plugin::Redis'; +}; + +use DateTime; +use XML::Feed; + +get '/:project' => sub { + my $project = params->{project}; + + my $res = redis->get( key_project($project) ); + + send_error( "Project $project not found", 404 ) if !$res; + + my $desc = from_json($res); + + my @ids = redis->smembers( key_builds_project($project) ); + + my @builds; + foreach my $id (@ids) { + my $res = redis->get($id); + push @builds, from_json($res) if $res; + } + + template 'project/index', + { project => $project, builds => \@builds, %$desc }; +}; + +get '/:project/feed' => sub { + my $project = params->{project}; + + my @builds = reverse( redis->smembers( key_builds_project($project) ) ); + + my $feed = XML::Feed->new('Atom'); + $feed->title('builds for '.$project); + + foreach (splice(@builds, 0, 5)) { + my $res = redis->get($_); + next unless $res; + my $desc = from_json($res); + + foreach my $version (keys %{$desc->{version}}) { + my $entry = XML::Feed::Entry->new(); + $entry->title("build for ".$desc->{commit}.' on '.$version); + $entry->summary("Result: ".$desc->{version}->{$version}); + $feed->add_entry($entry); + } + } + + content_type('application/atom+xml'); + $feed->as_xml; +}; + +1; diff --git a/lib/jitterbug/WebService.pm b/lib/jitterbug/WebService.pm new file mode 100644 index 0000000..4f89be8 --- /dev/null +++ b/lib/jitterbug/WebService.pm @@ -0,0 +1,34 @@ +package jitterbug::WebService; + +BEGIN { + use Dancer ':syntax'; + load_plugin 'jitterbug::Plugin::Redis'; +} + +use File::Spec; + +set serializer => 'JSON'; + +get '/build/:project/:commit/:version' => sub { + my $project = params->{project}; + my $commit = params->{commit}; + my $version = params->{version}; + + my $conf = setting 'jitterbug'; + + my $file = File::Spec->catfile( $conf->{reports}->{dir}, + $project, $commit, $version . '.txt' ); + + if ( -f $file ) { + open my $fh, '<', $file; + my @content = <$fh>; + close $fh; + { + commit => $commit, + version => $version, + content => join( '', @content ), + }; + } +}; + +1; diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000..2edb296 --- /dev/null +++ b/public/404.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html lang="en-US"> +<head> +<title>Error 404</title> +<link rel="stylesheet" href="/css/error.css" /> +<meta charset=UTF-8" /> +</head> +<body> +<h1>Error 404</h1> +<div id="content"> +<h2>Page Not Found</h2><p>Sorry, this is the void.</p> +</div> +<footer> +Powered by <a href="http://perldancer.org/">Dancer</a> 1.1810 +</footer> +</body> +</html> \ No newline at end of file diff --git a/public/500.html b/public/500.html new file mode 100644 index 0000000..006b273 --- /dev/null +++ b/public/500.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html lang="en-US"> +<head> +<title>Error 500</title> +<link rel="stylesheet" href="/css/error.css" /> +<meta charset=UTF-8" /> +</head> +<body> +<h1>Error 500</h1> +<div id="content"> +<h2>Internal Server Error</h2><p>Wooops, something went wrong</p> +</div> +<footer> +Powered by <a href="http://perldancer.org/">Dancer</a> 1.1810 +</footer> +</body> +</html> \ No newline at end of file diff --git a/public/css/error.css b/public/css/error.css new file mode 100644 index 0000000..003ee2a --- /dev/null +++ b/public/css/error.css @@ -0,0 +1,70 @@ +body { + font-family: Lucida,sans-serif; +} + +h1 { + color: #AA0000; + border-bottom: 1px solid #444; +} + +h2 { color: #444; } + +pre { + font-family: "lucida console","monaco","andale mono","bitstream vera sans mono","consolas",monospace; + font-size: 12px; + border-left: 2px solid #777; + padding-left: 1em; +} + +footer { + font-size: 10px; +} + +span.key { + color: #449; + font-weight: bold; + width: 120px; + display: inline; +} + +span.value { + color: #494; +} + +/* these are for the message boxes */ + +pre.content { + background-color: #eee; + color: #000; + padding: 1em; + margin: 0; + border: 1px solid #aaa; + border-top: 0; + margin-bottom: 1em; +} + +div.title { + font-family: "lucida console","monaco","andale mono","bitstream vera sans mono","consolas",monospace; + font-size: 12px; + background-color: #aaa; + color: #444; + font-weight: bold; + padding: 3px; + padding-left: 10px; +} + +pre.content span.nu { + color: #889; + margin-right: 10px; +} + +pre.error { + background: #334; + color: #ccd; + padding: 1em; + border-top: 1px solid #000; + border-left: 1px solid #000; + border-right: 1px solid #eee; + border-bottom: 1px solid #eee; +} + diff --git a/public/css/style.css b/public/css/style.css new file mode 100644 index 0000000..b4ae038 --- /dev/null +++ b/public/css/style.css @@ -0,0 +1,34 @@ +body { + font-family: Lucida,sans-serif; + color: #eee; + background-color: #1f1b1a; +} + +#content { + color: #000; + background-color: #eee; + padding: 1em; + margin: 1em; + padding-top: 0.5em; +} + +a { + color: #a5ec02; +} + +h1 { + color: #a5ec02; +} + +footer { + border-top: 1px solid #aba29c; + margin-top: 2em; + padding-top: 1em; + font-size: 10px; + color: #ddd; +} + +pre { + font-family: \"lucida console\",\"monaco\",\"andale mono\",\"bitstream vera sans mono\",\"consolas\",monospace; +} + diff --git a/public/dispatch.cgi b/public/dispatch.cgi new file mode 100755 index 0000000..0d040ec --- /dev/null +++ b/public/dispatch.cgi @@ -0,0 +1,3 @@ +#!/usr/bin/env perl +use Plack::Runner; +Plack::Runner->run('/home/franck/code/projects/c/jitterbug/jitterbug.pl'); diff --git a/public/dispatch.fcgi b/public/dispatch.fcgi new file mode 100755 index 0000000..90e14c9 --- /dev/null +++ b/public/dispatch.fcgi @@ -0,0 +1,6 @@ +#!/usr/bin/env perl +use Plack::Handler::FCGI; + +my $app = do('/home/franck/code/projects/c/jitterbug/jitterbug.pl'); +my $server = Plack::Handler::FCGI->new(nproc => 5, detach => 1); +$server->run($app); diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..957f4b4 --- /dev/null +++ b/public/favicon.ico Binary files differdiff --git a/scripts/builder.pl b/scripts/builder.pl new file mode 100644 index 0000000..d65ace8 --- /dev/null +++ b/scripts/builder.pl @@ -0,0 +1,74 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +use Redis; +use JSON; +use YAML qw/LoadFile Dump/; +use File::Spec; +use File::Path qw/rmtree/; +use File::Basename; +use Git::Repository; + +$|++; + +my $conf = LoadFile('config.yml'); +my $redis = Redis->new(server => $conf->{redis}); +my $key = join(':', 'jitterbug', 'tasks'); + +while (1) { + my $task_key = $redis->spop($key); + if ($task_key) { + my $task = $redis->get($task_key); + my $desc = JSON::decode_json($task); + my $repo = $desc->{repo} . '.git'; + my $commit = delete $desc->{id}; + my $project = delete $desc->{project}; + + my $report_path = + File::Spec->catdir( $conf->{jitterbug}->{reports}->{dir}, + $project, $commit ); + + my $build_dir = + File::Spec->catdir( $conf->{jitterbug}->{build}->{dir}, $project ); + + # my $r = Git::Repository->create( clone => $repo => $build_dir ); + # $r->run('checkout', $commit); + + # my $res = `./scripts/capsule.sh $build_dir $report_path`; + + # rmtree($build_dir); + + $redis->del($task_key); + + my $build = { + project => $project, + repo => $repo, + commit => $commit, + status => 1, + time => time(), + %$desc, + }; + + my @versions = glob($report_path.'/*'); + foreach my $version (@versions) { + open my $fh, '<', $version; + my @lines = <$fh>; + my $result = pop @lines; + chomp $result; + $result =~ s/Result:\s//; + my ($name, ) = basename($version); + $name =~ s/\.txt//; + $build->{version}->{$name} = $result; + } + + my $build_key = join( ':', 'jitterbug', 'build', $commit ); + $redis->set( $build_key, JSON::encode_json($build) ); + + my $project_build = join( ':', 'jitterbug', 'builds', $project ); + $redis->sadd( $project_build, $build_key ); + warn "done, next\n"; + } + sleep 5; +} diff --git a/scripts/builder.sh b/scripts/builder.sh new file mode 100755 index 0000000..01eb92e --- /dev/null +++ b/scripts/builder.sh @@ -0,0 +1,21 @@ +#!/bin/sh -e + +gitrepo=$1 +project=$2 +commit=$3 + +ORIGIN=$(pwd) +BUILDDIR=$(mktemp -d) +LOGDIR="/tmp/jitterbug" +mkdir -p $LOGDIR +logfile="$LOGDIR/$project.$commit.txt" +cd $BUILDDIR +rm -rf $project +git clone $gitrepo $project +cd $project +git checkout $commit +perl Makefile.PL +make +make test 2>&1 > $logfile +cd .. +rm -rf $BUILDDIR diff --git a/scripts/capsule.sh b/scripts/capsule.sh new file mode 100755 index 0000000..946c38c --- /dev/null +++ b/scripts/capsule.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +set -e + +builddir=$1 +report_path=$2 + +mkdir -p $report_path + +cd $builddir + +source $HOME/perl5/perlbrew/etc/bashrc + +for perl in $HOME/perl5/perlbrew/perls/perl-5.12.* +do + theperl="$(basename $perl)" + perlbrew switch $theperl + hash -r + + perlversion=$(perl -v) + logfile="$report_path/$theperl.txt" + + perl Makefile.PL + make + HARNESS_VERBOSE=1 make test >> $logfile 2>&1 +done diff --git a/t/001_base.t b/t/001_base.t new file mode 100644 index 0000000..936ffff --- /dev/null +++ b/t/001_base.t @@ -0,0 +1,5 @@ +use Test::More tests => 1; +use strict; +use warnings; + +use_ok 'jitterbug'; diff --git a/t/002_index_route.t b/t/002_index_route.t new file mode 100644 index 0000000..4cab1ed --- /dev/null +++ b/t/002_index_route.t @@ -0,0 +1,11 @@ +use Test::More tests => 3; +use strict; +use warnings; + +# the order is important +use jitterbug; +use Dancer::Test; + +route_exists [GET => '/'], 'a route handler is defined for /'; +response_status_is ['GET' => '/'], 200, 'response status is 200 for /'; +response_content_like [GET => '/'], qr/Projects/, 'content looks OK for /'; diff --git a/t/data/hook.json b/t/data/hook.json new file mode 100644 index 0000000..2e55cb7 --- /dev/null +++ b/t/data/hook.json @@ -0,0 +1,44 @@ +{ + "payload" : { + "after" : "de8251ff97ee194a289832576287d6f8ad74e3d0", + "repository" : { + "owner" : { + "email" : "chris@ozmm.org", + "name" : "defunkt" + }, + "forks" : 2, + "watchers" : 5, + "private" : 1, + "name" : "github", + "url" : "http://github.com/sukria/Dancer", + "description" : "Youre lookin at it." + }, + "commits" : [ + { + "timestamp" : "2008-02-15T14:57:17-08:00", + "added" : [ + "filepath.rb" + ], + "url" : "http://github.com/defunkt/github/commit/41a212ee83ca127e3c8cf465891ab7216a705f59", + "author" : { + "email" : "chris@ozmm.org", + "name" : "Chris Wanstrath" + }, + "id" : "41a212ee83ca127e3c8cf465891ab7216a705f59", + "message" : "okay i give in" + }, + { + "timestamp" : "2008-02-15T14:36:34-08:00", + "url" : "http://github.com/sukria/Dancer/commit/8c3c1d6be0fa27ada4f03258ddea7683c967a925", + "author" : { + "email" : "chris@ozmm.org", + "name" : "Chris Wanstrath" + }, + "id" : "8c3c1d6be0fa27ada4f03258ddea7683c967a925", + "message" : "update pricing a tad" + } + ], + "ref" : "refs/heads/master", + "before" : "5aef35982fb2d34e9d9d4502f6ede1072793222d" + } +} \ No newline at end of file diff --git a/t/data/test.yaml b/t/data/test.yaml new file mode 100644 index 0000000..9636611 --- /dev/null +++ b/t/data/test.yaml @@ -0,0 +1,42 @@ +--- +after: 22116bcdb229c1514f3069aaaf9c87e9d5455409 +before: db8d02317fce7fa2d8f5b75273302eee7b266b87 +commits: + - + added: [] + + author: + email: franck@lumberjaph.net + name: franck cuny + id: 8c3c1d6be0fa27ada4f03258ddea7683c967a925 + message: test + modified: + - lib/Dancer.pm + removed: [] + + timestamp: 2010-09-23T08:04:42-07:00 + url: https://github.com/franckcuny/Dancer/commit/22116bcdb229c1514f3069aaaf9c87e9d5455409 +compare: https://github.com/franckcuny/Dancer/compare/db8d023...22116bc +forced: !!perl/scalar:JSON::XS::Boolean 0 +pusher: + email: franck@lumberjaph.net + name: franckcuny +ref: refs/heads/test +repository: + created_at: 2010/01/14 12:58:56 -0800 + description: Minimal-effort oriented web application framework for Perl (port of Ruby's Sinatra) + fork: !!perl/scalar:JSON::XS::Boolean 1 + forks: 0 + has_downloads: !!perl/scalar:JSON::XS::Boolean 1 + has_issues: !!perl/scalar:JSON::XS::Boolean 0 + has_wiki: !!perl/scalar:JSON::XS::Boolean 1 + homepage: '' + name: Dancer + open_issues: 0 + owner: + email: franck@lumberjaph.net + name: franckcuny + private: !!perl/scalar:JSON::XS::Boolean 1 + pushed_at: 2010/09/23 08:04:49 -0700 + url: https://github.com/sukria/Dancer + watchers: 1 diff --git a/views/index.tt b/views/index.tt new file mode 100644 index 0000000..7b44ac5 --- /dev/null +++ b/views/index.tt @@ -0,0 +1,8 @@ +<h2>Projects</h2> + +<ul> + : for $projects -> $project { + <li><a href="<: $uri_base :>project/<: $project :>"><: $project :></a></li> + : } +</ul> + diff --git a/views/layouts/main.tt b/views/layouts/main.tt new file mode 100644 index 0000000..bad65ab --- /dev/null +++ b/views/layouts/main.tt @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html lang="en-US"> + <head> + <title>jitterbug</title> + <link rel="stylesheet" href="/css/style.css" /> + <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script> + <meta charset="UTF-8" /> + <script type="text/javascript"> + $(document).ready(function() { + $('.commits a').click(function() { + var url = '/api/build/' + + $(this).parent("li").parent("ul").attr('id') + + '/' + + $(this).parent("li").attr('id') + + '/' + + $(this).text().toLowerCase(); + $.getJSON(url, null, function(data) { + $("#display_test_result").html("<pre>" + data.content + "<pre>") + }); + })}) + </script> + </head> + <body> + <h1>jitterbug</h1> + <div id="content"> + <: $content :> + </div> + <footer> + Powered by <a href="http://perldancer.org/">Dancer</a> 1.1810 + </footer> + </body> +</html> diff --git a/views/project/index.tt b/views/project/index.tt new file mode 100644 index 0000000..813907b --- /dev/null +++ b/views/project/index.tt @@ -0,0 +1,24 @@ +<h2><: $project :></h2> + +<ul> + <li>url: <: $url :></li> + <li>description: <: $description :></li> + <li><a href="<: $base_uri :>/project/<: $project :>/feed">feed</a></li> +</ul> + +<h3>Builds</h3> + +<ul class="commits" id="<: $project :>"> + :for $builds -> $build { + <li id="<: $build.commit :>"> + Commit <: $build.commit :> (<: $build.date.ymd :>)<br /> + author: <: $build.author.name :> - <a href="<: $build.compare :>">compare</a><br /> + :for $build.version.keys() -> $version { + <a href="#"><: $version :></a>=<: $build.version[$version] :> + :} + </li> + :} +</ul> + +<div id="display_test_result"> +</div> |