O novo serviço travis-ci.org pega projetos GitHub, percorre o código executando conjuntos de teste e notifica os proprietários se os testes falharem. Sua API permite o uso de scripts Perl para coletar dados históricos dos projetos, incluindo tabelas sobre quem quebrou o quê.
Por Mike Schilli
Projetos de código aberto trazem benefícios especiais relacionados a métodos ágeis de desenvolvimento, envolvendo a integração contínua (CI – Continuous Integration). Qualquer alteração de código no repositório público deve imediatamente acionar um conjunto de testes que são executados para chamar a atenção da comunidade de desenvolvimento de todas as versões criadas.
Substitua os cliques complicados
Se você acha o processo de criação de ambientes de testes contínuos com ferramentas como Jenkins (anteriormente chamada de “Hudson”) e informar todos os dados de configuração muito complicado, provavelmente vai gostar de um novo projeto chamado travis-ci.org no qual o desenvolvedor simplesmente faz o login, o que o leva para a home do projeto GitHub, onde será possível identificar repositórios do usuário e solicitar permissões (mais uma vez, você só precisa pressionar um botão) para manipular os testes posicionando-os na área administrativa.
Ao voltar para o Travis, o desenvolvedor encontrará uma lista de seus projetos GitHub com os atrativos botões On/Off (figura 1). Clicar no botão On permite acessar o servidor de CI para o projeto. Tudo que você precisa fazer é mover um arquivo YAML de três linhas para a raiz do projeto (figura 2) e, de agora em diante, o Travis vai lançar um conjunto de testes para cada projeto selecionado no GitHub (figura 3) e irá notificá-lo por e-mail se algo falhar. O serviço Travis introduziu recentemente o suporte para três versões do Perl (5.10, 5.12, 5.14), seguido por suporte para Java, JavaScript (com Node.js), Python, PHP e Ruby, bem como Clojure, Erlang, Groovy, Haskell e Scala, cujo suporte começou recentemente.
Simples é fácil
No mais simples de todos os casos, um arquivo YAML chamado .travis.yml (figura 2) só mostra a linguagem utilizada pelo projeto (linguagem Perl), e o Travis deriva convenções para execução de testes em linguagem específicas. Em Perl, isso é tipicamente o arquivo perl Makefile.PL; make test, mas também pode suportar os arquivos Build.PL mais modernos.
Se um projeto usa conjuntos de testes que não são padrão, o desenvolvedor precisa apenas definir os scripts necessários para chamá-los em .travis.yml. No caso de um projeto Perl, o serviço Travis não apenas executa um conjunto de testes, mas também fornece um ambiente limpo em um dos seus sistemas worker, pega os módulos do CPAN listados para os arquivos do projeto em pacotes compactados do espelho CPAN mais próximo, e os instala na ordem definida, antes de prosseguir com a execução do teste. Nada disso requer qualquer configuração; o serviço define os passos e as ações das convenções padrão definidos pela configuração language:perl.
O Travis mostra o progresso e a saída do conjunto de testes em uma janela de saída atualizada dinamicamente (figura 4) e marca a execução com um ponto verde na visão geral, se tudo correr bem. Se algo não der certo no ciclo atual ou o repositório se recuperar de uma falha anterior, o Travis envia ao desenvolvedor(es) uma breve mensagem de e-mail com informações; caso contrário, permanece mudo.
Immediate Smoketest
Um SmokeTest excelente para módulos CPAN já foi liberado; ele pega os pacotes tar do CPAN, descompacta-os e instala em vários ambientes de produção distintos, onde executa testes. Se ocorrerem erros, os smoketesters enviam os resultados para os endereços de e-mail cadastrados pelos desenvolvedores no CPAN. No entanto, como um engenheiro ágil, você provavelmente irá preferir executar testes unitários contínuos, após cada modificação do código para identificar erros em um estágio inicial, e não apenas após a liberação do arquivo tar no CPAN. Inicialmente, é bastante impressionante ver como logo após a emissão do comando git push no GitHub, a suíte de testes Travis começa a executar.
O GitHub oferece um mecanismo para que essa comunicação ocorra sem atrasos no repositório. Provedores como o Travis criam esses ganchos com código livre Ruby (figura 5) e os instalam no GitHub depois de uma revisão. A partir de alguns serviços disponíveis no GitHub, o proprietário do repositório de desenvolvimento pode permitir que um ou alguns membros executem o código do Travis sempre que um contribuinte do projeto altere algum código. O mecanismo do Travis ilustrado na figura 5 simplesmente chama uma URL em seu site para ativar o motor de testes.
Seguro como sua casa
O serviço Travis torna a vida muito mais fácil para o proprietário do repositório. Em vez de pedir aos colaboradores que instalem o mecanismo de testes eles mesmos, o proprietário do repositório fará fazendo isso em nome do usuário, usando OAuth para pedir privilégios administrativos para o repositório do usuário no GitHub (figura 6).
Embora a equipe do Travis não pretenda se proteger de eventuais ataques maliciosos, você nunca pode ter certeza sobre incidentes e acidentes futuros, e desenvolvedores conscientes sobre segurança podem querer revogar esses privilégios administrativos para o repositório logo após a instalação do mecanismo do serviço (figura 7). Isso não terá nenhum efeito sobre a execução do conjunto de testes.
Karma ruim
Graças a essa integração e abordagem inteligente, demoramos apenas cinco minutos para configurar o teste permanente de CI para um dos meus projetos Perl, depois de ler brevemente o guia de início rápido do serviço. Como o serviço Travis está ciente das convenções de linguagem padrão, os desenvolvedores não vão precisar adicionar uma única linha de código manualmente. Mas, se quiser ter algumas características adicionais que o Travis não possui, o serviço irá facilitar sua vida, fornecendo todos os resultados do teste sob a forma de um serviço web acessível através de URLs em REST compactadas (figuras 8 e 9). Os dados JSON retornados são adequados para uso avançado como programas Perl que desenham no módulo JSON do CPAN a análise dos dados e os investiga mais de perto. Por exemplo, se você quiser saber qual desenvolvedor tem mais frequentemente verificados no código determinado conjunto de testes que falharam e atribuir pontos de karma negativo a este desenvolvedor com base nisso, você pode rapidamente montar um roteiro baseado na listagem 1.
O script pega os resultados do teste do serviço Travis e determina quem foi o desenvolvedor responsável pela compilação verificada em cada código e para cada falha. O módulo CPAN JSON exporta a função from_json () na linha 9 e em seguida decodifica a string JSON enviado pelo servidor REST para lhe dar uma estrutura de dados Perl interna. Como a execução do script leva algum tempo para ser concluída, executar Log::log4perl em modo de depuração (linha 16) emite as ações atuais do script, como pegar as consultas REST na saída de erro padrão.
Do Travis para o Github
O serviço Travis só conhece o ID de commit de quem utiliza o hash SHA1 formatado em hexadecimal típico do sistema de controle de revisão Git. Para descobrir qual desenvolvedor postou o código no GitHub, a listagem 1 efetua consulta nos metadados do código usando o URL da API GitHub (linha 19). O resultado da consulta é outra string JSON que inclui o endereço de e-mail do desenvolvedor responsável. As duas APIs REST são muito semelhantes, o que provavelmente é resultado da implementação subjacente com Ruby.
Como o script só está interessado nos commits da semana anterior, ele ignora as entradas nos dados JSON com timestamps mais antigos. Os dados JSON armazenam a data no formato AAAA-MM-DDTHH::MM::SS, mas o formato parser DateTime::Format::strptime do CPAN o converte em um objeto DateTime para facilitar a comparação de datas posteriores. Para que isso aconteça, a linha 45 define um objeto do tipo DateTime com a data para exatamente uma semana atrás. Para descobrir se um commit possui mais do que uma semana de idade, a linha 66 só precisa comparar dois objetos DateTime usando o operador <.
Se o timestamp combinar com o período sob investigação, a linha 73 pega o ID do usuário e o utiliza para criar uma URL para a API do GitHub com livre acesso. A função get() exportada pelo CPAN LWP::Simple emite a solicitação HTTP. Isso devolve dados JSON, que permanecem disponíveis após a conversão em Perl. Abaixo da entrada do commit, você vai encontrar outro hash com outra estrutura de dados sob a entrada de dados do commiter. Esse hash finalmente contém um campo de e-mail com o endereço de e-mail do autor. A linha 94 empurra esse valor em um hash, o que cria uma tabela com contadores para endereços de e-mail dos committers que quebraram a compilação do código.
O laço do loop nas linhas 99-104 itera sobre os desenvolvedores em ordem alfabética, e a função de impressão no corpo do loop gera os pontos de karma negativo para cada desenvolvedor (figura 10). É sabido que os engenheiros de desenvolvimento do Facebook atribuem pontos de karma negativo para os desenvolvedores que não tenham feito a lição de casa e acompanham esses colegas com “karma ruim” como falcões no momento em que estes desenvolvedores realizem novos lançamentos críticos. Há rumores de que os engenheiros descontentes podem ser apaziguados por doações de bebidas alcoólicas caras, mas os reincidentes tenderão a encontrar comentários embaraçosos em suas revisões anuais.
Listagem 1: karma
001 #!/usr/local/bin/perl ‑w 002 ############################# 003 # karma ‑ Detect build 004 # breakers on travis‑ci.org 005 # Mike Schilli, 2012 006 # (m@perlmeister.com) 007 ############################# 008 use strict; 009 use JSON qw( from_json ); 010 use DateTime; 011 use 012 DateTime::Format::Strptime; 013 use Log::Log4perl qw(:easy); 014 use LWP::Simple qw( get ); 015 016 Log::Log4perl‑>easy_init( 017 $DEBUG); 018 019 my $github_api_url = 020 "https://api.github.com/" . 021 "repos"; 022 my $travis_api_url = 023 "http://travis‑ci.org"; 024 025 my ($repo) = @ARGV; 026 die "usage: $0 name/repo" 027 if !defined $repo; 028 029 my $build_json = get( 030 "$travis_api_url/" . 031 "$repo/builds.json" 032 ); 033 my $build_data = 034 from_json($build_json); 035 036 m y $f = 037 DateTime::Format::Strptime 038 ‑>new( 039 pattern => 040 "%Y‑%m‑%dT%H:%M:%SZ", 041 time_zone => 042 "America/Los_Angeles", 043 ); 044 045 m y $last_week_dt = 046 DateTime‑>today( 047 time_zone => "local") 048 ‑>add(weeks => ‑1); 049 050 m y %build_breakers = (); 051 052 f or my $build (@$build_data) 053 { 054 055 if ($build‑>{result} == 0) { 056 DEBUG "Build ", 057 "$build‑>{ commit } ok"; 058 next; 059 } 060 061 my $build_dt = 062 $f‑>parse_datetime( 063 $build‑>{started_at}); 064 065 if ( 066 $build_dt < $last_week_dt) 067 { 068 DEBUG "Ignoring ", 069 "old build $build_dt"; 070 next; 071 } 072 073 my $github_request = 074 "$github_api_url/$repo/" . 075 "commits/" . 076 "$build‑>{ commit }"; 077 078 DEBUG "Fetching ", 079 "$github_request"; 080 081 my $github_json = get( 082 "$github_api_url/$repo" . 083 "/commits" . 084 "/$build‑>{ commit }" ); 085 my $github_data = 086 from_json($github_json); 087 088 my $committer = 089 $github_data‑>{commit} 090 ‑>{committer}‑>{email}; 091 092 DEBUG "$committer broke ", 093 "build ", 094 "$build‑>{ commit }"; 095 $build_breakers{ 096 $committer }++; 097 } 098 099 for ( 100 sort keys %build_breakers) 101 { 102 print "$_: ", 103 "‑$build_breakers{ $_ }\n"; 104 }