Escribir un ejecutor de pruebas de IRemoteTest fragmentado

Al escribir un corredor de prueba, es importante pensar en la escalabilidad. Pregúntese, "si mi corredor de pruebas tuviera que ejecutar 200K casos de prueba", ¿cuánto tiempo tomaría?

Sharding es una de las respuestas disponibles en Trade Federation. Requiere dividir todas las pruebas que el corredor necesita en varios fragmentos que se pueden paralelizar.

Esta página describe cómo hacer que su corredor se pueda fragmentar para Tradefed.

Interfaz a implementar

La interfaz más importante que se debe implementar para que TF la considere fragmentable es IShardableTest , que contiene dos métodos: split(int numShard) y split() .

Si su fragmentación va a depender de la cantidad de fragmentos solicitados, debe implementar split(int numShard) . De lo contrario, implemente split() .

Cuando se ejecuta un comando de prueba de TF con los parámetros --shard-count y --shard-index , TF itera a través de todos los IRemoteTest para buscar los que implementan IShardableTest . Si lo encuentra, llamará a split para obtener un nuevo objeto IRemoteTest para ejecutar un subconjunto de casos de prueba para un fragmento específico.

¿Qué debo saber sobre la implementación dividida?

  • Su corredor puede fragmentar solo en algunas condiciones; en ese caso, devuelva null cuando no fragmentó.
  • Intente dividir todo lo que tenga sentido: divida su corredor en la unidad de ejecución que tenga sentido para él. Realmente depende de tu corredor. Por ejemplo: HostTest se fragmenta en el nivel de clase, cada clase de prueba se coloca en un fragmento separado.
  • Si tiene sentido, agregue algunas opciones para controlar un poco la fragmentación. Por ejemplo: AndroidJUnitTest tiene un ajur-max-shard para especificar el número máximo de fragmentos en los que podría dividirse, independientemente del número solicitado.

Implementación de ejemplo detallada

Aquí hay un fragmento de código de ejemplo que implementa IShardableTest al que puede hacer referencia. El código completo está disponible en (https://android.googlesource.com/platform/tools/tradefederation/+/refs/heads/master/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 ejemplo simplemente crea una nueva instancia de sí mismo y le establece parámetros de fragmento. Sin embargo, la lógica de división puede ser totalmente diferente de una prueba a otra; y mientras sea determinista y produzca subconjuntos colectivos exhaustivos, está bien.

Independencia

¡Los fragmentos deben ser independientes! Dos fragmentos creados por su implementación de split en su corredor no deben tener dependencias entre sí ni compartir recursos.

¡La división de fragmentos debe ser determinista! Esto también es obligatorio, dadas las mismas condiciones, su método de split siempre debe devolver exactamente la misma lista de fragmentos en el mismo orden.

NOTA: Dado que cada fragmento puede ejecutarse en diferentes instancias de TF, es fundamental asegurarse de que la lógica split produzca subconjuntos que sean mutuamente excluyentes y colectivamente exhaustivos de manera determinista.

Cómo fragmentar una prueba localmente

Para fragmentar una prueba en un TF local, simplemente puede agregar la opción --shard-count a la línea de comando.

tf >run host --class com.android.tradefed.UnitTests --shard-count 3

Luego, TF generará automáticamente comandos para cada fragmento y los ejecutará.

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)

Agregación de resultados de prueba

Dado que TF no realiza ninguna agregación de resultados de prueba para invocaciones fragmentadas, debe asegurarse de que su servicio de informes lo admita.