Écrire un exécuteur de test IRemoteTest segmenté

Lorsque vous écrivez un lanceur de test, il est important de penser à l'évolutivité. Demander vous-même, « si mon lanceur de test a dû exécuter 200 000 scénarios de test » combien de temps cela prendrait-il ?

La segmentation est l'une des réponses disponibles au sein de la Fédération du commerce. Elle nécessite diviser tous les tests dont l'exécuteur a besoin en plusieurs fragments pouvant être en parallèle.

Cette page explique comment rendre votre exécuteur partitionnable pour Tradefed.

Interface d'implémentation

La seule interface la plus importante à implémenter pour être considérée comme pouvant être segmentée par TF signifie IShardableTest qui contient deux méthodes: split(int numShard) et split().

Si la segmentation dépend du nombre de segments demandés, doit implémenter split(int numShard). Sinon, implémentez split().

Lorsqu'une commande de test TF est exécutée avec les paramètres de segmentation --shard-count et --shard-index, TF parcourt tous les IRemoteTest pour rechercher des uns l'implémentation de IShardableTest. S'il le trouve, il appellera split pour obtenir un nouvel objet IRemoteTest afin d'exécuter un sous-ensemble de scénarios de test pour un segment.

Que faut-il savoir à propos de l'implémentation du fractionnement ?

  • Votre exécuteur peut partitionner sous certaines conditions uniquement : Dans ce cas, renvoyez null lorsque vous n'avez pas effectué la segmentation.
  • Essayez de diviser autant que nécessaire: divisez votre coureur en unité de une exécution qui a du sens. Cela dépend vraiment de votre coureur. Pour Exemple: Test hôte est segmentée au niveau de la classe, chaque classe de test est placée dans une partition distincte.
  • Si c'est judicieux, ajoutez des options pour contrôler un peu la segmentation. Par exemple: AndroidJUnitTest comporte un champ ajur-max-shard pour indiquer le nombre maximal de segments à répartir, quel que soit le nombre demandé.

Exemple détaillé d'implémentation

Voici un exemple d'extrait de code mettant en œuvre IShardableTest que vous pouvez référence. Le code complet est disponible à l’adresse (https://android.googlesource.com/platform/tools/tradefederation/+/refs/heads/main/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 définit la partition des paramètres. Cependant, la logique de fractionnement peut être totalement différente tester pour tester ; tant qu'il est déterministe et génère collectivement des sous-ensembles exhaustifs, ce n'est pas grave.

L'indépendance

Les segments doivent être indépendants ! Deux segments créés par votre implémentation de Les split de votre exécuteur ne doivent pas avoir de dépendances entre eux et ne doivent pas partager ressources.

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

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

Segmenter un test en local

Pour segmenter un test sur un fichier 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

Ensuite, TF générera automatiquement des commandes pour chaque segment et les exécutera.

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 de test

Étant donné que TF n'agrège pas les résultats des tests pour les appels segmentés, vous vous devez vous assurer que votre service de reporting le prend en charge.