Scrivi un test runner sharded IRemoteTest

Quando si scrive un test runner, è importante pensare alla scalabilità. Chiediti: "se il mio test runner dovesse eseguire 200K casi di test" quanto tempo impiegherebbe?

Sharding è una delle risposte disponibili in Trade Federation. Richiede la suddivisione di tutti i test necessari al corridore in diversi blocchi che possono essere parallelizzati.

Questa pagina descrive come rendere il tuo runner shardable per Tradefed.

Interfaccia da implementare

La singola interfaccia più importante da implementare per essere considerato shardable da TF è IShardableTest , che contiene due metodi: split(int numShard) e split() .

Se il vostro sharding sta andando a dipendere dal numero di frammenti richiesto, è necessario implementare split(int numShard) . Altrimenti, attuare split() .

Quando un comando di test TF viene eseguito con sharding parametri --shard-count e --shard-index , itera TF tutta IRemoteTest per cercare quelli attuazione IShardableTest . Se trovato, si chiamerà split per ottenere un nuovo IRemoteTest oggetto di eseguire un sottoinsieme di casi di test per una specifica shard.

Cosa devo sapere sull'implementazione della divisione?

  • Il corridore può scheggiarsi solo in determinate condizioni; in quel caso il ritorno null quando non hai Shard.
  • Prova a dividere quanto ha senso: dividi il tuo corridore in unità di esecuzione che ha senso per esso. Dipende molto dal tuo corridore. Per esempio: Hosttest è sharded a livello di classe, ogni classe di test viene messo in un frammento separato.
  • Se ha senso, aggiungi alcune opzioni per controllare un po' lo sharding. Per esempio: AndroidJUnitTest ha un ajur-max-shard per specificare il numero massimo di frammenti che potrebbe dividere in, indipendentemente dal numero richiesto.

Esempio di implementazione dettagliata

Ecco un frammento di codice di esempio attuazione IShardableTest è possibile fare riferimento. Il codice completo è disponibile su (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;
    }
    ...
}

Questo esempio crea semplicemente una nuova istanza di se stesso e imposta i parametri shard su di essa. Tuttavia, la logica di suddivisione può essere totalmente diversa da test a test; e finché è deterministico e produce sottoinsiemi collettivamente esaustivi, va bene.

Indipendenza

I frammenti devono essere indipendenti! Due frammenti creati da l'implementazione della split nel vostro corridore non dovrebbero avere dipendenze da loro o condividere risorse.

La suddivisione dei frammenti deve essere deterministica! Questo è anche obbligatoria, date le stesse condizioni, la vostra split metodo deve sempre tornare esattamente lo stesso elenco di frammenti nello stesso ordine.

NOTA: Poiché ogni frammento può funzionare in diverse istanze TF, è fondamentale per garantire la split produce logici sottoinsiemi che sono mutuamente esclusive e collettivamente esaustivo in modo deterministico.

Come shard un test localmente

Per coccio una prova su una TF locali, si può semplicemente aggiungere il --shard-count opzione alla riga di comando.

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

Quindi TF genererà automaticamente i comandi per ogni frammento 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 alcuna aggregazione dei risultati dei test per le chiamate sharded, è necessario assicurarsi che il servizio di reportistica lo supporti.