Sharded IRemoteTest-Test-Runner schreiben

Beim Schreiben eines Test-Runners ist es wichtig, die Skalierbarkeit zu berücksichtigen. Fragen Sie sich: „Wenn mein Test-Runner 200.000 Testläufe ausführen müsste, wie lange würde das dauern?“

Sharding ist eine der Antworten, die in Trade Federation verfügbar sind. Dazu müssen alle Tests, die der Runner benötigt, in mehrere Chunks aufgeteilt werden, die parallelisiert werden können.

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

Zu implementierende Schnittstelle

Die wichtigste Schnittstelle, die implementiert werden muss, damit TF sie als shardable betrachtet, ist IShardableTest. Sie enthält zwei Methoden: split(int numShard) und split().

Wenn Ihr Sharding von der Anzahl der angeforderten Shards abhängt, sollten Sie split(int numShard) implementieren. Implementieren Sie andernfalls 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 sie gefunden wird, wird split aufgerufen, um ein neues IRemoteTest-Objekt abzurufen, mit dem eine Teilmenge von Testläufen für einen bestimmten Shard ausgeführt werden kann.

Was sollte ich über die Split-Implementierung wissen?

  • Ihr Runner kann nur unter bestimmten Bedingungen sharden. Geben Sie in diesem Fall null zurück, wenn Sie nicht geshardet haben.
  • Versuchen Sie, den Runner so weit wie möglich aufzuteilen, sodass er in sinnvolle Ausführungseinheiten unterteilt wird. Das hängt von Ihrem Runner ab. Beispiel: HostTest wird auf Klassenebene aufgeteilt. Jede Testklasse wird in einen separaten Shard eingefügt.
  • Fügen Sie bei Bedarf einige Optionen hinzu, um das Sharding etwas zu steuern. Beispiel: AndroidJUnitTest hat ein ajur-max-shard, um die maximale Anzahl von Shards anzugeben, in die es aufgeteilt werden kann, unabhängig von der angeforderten Anzahl.

Detaillierte Beispielimplementierung

Hier ist ein Beispielcode-Snippet für die Implementierung von IShardableTest, das Sie als Referenz verwenden können. Der vollständige Code ist unter https://android.googlesource.com/platform/tools/tradefederation/+/refs/heads/android16-release/test_framework/com/android/tradefed/testtype/InstalledInstrumentationsTest.java verfügbar.

/**
 * 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 es werden Shard-Parameter dafür festgelegt. Die Aufteilungslogik kann sich jedoch von Test zu Test völlig unterscheiden. Solange sie deterministisch ist und kollektiv erschöpfende Teilmengen ergibt, ist das in Ordnung.

Unabhängigkeit

Shards müssen unabhängig sein. Zwei Shards, die durch Ihre Implementierung von split in Ihrem Runner erstellt werden, dürfen keine Abhängigkeiten voneinander haben und keine Ressourcen gemeinsam nutzen.

Die Aufteilung in Shards muss deterministisch sein. Das ist auch obligatorisch. Unter denselben Bedingungen sollte Ihre split-Methode immer genau dieselbe Liste von Shards in derselben Reihenfolge zurückgeben.

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

Test lokal aufteilen

Wenn Sie einen Test auf einer lokalen TF aufteilen möchten, fügen Sie der Befehlszeile einfach die Option --shard-count hinzu.

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

TF generiert dann 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)

Zusammenfassung der Testergebnisse

Da TF keine Testresultate für Shard-Aufrufe zusammenfasst, muss Ihr Berichterstellungsdienst dies unterstützen.