Unidade de Teste para SHA256

Conforto, paz, alívio. Unidade de teste pode trazer este tipo de emoção. Eu esqueci, ele traz dividendos também. Este artigo mostra que criar uma infraestrutura de unidade de teste pode ser bem simples. Esta implementação de unidade de teste foi construída para ajudar a testar SHA256, Uma Implementação Literal.

Unidade de Teste: Razão

Algumas razões. Ele documenta os testes que rodaram num pedaço de código. Ele mostra qual parte do código foi testada. Faz parte do código, portanto fala a língua do desenvolvedor. E mais importante, toda vez que o código é modificado, nós podemos rodar exatamente os mesmos testes e assim ter certeza de que a modifição não introduziu bugs. Conforto, paz e alívio.

Para mim, unidade de teste esta no mesmo nível de controle de versão, ele traz conforto, paz e alívio.

Unidade de Teste: Limitações

Código testado com unidade de teste não esta necessariamente correto. Embora as partes estejam corretas, não significa que o todo se comporta como esperado. Unidade de teste não funciona muito bem com software que manipula entrada de dados do usuário ou outros eventos do ambiente externo, eles precisam ser emulados, o que nunca é igual ao mundo real.

Unidade de Teste: Infraestrutura

Há muitas infraestruturas para se escolher. Algumas simples, outras complicadas. Melhor ter uma infraestrutura simples e implementada do que ter uma infraestrutura cheia de opções não necessárias e que não seja implementada. Por exemplo, olhei Google Test, tenho certeza que é bom, mas ele requer pthreads, o que não existe ainda em Webassembly. Mas de novo, por que complicar? Eu escolhi uma unidade de teste mínima e simples.

MinUnit, Unidade de Teste Feita de Duas Macros

MinUnit é mais do que suficiente. Não tem nenhuma dependencia. Ele pode até rodar em ambientes sem sistema operacional.

Eu o modifiquei um pouco, vamos dar uma olhada.

Há apenas duas macros mu_assert() e mu_run_test(), estão definidas e comentadas abaixo. Use mu_assert() para verificar o resultado de um teste, a entrada test de mu_assert é a condição de test, e message é a mensagem que é exibida quando o teste falha. Use mu_run_test() para executar uma função que contem um ou mais testes.

Conteúdo modificado de minunit.h


/* file: minunit.h */ 

// Minimum Unit Testing, assert macro. 
// If the test condition is 0(FALSE), then print message and return -1 
// If the test condition is not 0(TRUE), increment test number and 
// go to the next test 
// 
#define mu_assert(message, test) \ 
   do \ 
   { \ 
       DBG_LOG(DEBUG_WASM_DEBUG, "Running test:%d\n", tests_run); \ 
       if(!(test)) \ 
       { \ 
           DBG_LOG(DEBUG_WASM_DEBUG, "%s-FAILED!\n", message); \ 
           return -1; \ 
       } \ 
       tests_run++; \ 
   } while (0) 

// Minimum Unit Testing, run test macro. 
// Execute test_func(), if test_func() is successfull go to the next test, 
// return -1 otherwise 
// 
// test_func() should return 0 if succssfull 
// 
#define mu_run_test(test_func) do{ if( test_func() )  return -1; } while (0) 

extern int tests_run; 
 

Confuso sobre o while(0)? Leia While(0), Por que as Macros o Usam?.

O Que Testar?

Nós vamos testar o SHA256, Uma Implementação Literal. Em algum momento nós temos que descobrir o que será testado. Aqui algumas dicas:

  • Alimentar a função com data de tamanho zero. Esta é fácil.
  • Olhe por casos de transição (corner case). É incrível a quantidade de erros que ocorrem em volta das transições. Por exemplo, imagine que você tenha que construír uma cerca de 10 metros e precisa colocar um poste a cada metro. Quantos postes precisa? Não são 10. São necessários 11 postes. Este tipo de situação gera um monte de bugs! Um jeito de pensar é o de reduzir a situação para um tamanho mínimo, por exemplo, seriam necessários 2 postes para criar uma cerca de 1 metro, portanto, é seguro dizer que são necessários (número de metros+1) postes para criar este tipo de cerca.

Examinando a implementação SHA256, eu consigo ver que há uma importante transição que necessita ser testada, o caso em que a mensagem de entrada tem 448 bits:


  // Pad the message so that its length is congruent to 448 modulo 512 
  if( l%512 < 448) 
     k = 448 - l%512; 
  else 
     k = 512 + 448 - l%512; 

