Écrire un exécuteur de test IRemoteTest partagé

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

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 qui peuvent être parallélisés.

Cette page décrit comment rendre votre runner partageable 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 --shard-count et --shard-index , TF parcourt tous les IRemoteTest pour rechercher ceux qui implémentent 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 un fragment spécifique.

Que dois-je savoir sur la mise en œuvre fractionnée ?

  • Votre coureur ne peut partager que sous certaines conditions ; dans ce cas, retournez null lorsque vous n'avez pas partitionné.
  • Essayez de diviser autant que cela a du sens : divisez votre coureur en unité d'exécution qui lui convient. Cela dépend vraiment de votre coureur. 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 quelques options pour contrôler un peu le sharding. Par exemple : AndroidJUnitTest a un ajur-max-shard pour spécifier le nombre maximal de fragments qu'il peut diviser, quel que soit le nombre demandé.

Exemple de mise en œuvre détaillée

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/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;
    }
    ...
}

Cet exemple crée simplement une nouvelle instance de lui-même et lui définit des paramètres de partition. Cependant, la logique de division peut être totalement différente d'un test à l'autre ; et tant qu'il est déterministe et donne des sous-ensembles collectivement exhaustifs, ça va.

Indépendance

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

Le fractionnement des fragments doit être déterministe ! Ceci est également obligatoire, étant donné les mêmes conditions, votre méthode de 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 s'assurer que la logique de split produit des sous-ensembles mutuellement exclusifs et collectivement exhaustifs de manière déterministe.

Comment partitionner un test localement

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

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