Scrivi un programma di test IRemoteTest suddiviso in parti

Quando scrivi un test runner, è importante pensare alla scalabilità. Chiediti: "Se il mio test runner dovesse eseguire 200.000 casi di test", quanto tempo ci vorrebbe?

Lo sharding è una delle risposte disponibili in Trade Federation. Richiede la suddivisione di tutti i test che il runner deve eseguire in diversi blocchi che possono essere parallelizzati.

Questa pagina descrive come rendere frammentabile il runner per Tradefed.

Interfaccia da implementare

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

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

Quando viene eseguito un comando di test TF con i parametri di partizionamento --shard-count e --shard-index, TF esamina tutti i IRemoteTest per cercare quelli che implementano IShardableTest. Se viene trovato, chiama split per ottenere un nuovo oggetto IRemoteTest per eseguire un sottoinsieme di scenari di test per uno shard specifico.

Cosa devo sapere dell'implementazione suddivisa?

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

Esempio di implementazione dettagliato

Di seguito è riportato un esempio di snippet di codice che implementa IShardableTest a cui puoi fare riferimento. Il codice completo è disponibile all'indirizzo (https://android.googlesource.com/platform/tools/tradefederation/+/refs/heads/android16-release/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 dello shard. Tuttavia, la logica di suddivisione può essere completamente diversa da un test all'altro e, purché sia deterministica e produca collettivamente sottoinsiemi esaustivi, va bene.

Indipendenza

Gli shard devono essere indipendenti. I due shard creati dall'implementazione di split nel runner non devono avere dipendenze reciproche o condividere risorse.

La suddivisione degli shard deve essere deterministica. Anche questo è obbligatorio, dato che, a parità di condizioni, il metodo split deve sempre restituire esattamente lo stesso elenco di shard nello stesso ordine.

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

Dividere un test in shard localmente

Per suddividere un test su un TF locale, puoi semplicemente aggiungere l'opzione --shard-count alla riga di comando.

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

TF genererà ed eseguirà automaticamente i comandi per ogni shard.

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 dei test

Poiché TF non esegue l'aggregazione dei risultati dei test per le chiamate suddivise, devi assicurarti che il tuo servizio di reporting lo supporti.