Esta é claramente uma condição que precisa ser testada.

  • Transição +-1. Apenas uma adição para o caso anterior, algumas vezes as transições se manifestam em contagens +-1.
  • Grande quantidade de dado. Muito importante para testar vazamento de memória. Também ajuda achar bugs quando a implementação trava ou escreve além dos buffers.

Modulo de Unidade de Test do SHA256

Veja o arquivo sha256-unit-test.c abaixo. Nós basicamente criamos a estrutura sha_test_vector_entry_t que contém todos os dados necessários para exercer um teste, então criamos a tabela sha_test_vector com todos os testes. A maioria dos testes eu peguei de SHA256 useful test vectors.


#include <string.h> 
#include <emscripten.h> 

#include "sha256.h" 
#include "debug-wasm.h" 
#include "minunit.h" 

// This variable identify what test is running 
int tests_run; 

static int SHA256_UT_TableBasedTests(void) 
{ 
   typedef struct _SHA_TEST_VECTOR_ENTRY_t_ 
   { 
       char        *desc;              // Test description, shows when test fails 
       uint8_t     *expected_result;   // Expected result 
       uint8_t     *input;             // input to be fed to the tested function 
       size_t      input_size;         // input size 
       size_t      input_times;        // how many times data is fed to the tested function 
   } sha_test_vector_entry_t; 
    
   unsigned char shaResult[SHA256_DIGEST_SIZE];    // SHA result goes here 
    
   // Test vectors 
   sha_test_vector_entry_t sha_test_vector[]= 
   { 
       { 
           "No data", 
           (uint8_t []){0xe3,0xb0,0xc4,0x42,0x98,0xfc,0x1c,0x14,0x9a,0xfb,0xf4,0xc8,0x99,0x6f,0xb9,0x24,0x27,0xae,0x41,0xe4,0x64,0x9b,0x93,0x4c,0xa4,0x95,0x99,0x1b,0x78,0x52,0xb8,0x55}, 
           0, 
           0, 
           1 
       }, 
       { 
           "When known abc", 
           (uint8_t []){0xba,0x78,0x16,0xbf ,0x8f,0x01,0xcf,0xea ,0x41,0x41,0x40,0xde ,0x5d,0xae,0x22,0x23 ,0xb0,0x03,0x61,0xa3 ,0x96,0x17,0x7a,0x9c,0xb4,0x10,0xff,0x61,0xf2,0x00,0x15,0xad}, 
           (uint8_t []){'a','b','c'}, 
           3, 
           1 
       }, 
       { 
           "440bits = corner case-1 byte", 
           (uint8_t []){0xaa,0x35,0x3e,0x00,0x9e,0xdb,0xae,0xbf,0xc6,0xe4,0x94,0xc8,0xd8,0x47,0x69,0x68,0x96,0xcb,0x8b,0x39,0x8e,0x01,0x73,0xa4,0xb5,0xc1,0xb6,0x36,0x29,0x2d,0x87,0xc7}, 
           (uint8_t []){'a','b','c','d','b','c','d','e','c','d','e','f','d','e','f','g','e','f','g','h','f','g','h','i','g','h','i','j','h','i','j','k','i','j','k','l','j','k','l','m','k','l','m','n','l','m','n','o','m','n','o','p','n','o','p'}, 
           55, 
           1 
       }, 
       { 
           "448bits = corner case", 
           (uint8_t []){0x24,0x8d,0x6a,0x61,0xd2,0x06,0x38,0xb8,0xe5,0xc0,0x26,0x93,0x0c,0x3e,0x60,0x39,0xa3,0x3c,0xe4,0x59,0x64,0xff,0x21,0x67,0xf6,0xec,0xed,0xd4,0x19,0xdb,0x06,0xc1}, 
           (uint8_t []){'a','b','c','d','b','c','d','e','c','d','e','f','d','e','f','g','e','f','g','h','f','g','h','i','g','h','i','j','h','i','j','k','i','j','k','l','j','k','l','m','k','l','m','n','l','m','n','o','m','n','o','p','n','o','p','q'}, 
           56, 
           1 
       }, 
       { 
           "456bits = corner case+1 byte", 
           (uint8_t []){0x4e,0x0e,0xa7,0x75,0xaa,0x77,0x66,0xcf,0x27,0x36,0xd5,0xa4,0x0e,0x4c,0x6f,0x76,0xe1,0xc4,0xc6,0x9e,0xda,0x0f,0x3c,0x78,0x2c,0x5a,0x2b,0xd7,0x7b,0x3f,0x96,0x95}, 
           (uint8_t []){'a','b','c','d','b','c','d','e','c','d','e','f','d','e','f','g','e','f','g','h','f','g','h','i','g','h','i','j','h','i','j','k','i','j','k','l','j','k','l','m','k','l','m','n','l','m','n','o','m','n','o','p','n','o','p','q','r'}, 
           57, 
           1 
       }, 
       { 
           "896bits = 2*corner case", 
           (uint8_t []){0xcf,0x5b,0x16,0xa7,0x78,0xaf,0x83,0x80,0x03,0x6c,0xe5,0x9e,0x7b,0x04,0x92,0x37,0x0b,0x24,0x9b,0x11,0xe8,0xf0,0x7a,0x51,0xaf,0xac,0x45,0x03,0x7a,0xfe,0xe9,0xd1}, 
           (uint8_t []){'a','b','c','d','e','f','g','h','b','c','d','e','f','g','h','i','c','d','e','f','g','h','i','j','d','e','f','g','h','i','j','k','e','f','g','h','i','j','k','l','f','g','h','i','j','k','l','m','g','h','i','j','k','l','m','n','h','i','j','k','l','m','n','o','i','j','k','l','m','n','o','p','j','k','l','m','n','o','p','q','k','l','m','n','o','p','q','r','l','m','n','o','p','q','r','s','m','n','o','p','q','r','s','t','n','o','p','q','r','s','t','u'}, 
           112, 
           1 
       } 
   }; 

   // Loop all test cases 
   for(size_t test=0 ; test<6 ; test++ ) 
   { 
       // Feed data into tested function this many times 
       for( size_t input_times=0 ; input_times<sha_test_vector[test].input_times ; input_times++ ) 
       { 
           // Feed data 
           Sha256_Compute(sha_test_vector[test].input, sha_test_vector[test].input_size, shaResult); 
       } 
        
       // Did it work? 
       mu_assert(sha_test_vector[test].desc, (memcmp( shaResult, sha_test_vector[test].expected_result, SHA256_DIGEST_SIZE )==0)); 
   } 
        
   return 0; 
} 

