Écrire un exécuteur de test IRemoteTest partagé

Lors de l'écriture d'un exécuteur de test, il est important de penser à l'évolutivité. Demandez-vous : « si mon exécuteur de tests devait exécuter 200 000 cas de test », combien de temps cela prendrait-il ?

Le Sharding est l’une des réponses disponibles dans Trade Federation. Cela nécessite de diviser tous les tests dont le coureur a besoin en plusieurs morceaux pouvant être parallélisés.

Cette page décrit comment rendre votre coureur partageable pour Tradefed.

Interface à mettre en œuvre

L'interface la plus importante à implémenter pour être considérée comme partageable 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 parcourt tous les IRemoteTest pour rechercher ceux implémentant IShardableTest . S'il est trouvé, il appellera 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 la mise en œuvre fractionnée ?

  • Votre coureur peut éclater sous certaines conditions uniquement ; dans ce cas, renvoyez null lorsque vous n'avez pas partitionné.
  • Essayez de diviser autant que cela a du sens : divisez votre coureur en unités d'exécution qui lui conviennent. Cela dépend vraiment de votre coureur. Par exemple : HostTest est fragmenté au niveau de la classe, chaque classe de test est placée dans une partition distincte.
  • Si cela a du sens, ajoutez quelques options pour contrôler un peu le partitionnement. Par exemple : AndroidJUnitTest a un ajur-max-shard pour spécifier le nombre maximum de fragments dans lesquels il peut être divisé, quel que soit le nombre demandé.

Exemple détaillé de mise en œuvre

Voici un exemple d'extrait de code implémentant IShardableTest que vous pouvez référencer. Le code complet est disponible sur (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 nouvelle instance de lui-même et lui définit les paramètres de partition. Cependant, la logique de fractionnement peut être totalement différente d’un test à l’autre ; et tant qu'il est déterministe et produit des sous-ensembles collectivement exhaustifs, tout va bien.

Indépendance

Les fragments doivent être indépendants ! Deux fragments créés par votre implémentation de split dans votre runner ne doivent pas avoir de dépendances l'un sur l'autre ni partager de ressources.

Le fractionnement des fragments 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 fragments dans le même ordre.

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

Partager un test localement

Pour partager un test sur un TF local, vous pouvez simplement 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 fragment 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 des tests

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