Sharded IRemoteTest-Test-Runner schreiben

Beim Erstellen eines Testlaufs ist es wichtig, die Skalierbarkeit zu berücksichtigen. Fragen Sie sich: „Wie lange würde es dauern, wenn mein Test-Runner 200.000 Testfälle ausführen müsste?“

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

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

Zu implementierende Schnittstelle

Die wichtigste Schnittstelle, die Sie implementieren müssen, damit Ihr Test von TF als shardbar eingestuft wird, ist IShardableTest. Sie enthält zwei Methoden: split(int numShard) und split().

Wenn das Sharding von der Anzahl der angeforderten Shards abhängen soll, 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, durchsucht TF alle IRemoteTest nach solchen, die IShardableTest implementieren. Wenn ein Shard gefunden wird, wird split aufgerufen, um ein neues IRemoteTest-Objekt abzurufen, mit dem eine Teilmenge der Testfälle für einen bestimmten Shard ausgeführt wird.

Was sollte ich über die gesplittete Implementierung wissen?

  • Ihr Runner kann nur unter bestimmten Bedingungen gesplittet werden. Geben Sie in diesem Fall null zurück, wenn keine Sharding-Technologie verwendet wurde.
  • Versuchen Sie, so viel wie möglich zu teilen: Teilen Sie Ihren Runner in eine sinnvolle Ausführungseinheit auf. Das hängt von deinem Laufschuh ab. Beispiel: HostTest wird auf Klassenebene geSharded. Jede Testklasse wird in einem separaten Shard gespeichert.
  • Fügen Sie nach 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 unabhängig von der angeforderten Anzahl aufgeteilt werden kann.

Detaillierte Beispielimplementierung

Hier ist ein Beispiel für ein Code-Snippet, in dem IShardableTest implementiert ist. 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 selbst erstellt und Shard-Parameter werden dafür festgelegt. Die Aufteilungslogik kann jedoch von Test zu Test völlig unterschiedlich sein. Solange sie deterministisch ist und insgesamt erschöpfende Teilmengen liefert, ist das in Ordnung.

Unabhängigkeit

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

Die Shard-Aufteilung muss deterministisch sein. Dies ist ebenfalls 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 deterministisch zu Teilmengen führt, die sich gegenseitig ausschließen und insgesamt erschöpfend sind.

Test lokal in Shards aufteilen

Wenn Sie einen Test auf einem lokalen TF-Server partitionieren möchten, fügen Sie einfach die Option --shard-count zur Befehlszeile hinzu.

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

Anschließend werden von TF automatisch Befehle für jeden Shard generiert und ausgeführt.

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)

Testergebnisaggregation

Da in TF keine Testergebnisaggregation für ge shardete Aufrufe erfolgt, muss Ihr Berichtsdienst diese Funktion unterstützen.