// Run SHA256 unit test 
// returns 0 if all tests passed, non 0 if at least a test failed 
// 
EMSCRIPTEN_KEEPALIVE int SHA256_Unit_Test(void) 
{ 
   mu_run_test(SHA256_UT_TableBasedTests); 
   return 0; 
} 
 

Onde Esta o Teste Com Grande Quantidade de Dado?

Um milhão de caracteres a tornou meu Firefox unresponsive, eu decidi endereçar esta condição num próximo artigo, desta forma eu não misturo os assuntos neste artigo.

Rodando a Unidade de Teste

Todos os testes passaram, assim é que deve se parecer:

sha256-all-pass

A Unidade de Teste Precisa Ser Testada!

Já aconteceu comigo antes, tinha criado os testes, todos passaram, mas a unidade de teste não estava testando nada! Alegria de pobre. Não se esqueça de testar os seus testes! Aqui eu apenas mudei um byte do vetor de resultados esperados do teste de 448 bits, desta forma o resultado não é alcançado, você deve ver o seguinte erro:

Detalhes Finais

Este código complementa o artigo SHA256, Uma Implementação Literal, lá você pode encontrar o código fonte de sha256.c, sha256.h, debug-wasm.c e debug-wasm.h. A seguir está o conteúdo do arquivo sha256.html, que chama a função de unidade de teste:


<html> 
<head> 
 <meta charset="UTF-8"> 
 <script type="text/javascript" src="sha256.js"></script> 
 <script> 
   Module() 
   .then(function(instance){ 
       var exports = instance['asm']; // the .js file puts the exports in the 'asm' field 
       // Add button listener, calls Webassembly's sha256Test() when clicked 
       var button = document.getElementById('sha256Button'); 
       button.addEventListener('click', function() { 
           exports._SHA256_Unit_Test(); 
       }, false); 
     } 
 ); 
 </script> 
</head> 
<body> 
 <input type="button" id="sha256Button" value="click here to run SHA256 UNIT TESTs"/> 
</body> 
</html> 
 

Compilando:


emcc sha256.c sha256-unit-test.c debug-wasm.c -O1 -s MODULARIZE=1 -s WASM=1 -o sha256.js  
 

Inicie o servidor EMSCRIPTEN:


emrun --no_browser --port 8080 . 
 

E aponte os seu browser para: http://localhost:8080/sha256.html


Leave a message below. Webassembly is evolving rapidly, please let me know if this post got outdated.

Enjoyed this post?

Don't miss new posts: Share it with your friends:

Você pode gostar...

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *