- Shell 100%
O CI derrubou `partida_aceita_dois_jogadores_e_recusa_o_terceiro` com `nome1 = "r", nome2 = "r", nome3 = "a"`. Os dois primeiros jogadores receberam o mesmo id, e o terceiro foi aceito em vez de recusado. Não é defeito do servidor: entrar com um nome que já está na partida é reconexão, por desenho. O `join_game` devolve o id que aquele jogador já tinha e não consome a vaga -- é o que permite fechar o navegador e voltar para a mesma partida, e é o que os testes de unidade `test_join_game_reconnection` já afirmavam. A propriedade é que estava escrita como se três nomes quaisquer fossem três jogadores. Ganha `prop_assume!` de nomes distintos. E a regra que o proptest encontrou por acidente passa a ser afirmada de propósito, em `entrar_com_o_nome_de_quem_ja_esta_e_reconexao`: a reconexão devolve o mesmo id, não consome a vaga do segundo jogador, vale também para o segundo, e continua valendo com a partida cheia. O caso só aparecia quando o proptest sorteava nomes iguais -- raro com nomes de até 12 letras, e por isso passou despercebido localmente até o runner tirar a carta. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PhdPjHUuVhXaitwT4mG53t |
||
|---|---|---|
| .cargo | ||
| .githooks | ||
| .woodpecker | ||
| battleship-client@60983b15d7 | ||
| battleship-common@1910b91495 | ||
| battleship-server@c8dd5899fe | ||
| .gitignore | ||
| .gitmodules | ||
| Cargo.lock | ||
| Cargo.toml | ||
| LICENSE | ||
| README.md | ||
Batalha Naval
Batalha Naval multijogador com servidor em axum e cliente web em WebAssembly. Escrito como protótipo de curso de Rust.
Dois jogadores entram numa partida, posicionam a frota e alternam tiros até um deles afundar todos os navios do outro. A comunicação é por WebSocket.
Estrutura
Workspace com três crates, cada um em seu próprio submódulo git:
| crate | o que é | repositório |
|---|---|---|
battleship-common |
tipos, regras e protocolo — a biblioteca compartilhada | battleship-common |
battleship-server |
servidor axum com WebSocket, persistência em sled | battleship-server |
battleship-client |
cliente web em Yew, compilado para WebAssembly | battleship-client |
cargo run -p battleship-server # sobe o servidor
cd battleship-client && trunk serve # sobe o cliente
Como os crates são submódulos, depois de clonar:
git submodule update --init --recursive
Cada um tem o seu próprio pipeline, que responde a uma pergunta que esta
build integrada não responde: o crate se sustenta sozinho? Por isso o
servidor e o cliente declaram battleship-common pelo repositório dela,
e não por caminho — sem isso um clone isolado nem compila. Dentro deste
workspace o [patch] do Cargo.toml da raiz redireciona essa
dependência de volta para o diretório ao lado, então a build integrada
usa o código do submódulo, no SHA fixado aqui, e não a ponta de main.
Sobre o battleship-common
Vale um parágrafo, porque é o que este projeto ensina de mais caro.
Havia três cópias dos tipos do jogo: o servidor compilava contra uma
cópia própria de 513 linhas, o cliente tinha outra em src/common.rs, e
o battleship-common de verdade — 782 linhas, com testes — não era
usado por ninguém. As cópias divergiram, e o jogo parou de funcionar
ponta a ponta:
- o envelope da mensagem (
tag = "type"de um lado, comcontent = "data"do outro); - o nome da célula de água (
Emptyno servidor,Waterno cliente); - o eixo em que um navio horizontal se estende.
Cada lado testava a sua própria cópia, e as duas suítes passavam. O defeito só aparecia com os dois processos no ar.
Hoje os três crates compartilham a biblioteca, e há testes cujo único
propósito é impedir que a cópia volte: battleship-client/tests/spider
compara byte a byte a serialização feita pelo caminho do cliente com a
feita pela biblioteca.
Testes
cargo test --workspace # 265 testes
cargo test -p battleship-common --test spider
cargo mutants -p battleship-common # índice de mutação: 0 sobreviventes
O cliente é wasm: os componentes Yew e o WebSocketService só rodam
dentro de um navegador, e por isso ficam fora da conta de mutação (veja
battleship-client/.cargo/mutants.toml). Para cobri-los é preciso teste
de navegador:
cd battleship-client && wasm-pack test --headless --firefox
Verificações antes do commit
O .githooks/pre-commit roda aqui o que o Woodpecker roda no servidor. Ele
é gerado a partir do .woodpecker/pipelines.yml deste repositório, então os
comandos não podem divergir do CI. Ative uma vez por clone -- o hook é
versionado, a configuração que aponta para ele não é:
git config core.hooksPath .githooks
Roda tudo a cada git commit, do mais barato ao mais caro, e para no
primeiro que falhar: lint do pipeline, cargo fmt --check, clippy com -D warnings, build, testes, cobertura (piso 90%), auditoria de dependências e
a perna MSRV 1.90.
O teste de mutação só existe aqui. Ele saiu do pipeline: mutação
recompila o crate uma vez por mutante, e no runner isso eram dezenas de
minutos por push, num passo que ninguém acompanha enquanto espera. O teto
continua em .mutants-teto, versionado -- a regra não afrouxou, mudou de
lugar. É de longe a etapa mais lenta; para pular só ela:
PRE_COMMIT_SEM_MUTACAO=1 git commit ...
Para pular tudo, git commit --no-verify. Um hook que não se pode pular é
um hook que todo mundo desativa.
Licença
GPL-3.0-or-later. Veja LICENSE.