Écrire un exécuteur de test IRemoteTest segmenté

Lorsque vous écrivez un lanceur de test, il est important de penser à l'évolutivité. Demandez-vous : "Si mon outil d'exécution 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. Elle nécessite de diviser tous les tests dont l'exécuteur a besoin en plusieurs fragments pouvant être parallélisés.

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

Interface d'implémentation

L'interface la plus importante à mettre en œuvre pour être considérée comme pouvant être segmentée par TF est IShardableTest, qui contient deux méthodes: split(int numShard) et split().

Si la segmentation est destinée à dépendre du nombre de segments demandés, 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 fractionnement --shard-count et --shard-index, TF itère sur tous les IRemoteTest pour rechercher ceux qui implémentent IShardableTest. S'il le trouve, il appelle split pour obtenir un nouvel objet IRemoteTest afin d'exécuter un sous-ensemble de scénarios de test pour un segment spécifique.

Que dois-je savoir sur l'implémentation de la division ?

  • Votre exécuteur peut effectuer un fractionnement uniquement dans certaines conditions. Dans ce cas, renvoyez null lorsque vous n'avez pas effectué de fractionnement.
  • Essayez de diviser autant que nécessaire: divisez votre exécuteur en unité d'exécution appropriée. Cela dépend vraiment de votre exécuteur. Par exemple, HostTest est partitionné au niveau de la classe. Chaque classe de test est placée dans un segment distinct.
  • Si cela est pertinent, ajoutez quelques options pour contrôler un peu le fractionnement. Par exemple, AndroidJUnitTest possède un ajur-max-shard pour spécifier le nombre maximal de fragments qu'il peut diviser, quel que soit le nombre demandé.

Exemple d'implémentation détaillé

Voici un exemple d'extrait de code implémentant IShardableTest que vous pouvez consulter. Le code complet est disponible à l'adresse suivante : (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 lui attribue des paramètres de fragment. Cependant, la logique de répartition peut être totalement différente d'un test à l'autre. Tant qu'elle est déterministe et qu'elle génère des sous-ensembles collectivement exhaustifs, tout va bien.

Independence

Les segments doivent être indépendants ! Les deux fragments créés par votre implémentation de split dans votre exécuteur ne doivent pas dépendre les uns des autres ni partager de ressources.

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

REMARQUE: Étant donné que chaque fragment 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 mutuellement exclusifs et collectivement exhaustifs de manière déterministe.

Diviser 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 des tests

Étant donné que TF n'effectue aucune agrégation des résultats de test pour les appels segmentés, vous devez vous assurer que votre service de création de rapports est compatible avec cette fonctionnalité.