Usando o git Bisect para descobrir origem de bug
Usando o git Bisect para descobrir origem de bug
Imagine o seguinte cenário. Você fez um código que funcionava corretamente. Depois de muitos commits, descobre que não funciona mais. E agora? O que aconteceu? O que posso fazer?
Uma possível solução é buscar o commit que gerou esse bug, para então corrigí-lo.
Esse post será dividido em 3 partes. A primeira é apenas para gerar o cenário com tal problema, a segunda é solucionando manualmente, e a última automaticamente.
Gerando cenário
Para esse tutorial, teremos 7 commits:
- Primeiro commit (projeto em branco)
- Commit com o código funcionando corretamente
- Commit aleatório
- Commit aleatório
- Commit que gerou o bug
- Commit aleatório
- Commit aleatório
Será feito em python (pois já vem nativas no Linux em geral, e será fácil de testar).
Commit 1
Execute o seguinte comando no seu shell
git init
echo 'My new project' > README.txt
git add .
git commit -m "First commit"
Commit 2
Crie um arquivo chamado test_nice.py, e coloque o seguinte conteúdo.
import unittest
class TestStringMethods(unittest.TestCase):
def test_something_test(self):
self.assertEqual('foo'.upper(), 'FOO')
Você pode testar que está funcionando com o comando “python -m unittest discover”. O resultado será o seguinte:
$ python -m unittest discover
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Por fim, adicione-o ao repositório com:
git add .
git commit -m "First test working :)"
Commits 3 e 4
Gere-os com os comandos:
echo 'file 1' > f1.txt
git add .
git commit -m "One file"
echo 'file 2' > f2.txt
git add .
git commit -m "Another file"
Commit 5
Agora vamos fazer o commit com bug. Edite a última linha do arquivo test_nice.py colocando uma asserção errada:
self.assertEqual('foo'.upper(), 'FOO error')
E adicione ao repositório:
git add .
git commit -m "Someone added a bug"
Commit 6 e 7
Adicione os últimos commits:
echo 'file 3' > f3.txt
git add .
git commit -m "File 3"
echo 'file 4' > f4.txt
git add .
git commit -m "My last file"
E pronto. Temos um código bugado, diversos commits, e não sabemos onde deu problema. Verifique que não funciona com o mesmo comando python -m unittest discover:
$ python -m unittest discover
F
======================================================================
FAIL: test_something_test (test_nice.TestStringMethods)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/rafael/Projects/git/bisect/test_nice.py", line 7, in test_something_test
self.assertEqual('foo'.upper(), 'FOO error')
AssertionError: 'FOO' != 'FOO error'
- FOO
+ FOO error
----------------------------------------------------------------------
Ran 1 test in 0.001s
Solução manual
Vamos manualmente buscar esse erro. O primeiro passo é saber algum commit que temos certeza que está funcionando. Com o comando git log, buscamos o nosso commit “First test working :)”
$ git log
[commits mais recentes]
commit 6e2d28a3f91d6fdea243575b9cf7f792ee61e188
Author: Example <[email protected]>
Date: Fri Jan 6 16:58:23 2017 +0000
First test working :)
[commits mais antigos]
Com isso, temos o commit 6e2d28a3f91d6fdea243575b9cf7f792ee61e188 como referência (o seu commit será diferente, utilize o seu). Execute os seguintes comandos:
git bisect start
git bisect bad
git bisect good 6e2d28a3f91d6fdea243575b9cf7f792ee61e188
O primeiro comando inicializa a sessão bisect, para buscarmos o erro. O segundo comando diz que o commit atual está quebrado. O último comando diz que o commit dado está funcionando bem.
Agora a cada commit, você verifica se está ok, e sinaliza se está bom ou não. Se estiver bom, git bisect good, ou então git bisect bad no caso contrário. No nosso exemplo:
python -m unittest discover
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Como está bom, então executamos git bisect good.
Refazendo o comando:
python -m unittest discover
F
======================================================================
FAIL: test_something_test (test_nice.TestStringMethods)
[...]
git bisect bad
8aa15bf9c745d0f7939b7a114db137fed2783276 is the first bad commit
commit 8aa15bf9c745d0f7939b7a114db137fed2783276
Author: Example <[email protected]>
Date: Fri Jan 6 16:59:27 2017 +0000
Someone added a bug
:100644 100644 c68990ec5cc5602c1adcdad80e1ef7d7f68b1188 09d9d6e4d7469ece38eb449541922214807e6332 M test_nice.py
Com isso, descobrimos que o bug aconteceu no commit 8aa15bf. Agora é ver o que aconteceu pelo código. Depois de analisar, basta executar git bisect reset para voltar onde estava inicialmente.
Com uma imagem fica mais fácil de entender o que aconteceu:
Simples, não? Mas tem uma forma automática mais rápida.
Solução automática
No nosso caso, temos o comando que retorna o erro. A biblioteca unittest retorna 0 quando não há erros, e um valor maior que 0 no caso o contrário. É exatamente o que o git precisa para fazer todo esse processo manualmente. Execute os comandos:
git bisect start
git bisect bad
git bisect good 6e2d28a3f91d6fdea243575b9cf7f792ee61e188
git bisect run python -m unittest discover
Os três primeiros comandos já vimos. O último vai adicionar o comando python -m unittest discover
para a verificação. Se o comando retornar 0
, então é considerado good
, e não 0
é bad
. Assim, temos o mesmo resultado.
Normalmente não é tão simples esse processo. Nesse tutorial usamos apenas 7 commits simples, o que não será o caso. Mas tendo a ideia, fica mais fácil de repetir o processo.
Espero que tenha ficado bem claro. Qualquer dúvida, deixe uma mensagem nos comentários.
Referências