Écrire un exécuteur de test IRemoteTest fractionné

Lorsque vous écrivez un test runner, il est important de penser à la scalabilité. Demandez-vous : "Si mon test runner devait exécuter 200 000 cas de test, combien de temps cela prendrait-il ?"

Le partitionnement est l'une des réponses disponibles dans Trade Federation. Il nécessite de diviser tous les tests dont le runner a besoin en plusieurs blocs qui peuvent être parallélisés.

Cette page explique comment rendre votre runner partitionnable pour Tradefed.

Interface à implémenter

L'interface la plus importante à implémenter pour être considérée comme partitionnable par TF est IShardableTest, qui contient deux méthodes : split(int numShard) et split().

Si votre partitionnement dépend du nombre de partitions demandées, vous devez implémenter split(int numShard). Sinon, implémentez split().

Lorsqu'une commande de test TF est exécutée avec les paramètres de partitionnement --shard-count et --shard-index, TF itère sur tous les IRemoteTest pour rechercher ceux qui implémentent IShardableTest. Si elle en trouve, elle appelle split pour obtenir un nouvel objet IRemoteTest afin d'exécuter un sous-ensemble de cas de test pour une partition spécifique.

Que dois-je savoir sur l'implémentation du fractionnement ?

  • Votre runner ne peut être partitionné que sous certaines conditions. Dans ce cas, renvoyez null lorsque vous n'avez pas partitionné.
  • Essayez de partitionner autant que possible : divisez votre runner en unités d'exécution qui lui conviennent. Cela dépend vraiment de votre runner. Par exemple : HostTest est partitionné au niveau de la classe. Chaque classe de test est placée dans une partition distincte.
  • Si cela a du sens, ajoutez des options pour contrôler un peu le partitionnement. Par exemple, AndroidJUnitTest dispose d'un ajur-max-shard pour spécifier le nombre maximal de partitions dans lesquelles il peut être divisé, quel que soit le nombre demandé.

Exemple d'implémentation détaillée

Voici un exemple d'extrait de code implémentant IShardableTest auquel vous pouvez vous référer. Le code complet est disponible à l'adresse (https://android.googlesource.com/platform/tools/tradefederation/+/refs/heads/android17-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;
    }
    ...
}

Cet exemple crée simplement une instance de lui-même et y définit des paramètres de partition. Toutefois, la logique de fractionnement peut être totalement différente d'un test à l'autre. Tant qu'elle est déterministe et génère des sous-ensembles collectivement exhaustifs, elle est acceptable.

Indépendance

Les partitions doivent être indépendantes. Deux partitions créées par votre implémentation de split dans votre runner ne doivent pas avoir de dépendances l'une par rapport à l'autre ni partager de ressources.

Le fractionnement des partitions doit être déterministe. Ceci est également obligatoire. Dans les mêmes conditions, votre méthode split doit toujours renvoyer exactement la même liste de partitions dans le même ordre.

REMARQUE : Étant donné que chaque partition peut s'exécuter sur différentes instances TF, il est essentiel de s'assurer que la logique split génère des sous-ensembles qui s'excluent mutuellement et sont collectivement exhaustifs de manière déterministe.

Partitionner un test en local

Pour partitionner un test sur un TF local, il vous suffit d'ajouter l'option --shard-count à la ligne de commande.

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

TF génère ensuite automatiquement des commandes pour chaque partition et les exécute.

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)

Agrégation des résultats des tests

Étant donné que TF n'effectue aucune agrégation des résultats des tests pour les appels partitionnés, vous devez vous assurer que votre service de reporting la prend en charge.