Schreiben Sie einen Shard-IRemoteTest-Testläufer

Beim Schreiben eines Testläufers ist es wichtig, über die Skalierbarkeit nachzudenken. Fragen Sie sich: „Wenn mein Testläufer 200.000 Testfälle ausführen müsste“, wie lange würde das dauern?

Sharding ist eine der in Trade Federation verfügbaren Antworten. Es erfordert die Aufteilung aller Tests, die der Läufer benötigt, in mehrere Blöcke, die parallelisiert werden können.

Auf dieser Seite wird beschrieben, wie Sie Ihren Runner für Tradefed fragmentierbar machen.

Schnittstelle zur Implementierung

Die wichtigste zu implementierende Schnittstelle, die von TF als shardbar betrachtet werden muss, ist IShardableTest , die zwei Methoden enthält: split(int numShard) und split() .

Wenn Ihr Sharding von der Anzahl der angeforderten Shards abhängt, sollten Sie split(int numShard) implementieren. Andernfalls implementieren Sie split() .

Wenn ein TF-Testbefehl mit den Sharding-Parametern --shard-count und --shard-index ausgeführt wird, durchläuft TF alle IRemoteTest , um nach solchen zu suchen, die IShardableTest implementieren. Wenn es gefunden wird, wird split aufgerufen, um ein neues IRemoteTest Objekt abzurufen, um eine Teilmenge von Testfällen für einen bestimmten Shard auszuführen.

Was sollte ich über die Split-Implementierung wissen?

  • Ihr Läufer kann nur unter bestimmten Bedingungen splittern; Geben Sie in diesem Fall null zurück, wenn Sie kein Shard durchgeführt haben.
  • Versuchen Sie, so viel aufzuteilen, wie es sinnvoll ist: Teilen Sie Ihren Läufer in Ausführungseinheiten auf, die für ihn sinnvoll sind. Es hängt wirklich von Ihrem Läufer ab. Beispiel: HostTest wird auf Klassenebene geshardt, jede Testklasse wird in einem separaten Shard abgelegt.
  • Wenn es sinnvoll ist, fügen Sie einige Optionen hinzu, um das Sharding ein wenig zu steuern. Beispiel: AndroidJUnitTest verfügt über einen ajur-max-shard um die maximale Anzahl an Shards anzugeben, in die es aufgeteilt werden kann, unabhängig von der angeforderten Anzahl.

Detaillierte Beispielimplementierung

Hier ist ein Beispielcode-Snippet zur Implementierung IShardableTest , auf das Sie verweisen können. Der vollständige Code ist verfügbar unter (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;
    }
    ...
}

In diesem Beispiel wird einfach eine neue Instanz von sich selbst erstellt und Shard-Parameter dafür festgelegt. Allerdings kann die Aufteilungslogik von Test zu Test völlig unterschiedlich sein; und solange es deterministisch ist und insgesamt umfassende Teilmengen liefert, ist es in Ordnung.

Unabhängigkeit

Shards müssen unabhängig sein! Zwei Shards, die durch Ihre split Implementierung in Ihrem Runner erstellt wurden, sollten keine Abhängigkeiten voneinander aufweisen oder Ressourcen gemeinsam nutzen.

Die Aufteilung von Shards muss deterministisch sein! Dies ist auch obligatorisch, da Ihre split Methode unter denselben Bedingungen immer genau dieselbe Liste von Shards in derselben Reihenfolge zurückgeben sollte.

HINWEIS: Da jeder Shard auf verschiedenen TF-Instanzen ausgeführt werden kann, ist es wichtig sicherzustellen, dass die split Logik Teilmengen ergibt, die sich gegenseitig ausschließen und auf deterministische Weise kollektiv erschöpfend sind.

Shard einen Test lokal

Um einen Test auf einem lokalen TF zu teilen, können Sie einfach die Option --shard-count zur Befehlszeile hinzufügen.

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

Dann erzeugt TF automatisch Befehle für jeden Shard und führt sie aus.

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)

Aggregation der Testergebnisse

Da TF keine Testergebnisaggregation für Shard-Aufrufe durchführt, müssen Sie sicherstellen, dass Ihr Berichtsdienst dies unterstützt.