Ao escrever um executor de testes, é importante pensar na escalonabilidade. Pergunte a si mesmo: "Se meu executor de testes tivesse que executar 200 mil casos de teste, quanto tempo isso levaria?"
O sharding é uma das respostas disponíveis no Trade Federation. É necessário dividir todos os testes que o executor precisa em vários blocos que podem ser paralelizados.
Esta página descreve como tornar seu executor fragmentável para o Tradefed.
Interface a ser implementada
A interface mais importante para implementar e ser considerada fragmentável pelo
TF é
IShardableTest,
que contém dois métodos: split(int numShard)
e split()
.
Se o sharding depender do número de shards solicitados, implemente split(int numShard)
. Caso contrário, implemente split()
.
Quando um comando de teste do TF é executado com parâmetros de fragmentação --shard-count
e --shard-index
, o TF itera por todos os IRemoteTest
para procurar aqueles que implementam IShardableTest
. Se encontrado, ele vai chamar split
para
receber um novo objeto IRemoteTest
e executar um subconjunto de casos de teste para um fragmento
específico.
O que preciso saber sobre a implementação da divisão?
- O executor pode fazer o sharding apenas em algumas condições. Nesse caso, retorne
null
quando não fizer o sharding. - Tente dividir o máximo possível: divida o executor em unidades de execução que façam sentido para ele. Isso depende muito do seu executor. Por exemplo: HostTest é fragmentado no nível da classe. Cada classe de teste é colocada em um fragmento separado.
- Se fizer sentido, adicione algumas opções para controlar um pouco o sharding.
Por exemplo:
AndroidJUnitTest
tem um
ajur-max-shard
para especificar o número máximo de fragmentos em que ele pode ser dividido, independente do número solicitado.
Exemplo detalhado de implementação
Confira um exemplo de snippet de código que implementa IShardableTest
para
referência. O código completo está disponível em
(https://android.googlesource.com/platform/tools/tradefederation/+/refs/heads/android16-release/test_framework/com/android/tradefed/testtype/InstalledInstrumentationsTest.java)
/**
* Runs all instrumentation found on current device.
*/
@OptionClass(alias = "installed-instrumentation")
public class InstalledInstrumentationsTest
implements IDeviceTest, IResumableTest, IShardableTest {
...
/** {@inheritDoc} */
@Override
public Collection<IRemoteTest> split(int shardCountHint) {
if (shardCountHint > 1) {
Collection<IRemoteTest> shards = new ArrayList<>(shardCountHint);
for (int index = 0; index < shardCountHint; index++) {
shards.add(getTestShard(shardCountHint, index));
}
return shards;
}
// Nothing to shard
return null;
}
private IRemoteTest getTestShard(int shardCount, int shardIndex) {
InstalledInstrumentationsTest shard = new InstalledInstrumentationsTest();
try {
OptionCopier.copyOptions(this, shard);
} catch (ConfigurationException e) {
CLog.e("failed to copy instrumentation options: %s", e.getMessage());
}
shard.mShardIndex = shardIndex;
shard.mTotalShards = shardCount;
return shard;
}
...
}
Este exemplo simplesmente cria uma nova instância de si mesmo e define parâmetros de fragmento para ela. No entanto, a lógica de divisão pode ser totalmente diferente de teste para teste. Desde que seja determinista e produza subconjuntos coletivamente exaustivos, não há problema.
Independência
Os fragmentos precisam ser independentes. Dois fragmentos criados pela sua implementação de
split
no seu executor não podem ter dependências um do outro nem compartilhar
recursos.
A divisão de fragmentos precisa ser determinista. Isso também é obrigatório. Nas mesmas condições, seu método split
sempre precisa retornar exatamente a mesma lista de fragmentos na mesma ordem.
OBSERVAÇÃO: como cada fragmento pode ser executado em instâncias diferentes do TF, é fundamental garantir que a lógica split
gere subconjuntos mutuamente exclusivos e coletivamente exaustivos de maneira determinista.
Fragmentar um teste localmente
Para fragmentar um teste em um TF local, basta adicionar a opção --shard-count
à linha de comando.
tf >run host --class com.android.tradefed.UnitTests --shard-count 3
Em seguida, o TF vai gerar e executar automaticamente comandos para cada fragmento.
tf >l i
Command Id Exec Time Device State
3 0m:03 [null-device-2] running stub on build 0 (shard 1 of 3)
3 0m:03 [null-device-1] running stub on build 0 (shard 0 of 3)
3 0m:03 [null-device-3] running stub on build 0 (shard 2 of 3)
Agregação de resultados de testes
Como o TF não faz agregação de resultados de testes para invocações fragmentadas, você precisa garantir que seu serviço de relatórios ofereça suporte a isso.