Back-End

7 nov, 2014

Integração contínua sem dores de cabeça

Publicidade

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.

Figura 1: Se o usuário Travis aciona o serviço de verificação para um projeto, o GitHub notifica o serviço sobre quaisquer atualizações de código realizadas.
Figura 1: Se o usuário Travis aciona o serviço de verificação para um projeto, o GitHub notifica o serviço sobre quaisquer atualizações de código realizadas.

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.

Figura 2: O arquivo .travis.yml no diretório raiz de um repositório diz ao Travis-ci.org os ambientes em que devem ser executados os testes.
Figura 2: O arquivo .travis.yml no diretório raiz de um repositório diz ao Travis-ci.org os ambientes em que devem ser executados os testes.

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.

Figura 3: Logo depois de uma atualização do código no GitHub, o Travis-ci.org lança a suíte de testes.
Figura 3: Logo depois de uma atualização do código no GitHub, o Travis-ci.org lança a suíte de testes.

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.

Figura 4: O conjunto de testes foi concluído com sucesso.
Figura 4: O conjunto de testes foi concluído com sucesso.

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.

Figura 5: O mecanismo de testes escrito em Ruby alerta o site Travis quando um desenvolvedor envia novo código para o projeto GitHub.
Figura 5: O mecanismo de testes escrito em Ruby alerta o site Travis quando um desenvolvedor envia novo código para o projeto GitHub.

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).

Figura 6: O Travis-ci.org precisa de acesso administrativo em todos os seus repositórios para instalar o mecanismo de testes.
Figura 6: O Travis-ci.org precisa de acesso administrativo em todos os seus repositórios para instalar o mecanismo de testes.

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.

Figura 7: Depois de instalar o mecanismo de testes, os usuários mais cautelosos devem revogar os privilégios administrativos atribuídos.
Figura 7: Depois de instalar o mecanismo de testes, os usuários mais cautelosos devem revogar os privilégios administrativos atribuídos.

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.

Figura 8: Usando a API REST para consultar o status do teste de um projeto.
Figura 8: Usando a API REST para consultar o status do teste de um projeto.

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.

 Figura 9: A compilação mais recente e seus resultados.
Figura 9: A compilação mais recente e seus resultados.

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.

Figura 10: O script karma atribui pontos negativos para o desenvolvedor cujo código apresentou problemas ou erros no conjunto de testes.
Figura 10: O script karma atribui pontos negativos para o desenvolvedor cujo código apresentou problemas ou erros no conjunto de testes.

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 }