Repository navigation
Estabilidade dos testes: esteira de instabilidade e as causas que ela achou - #8
Merged
Merged
Conversation
…s que falham "às vezes" Os testes que ainda dão "instável" no gate (CanvasGrid holdingAKey/rollover, SpellChecker backspace/sugestão, joystick da BarsTest, OverlayServiceCancelTest, PackOrbBudgetTest) falham uma vez a cada tantas rodadas e nunca no emulador local. Workflow Stress: os APKs são montados uma vez; 16 emuladores iguais ao do gate repetem as classes por ~40 min, reinstalando o app a cada volta; metade com o QEMU num cgroup limitado a 2 núcleos (perto do runner privado de 2 vCPU). Cada volta com falha guarda saída bruta, trecho do logcat e tela; o relatório soma falhas/execuções por teste e condição de CPU e fica vermelho quando acha algo. Roda por push em ci/esteira-lenta… (o código daquele commit) ou, na main, por Run workflow. Não bloqueia PR.
…repositório é público
… e a evidência sai na hora
…hora por um status no commit
… teste do mic seguia por baixo canvasGridDoesNotDisturbTheMicButton inicia uma gravação de verdade e o tearDown só zerava o estado exibido. A gravação continuava no OverlayService: na esteira de instabilidade, o KeyFlowKeyboardBarsTest seguinte achava "● 2:20" no lugar do "🎙" em 134 de 134 voltas, e o OverlayServiceCancelTest começava com o serviço já gravando (o ACTION_RECORD virava "parar e enviar"). No gate o teste seguinte do mesmo shard herdava a mesma gravação.
…— o GitHub recusava a descrição com emoji
…ow reinstalado
Esteira de instabilidade (38057718808): logo depois do `adb install -r`, o sistema reconstrói a lista de
teclados em segundo plano e recusa `ime enable` e `ime set` do KeyFlow ("unrecognized IME ID"). No emulador
lento isso passou de 75 s; o 1º teste de teclado da volta esperou 30 s por um teclado que não tinha como
subir (holdingAKey… "KeyFlowKeyboardView não anexou"). O awaitKeyboardAttached repetia só o `ime set` — sem
o `enable` aceito, nunca voltava — e um toque no campo recriado derrubava a espera (StaleObjectException).
O gate e a esteira esperam `ime list -a -s` listar o KeyFlow depois de instalar (até 180 s, com o tempo no
log); o awaitKeyboardAttached repete `enable` + `set` e tolera o campo recriado.
… teclado Na esteira (38057718808), typingCommits e slidingToAnotherKey falharam com o teclado já anexado e registrado: o toque no "q" não chegou ao campo, e o logcat não separa "o toque caiu na janela do probe, por baixo" de "a grade recebeu e a tecla se perdeu depois". A grade conta os eventos de toque e guarda o último (ação e posição, sem custo de texto por evento); as mensagens dos testes de digitação trazem isso, o texto do campo e a janela do IME vista pelo sistema de entrada. A leitura do campo tolera o nó trocado (StaleObjectException).
…ciador de janelas e a tela Esteira 38061331651: nas 5 falhas da tira de acentos o sistema de entrada via a janela do teclado em [0,1739][1080,2526] — 126 px (a barra de navegação com o teclado aberto) abaixo do normal [0,1613][1080,2400] — enquanto a view achava que estava no lugar; o toque calculado pela view caía 53 px acima da grade. Para separar "janela desenhada no lugar errado" de "só a área de toque errada", a mensagem traz agora a raiz da view (posição e tamanho), o que o gerenciador de janelas guardou (altura pedida, frame, estado de desenho, leash de insets) e uma captura da tela na hora da falha, que a esteira traz para a evidência. Conferido com uma falha provisória no emulador local (a 1ª versão com Regex quebrava no ICU do Android).
…ue só tocam Esteira 38066247962: typingCommits digitou "1" no lugar de "q" e o rollover "q0" no lugar de "qp", com a grade vendo o toque no lugar certo. No emulador lento a injeção do DOWN → UP passa dos 250 ms da tira de acentos, o toque vira "segurar" e soltar no lugar insere a 1ª variante (o número da tecla). accentHoldMsForTest alonga o segurar nos testes que só tocam (CanvasGridTest, SpellCheckerTest); os testes da tira de acentos seguem com os 250 ms do produto.
…steira compara condições Esteira 38066247962: as capturas na hora da falha mostraram o teclado desenhado fora do lugar — 126 px abaixo, com a fileira do espaço cortada, ou no topo da tela —, com o gerenciador de janelas e a view guardando a posição certa: quem errou foi a camada da animação de entrada. Essa camada é controlada pela tela que recebe o teclado, e o ImeProbeActivity roda no processo do próprio KeyFlow; no uso real são processos diferentes. ImeProbeRemoteActivity é o mesmo probe em `:probe`, escolhido na CanvasGridTest por `-e probeProcess remoto` (o padrão segue o de hoje, sem mudar o gate). A esteira aceita argumentos extras do teste (input instrument_args / STRESS_INSTRUMENT_ARGS) e o relatório separa as falhas por condição (CPU + argumentos). Conferido no emulador local: os 7 testes da CanvasGridTest passam com o campo no processo com.keyflow:probe.
…a view está Esteira (38066247962 e 38070354174): sob carga, a janela do teclado ficou para o sistema de entrada 126 px abaixo, no topo da tela ou com a geometria do 1º desenho, enquanto a view e o gerenciador de janelas guardavam a posição certa — por um instante (campo em outro processo) ou por segundos (no mesmo processo). O toque calculado pela view caía fora da grade. awaitImeWindowSettled espera o frame da janela do IME no `dumpsys input` bater com a raiz da view em duas leituras seguidas (até 10 s; senão falha dizendo os dois). Roda no fim do awaitKeyboardAttached (todas as classes de teclado) e antes de cada cálculo de posição de tecla na CanvasGridTest, SpellCheckerTest e LanguageTest. A espera às cegas de 5 s continua por ora: sai num passo à parte, quando a esteira mostrar que esta basta. 17/17 no emulador local.
…a comparar KeyFlow e Gboard Mede, durante cada abertura do campo, o frame da janela do IME no sistema de entrada contra o do gerenciador de janelas — vale para qualquer teclado. É o controle da esteira: se o Gboard, no mesmo emulador e campo, também fica fora do lugar, é do Android/emulador; se só o KeyFlow, é algo nosso (ex.: o 1º desenho demorando mais que o prazo de sincronização do Android). Só roda com `-e placementProbe true` (o gate pula). No emulador local, 3 aberturas cada: o KeyFlow ficou 2 leituras seguidas com a camada do tamanho do 1º desenho ([0,1613][840,1739]); o Gboard, 1 leitura isolada.
… janela e recriações com motivo Esteira de instabilidade (2026-10-10): a janela do KeyFlow ficava fora do lugar ao abrir, e o Gboard não. O log do teclado passa a dizer o tempo do onCreateInputView (leituras de config × montar a view), do onStartInputView até o 1º quadro (e o tamanho da janela nele), cada mudança de altura da janela e cada recriação da view com o motivo. Foi o que mostrou, no emulador lento, a janela nascendo no tamanho de Dialog (840×126) ou na altura da tela (2272) e o 1º quadro passando de 1 s. Nada de dado do usuário.
… de oito O onCreateInputView roda na thread principal bem quando o Android vai mostrar o teclado, e fazia oito runBlocking avulsos (tema, tamanho, idioma, fileira de números, skin, barras, grades, vibração) — oito idas e voltas entre threads, 224–420 ms no emulador lento. Com o 1º quadro passando do prazo de 1 s, o Android mostrava a janela ainda em transição (esteira de instabilidade, 2026-10-10). keyboardSnapshotOnce lê tudo de uma vez, com os mesmos padrões: 61 ms no mesmo emulador. Leitura fresca (não um cache), para uma config gravada logo antes de abrir o teclado valer já na 1ª montagem. SettingsRepositoryTest: 29/29, com os dois testes novos do retrato.
…m a frio scripts/ime-placement.py faz pelo adb o que o ImeWindowPlacementTest faz por dentro: lá a medição rodava no processo com.keyflow — o mesmo do teclado do KeyFlow e do UiAutomator — e competia com a thread principal do KeyFlow e não com a do Gboard. --cold encerra o processo do teclado antes de cada abertura (a comparação justa). No emulador lento, a frio: KeyFlow 6 de 8 aberturas com a janela em transição visível (região tocável vazia, tamanho de Dialog), Gboard 1 de 8.
…reabre o teclado Cada `am instrument` reinicia o processo do app, e o executor de testes e o UiAutomator rodam no mesmo processo do teclado do KeyFlow: o 1º teste de teclado pega a abertura a frio com a thread principal atrasada, o 1º quadro passa do prazo de 1 s do Android e a janela fica com a geometria da transição — no processo do teclado, presa por mais de 10 s (esteira 38074785127: 10 de 12 falhas eram "não assentou"). Medido de fora (scripts/ime-placement.py), o mesmo teclado quente fica no lugar. Na abertura (awaitKeyboardAttached), sem assentar em 5 s o teste fecha o teclado (só se ele estiver de pé) e toca no campo de novo, até 3 vezes, com um log a cada reabertura; no meio do teste não reabre. Emulador lento: 14/14 duas vezes, a 1ª rodada com 1 reabertura no teste a frio.
…r em 30 s — e dizem o estado do fim da espera Esteira 38082896996: commitCancel falhou 1 de 54 voltas (CPU limitada) — o teste dava 2 s ao "concluir" contra a janela de 3 s, e só parar e soltar o gravador passou disso. No emulador local, a frio, targetChange… falhou: o teste levou 19 s e a janela fechou antes do "desfazer" (esperava PAUSED, achou IDLE). Como no seletor do teclado, a classe usa OverlayService.cancelWindowMsForTest = 30 s; cancelExpiresAndDiscardsAudio, que testa a janela fechando, volta aos 3 s. O "concluir" ganha 15 s e tem que encerrar bem antes dos 30. A mensagem de espera era montada no começo (dizia o estado de antes e o array como endereço); agora é no fim, com os estados por nome. 5/5 duas vezes a partir de processo novo.
…fica órfão no cache
O work() conferia `closed` e só depois guardava o quadro: se o close() caísse entre os dois, o quadro entrava no cache
global de uma orbe já fechada e ficava lá até ser despejado. Achado pelo PackOrbBudgetTest na esteira de instabilidade
(2026-10-10): o teste começava com exatamente um quadro de 156 px (97.344 bytes) de outra orbe no cache, que sumia no meio
dele ("expected 97344 but was 0"). Conferir `closed` de novo depois do put cobre todas as ordens, porque o close() marca
`closed` antes de limpar. PackOrbBudgetTest 20/20 três vezes.
…sentamento faz o mesmo papel As seis classes esperavam 5 s por um "🎙" que nunca aparece (o mic é desenhado pela BarView); era o tempo para a entrada do teclado assentar, e tirá-lo sem troca rendeu 4 falhas em 2 rodadas do CI (508f953). O comentário pedia trocar só por uma condição que esperasse o mesmo assentamento: é o que o awaitKeyboardAttached faz agora (janela do IME onde a view está, reabrindo o teclado se não assenta). Uns 5 s a menos por teste de teclado. 25/25 no emulador local; a esteira valida sob carga.
Esteira 38087774045 (1 falha em 7.304 execuções): o teclado ficou preso no topo da tela ([120,128][1200,915]), a espera de assentamento mandou "voltar" e tocou no campo em (540, 283) — mas o teclado ainda cobria o campo, e o toque virou a tecla "r": o campo terminou "rooo" no lugar de "o". Agora, depois do "voltar", a reabertura espera o teclado sumir do sistema de entrada (até 3 s) e só então toca; se ele não sai, não toca e a próxima volta da espera tenta de novo.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resumo
Esteira de instabilidade (
.github/workflows/stress.yml): 16 emuladores repetem em laço os testes que falhavam "às vezes" no gate, metade com a CPU do emulador limitada, com aviso antecipado por status no commit e relatório por teste e condição. Ela achou as causas abaixo; da 1ª rodada (≈140 voltas com falha) à última (1 falha em 7.304 execuções, já corrigida em cd4adb5).Defeitos do app
onCreateInputViewfazia oito leituras bloqueantes do DataStore na thread principal (até 420 ms no emulador lento); agora uma (≈60 ms). Logs de diagnóstico da abertura (1º quadro, altura da janela, recriações com motivo).close()ficava órfão no cache global.collectAsStateOnMain) — no teste, valor vindo de outra thread recompunha fora dela.Testes
CanvasGridTestdeixava uma gravação real rodando (oBarsTestseguinte achava "● 2:20").ime listlistar o KeyFlow.…ForTest(desfazer o cancelamento, tira de acentos).ImeWindowPlacementTest, só com-e placementProbe true) e por fora (scripts/ime-placement.py, também a frio).Achado que fica como tarefa
A frio, num aparelho lento, a janela do KeyFlow aparece em transição por um instante (medido de fora: 6 de 8 aberturas; Gboard 1 de 8). Pede um perfil com ferramenta de verdade (Android Studio no A32 ou simpleperf). No aparelho rápido não se nota.
Validação
systemd-run --user --scope -p CPUQuota=300%).