Scrivi un runner del test IRemoteTest con sharding

Quando si scrive un test runner, è importante considerare la scalabilità. Chiedi a te stesso: "Se il mio test runner dovesse eseguire 200.000 casi di test", quanto tempo ci vorrebbe?

Lo sharding è una delle risposte disponibili nella Trade Federation. Richiede di suddividere tutti i test di cui ha bisogno il runner in più parti che possono essere parallelizzate.

In questa pagina viene descritto come impostare il tuo runner con sharding per il Tradefed.

Interfaccia da implementare

L'interfaccia più importante da implementare per essere considerata shardabile da TF è IShardableTest, che contiene due metodi: split(int numShard) e split().

Se lo sharding dipenderà dal numero di shard richiesti, dovresti implementare split(int numShard). In caso contrario, implementa split().

Quando un comando di test TF viene eseguito con i parametri di suddivisione --shard-count e --shard-index, TF esegue l'iterazione di tutti i IRemoteTest per cercare quelli che implementano IShardableTest. Se viene trovato, chiamerà split per recuperare un nuovo oggetto IRemoteTest per eseguire un sottoinsieme di casi di test per uno specifico frammento.

Cosa devo sapere sull'implementazione dell'organizzazione in più parti?

  • Il runner può eseguire lo sharding solo in alcune condizioni; in questo caso restituisci null quando non hai eseguito lo sharding.
  • Cerca di dividere il più possibile: dividi il tuo runner in unità di più efficace. Dipende molto dal tuo runner. Per esempio: Test Host lo sharding è a livello di classe, ogni classe di test viene inserita in uno shard separato.
  • Se ha senso, aggiungi alcune opzioni per controllare un po' l'organizzazione in parti. Ad esempio: AndroidJUnitTest ha un valore ajur-max-shard per specificare il numero massimo di shard che può indipendentemente dal numero richiesto.

Esempio di implementazione dettagliato

Di seguito è riportato un esempio di snippet di codice che implementa IShardableTest da utilizzare come riferimento. Il codice completo è disponibile all'indirizzo

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

Questo esempio crea semplicemente una nuova istanza di se stesso e imposta i parametri del frammento. Tuttavia, la logica di suddivisione può essere totalmente diversa da un test all'altro. Se è deterministica e genera sottoinsiemi collettivamente esaustivi, non ci sono problemi.

Indipendenza

Gli shard devono essere indipendenti. Due shard creati dall'implementazione di split nel tuo runner non devono avere dipendenze tra loro o condividere risorse.

La suddivisione degli shard deve essere deterministica! Anche questo passaggio è obbligatorio, dato che stesse condizioni, il tuo metodo split dovrebbe sempre restituire esattamente lo stesso elenco di più piccoli nello stesso ordine.

NOTA: poiché ogni frammento può essere eseguito su istanze TF diverse, è fondamentale assicurarsi che la logica split generi sottoinsiemi mutuamente esclusivi e collettivamente esaustivi in modo deterministico.

Suddividere un test localmente

Per eseguire lo sharding di un test su un TF locale, puoi semplicemente aggiungere l'opzione --shard-count a la riga di comando.

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

TF genererà automaticamente i comandi per ogni shard e li eseguirà.

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)

Aggregazione dei risultati del test

Poiché TF non esegue l'aggregazione dei risultati dei test per le invocazioni suddivise in parti, devi assicurarti che il tuo servizio di generazione di report lo supporti.